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 )