Chandra

 view release on metacpan or  search on metacpan

t/26_socket.t  view on Meta::CPAN

#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;

my $is_win32 = $^O eq 'MSWin32';

unless ($is_win32) {
    require IO::Socket::UNIX;
}

# Use done_testing() instead of fixed plan for cross-platform compatibility

use_ok('Chandra::Socket::Connection');
use_ok('Chandra::Socket::Hub');
use_ok('Chandra::Socket::Client');

# === Frame encoding ===
{
	my $frame = Chandra::Socket::Connection->encode_frame({ channel => 'test', data => { x => 1 } });
	ok(defined $frame, 'encode_frame returns data');
	my $len = unpack('N', substr($frame, 0, 4));
	is($len, length($frame) - 4, 'length prefix matches payload');
}

# === Frame decoding ===
{
	my $msg = { channel => 'hello', data => { name => 'world' } };
	my $frame = Chandra::Socket::Connection->encode_frame($msg);
	my @decoded = Chandra::Socket::Connection->decode_frames($frame);
	is(scalar @decoded, 1, 'decoded one message');
	is($decoded[0]->{channel}, 'hello', 'channel preserved');
	is($decoded[0]->{data}{name}, 'world', 'data preserved');
}

# === Multiple frames in one buffer ===
{
	my $f1 = Chandra::Socket::Connection->encode_frame({ channel => 'a', data => 1 });
	my $f2 = Chandra::Socket::Connection->encode_frame({ channel => 'b', data => 2 });
	my @decoded = Chandra::Socket::Connection->decode_frames($f1 . $f2);
	is(scalar @decoded, 2, 'decoded two messages from concatenated buffer');
	is($decoded[0]->{channel}, 'a', 'first message channel');
	is($decoded[1]->{channel}, 'b', 'second message channel');
}

# === Partial frame decoded as empty ===
{
	my $frame = Chandra::Socket::Connection->encode_frame({ channel => 'x', data => 1 });
	my $partial = substr($frame, 0, 6);  # incomplete payload
	my @decoded = Chandra::Socket::Connection->decode_frames($partial);
	is(scalar @decoded, 0, 'partial frame returns no messages');
}

# === Connection via socketpair ===
SKIP: {
	skip 'AF_UNIX socketpair not available on Windows', 13 if $is_win32;
	use Socket;
	socketpair(my $s1, my $s2, AF_UNIX, SOCK_STREAM, 0)
		or die "socketpair: $!";
	$s1->blocking(0);
	$s2->blocking(0);

	my $conn1 = Chandra::Socket::Connection->new(socket => $s1, name => 'side-a');
	my $conn2 = Chandra::Socket::Connection->new(socket => $s2, name => 'side-b');

	ok($conn1->is_connected, 'conn1 is connected');
	ok($conn2->is_connected, 'conn2 is connected');
	is($conn1->name, 'side-a', 'conn1 name');
	is($conn2->name, 'side-b', 'conn2 name');

	# Send from conn1, receive on conn2
	ok($conn1->send('ping', { val => 42 }), 'conn1 send succeeded');

	# Small delay for the socket buffer
	select(undef, undef, undef, 0.05);

	my @msgs = $conn2->recv;
	is(scalar @msgs, 1, 'conn2 received one message');
	is($msgs[0]->{channel}, 'ping', 'channel is ping');
	is($msgs[0]->{data}{val}, 42, 'data val is 42');
	is($msgs[0]->{from}, 'side-a', 'from is side-a');

	# Reply
	ok($conn2->send('pong', { val => 99 }), 'conn2 reply succeeded');
	select(undef, undef, undef, 0.05);

	my @replies = $conn1->recv;
	is(scalar @replies, 1, 'conn1 received reply');
	is($replies[0]->{channel}, 'pong', 'reply channel is pong');
	is($replies[0]->{data}{val}, 99, 'reply data val is 99');

	# Close
	$conn1->close;
	ok(!$conn1->is_connected, 'conn1 disconnected after close');

	$conn2->close;
}

# === Hub creation (Unix socket) ===
SKIP: {
	skip 'Unix socket path tests not applicable on Windows', 5 if $is_win32;
	my $name = "test-hub-$$";
	my $dir = $ENV{XDG_RUNTIME_DIR} || $ENV{TMPDIR} || '/tmp';
	my $path = "$dir/chandra-$name.sock";
	my $token_path = "$path.token";
	unlink $path if -e $path;

	my $hub = Chandra::Socket::Hub->new(name => $name);
	ok(-e $path, 'Unix socket file created');
	ok(-e $token_path, 'token file created');
	ok(length($hub->token) >= 16, 'token is non-trivial');



( run in 0.546 second using v1.01-cache-2.11-cpan-600a1bdf6e4 )