NAME

PAGI::Test::WebSocket - WebSocket connection for testing PAGI applications

SYNOPSIS

use PAGI::Test::Client;

my $client = PAGI::Test::Client->new(app => $ws_app);

# Callback style (auto-close)
$client->websocket('/ws', sub {
    my ($ws) = @_;
    $ws->send_text('hello');
    is $ws->receive_text, 'echo: hello';
});

# Explicit style
my $ws = $client->websocket('/ws');
$ws->send_text('hello');
is $ws->receive_text, 'echo: hello';
$ws->close;

# JSON convenience
$ws->send_json({ action => 'ping' });
my $data = $ws->receive_json;

DESCRIPTION

PAGI::Test::WebSocket provides a test client for WebSocket connections in PAGI applications. It handles the WebSocket protocol handshake and message exchange, making it easy to test WebSocket endpoints without starting a real server.

This module is typically used via PAGI::Test::Client's websocket method rather than directly.

CONSTRUCTOR

new

my $ws = PAGI::Test::WebSocket->new(
    app   => $app,     # Required: PAGI app coderef
    scope => $scope,   # Required: WebSocket scope hashref
);

Creates a new WebSocket test connection. Typically you don't call this directly; use PAGI::Test::Client's websocket method instead.

METHODS

send_text

$ws->send_text('Hello, server!');

Sends a text message to the WebSocket application.

send_bytes

$ws->send_bytes("\x00\x01\x02\x03");

Sends a binary message to the WebSocket application.

send_json

$ws->send_json({ action => 'ping', id => 123 });

Encodes a Perl data structure as JSON and sends it as a text message.

receive_text

my $text = $ws->receive_text;
my $text = $ws->receive_text($timeout);  # custom timeout in seconds

Waits for and returns the next text message from the server. Returns undef if the connection is closed. Throws an exception if timeout is reached (default: 5 seconds).

Only returns text messages; binary messages are skipped.

receive_bytes

my $bytes = $ws->receive_bytes;
my $bytes = $ws->receive_bytes($timeout);

Waits for and returns the next binary message from the server. Returns undef if the connection is closed. Throws an exception if timeout is reached (default: 5 seconds).

Only returns binary messages; text messages are skipped.

receive_json

my $data = $ws->receive_json;
my $data = $ws->receive_json($timeout);

Waits for a text message, decodes it as JSON, and returns the resulting Perl data structure. Dies if the message is not valid JSON.

close

$ws->close;
$ws->close($code);
$ws->close($code, $reason);

Closes the WebSocket connection. Default close code is 1000 (normal closure).

close_code

my $code = $ws->close_code;

Returns the WebSocket close code if the connection has been closed, or undef if still open.

close_reason

my $reason = $ws->close_reason;

Returns the WebSocket close reason if the connection has been closed, or an empty string if still open.

is_closed

if ($ws->is_closed) {
    say "Connection closed";
}

Returns true if the WebSocket connection has been closed.

INTERNAL METHODS

_start

$ws->_start;

Internal method called by PAGI::Test::Client to start the WebSocket connection, send the initial connect event, and wait for acceptance.

WEBSOCKET PROTOCOL

This module implements the PAGI WebSocket protocol:

1. Test sends websocket.connect event
2. App sends websocket.accept event
3. Test sends websocket.receive events with text or bytes
4. App sends websocket.send events with text or bytes
5. Either side sends websocket.disconnect or websocket.close

EXAMPLE

use Test2::V0;
use PAGI::Test::Client;
use Future::AsyncAwait;

# Simple echo WebSocket app
my $ws_app = async sub {
    my ($scope, $receive, $send) = @_;
    return unless $scope->{type} eq 'websocket';

    my $event = await $receive->();
    return unless $event->{type} eq 'websocket.connect';

    await $send->({ type => 'websocket.accept' });

    while (1) {
        my $msg = await $receive->();
        last if $msg->{type} eq 'websocket.disconnect';

        if (defined $msg->{text}) {
            await $send->({
                type => 'websocket.send',
                text => "echo: $msg->{text}"
            });
        }
    }
};

# Test it
my $client = PAGI::Test::Client->new(app => $ws_app);
$client->websocket('/ws', sub {
    my ($ws) = @_;
    $ws->send_text('hello');
    is $ws->receive_text, 'echo: hello', 'echoed text';
});

SEE ALSO

PAGI::Test::Client, PAGI::Test::Response, PAGI::WebSocket

AUTHOR

PAGI Contributors