Hypersonic
view release on metacpan or search on metacpan
t/0019-streaming.t view on Meta::CPAN
# ============================================================
# Test 4: Stream headers API
# ============================================================
subtest 'Stream headers API' => sub {
plan tests => 2;
my $stream = Hypersonic::Stream->new(fd => 100);
# Chaining works
my $ret = $stream->headers(200, { 'Content-Type' => 'text/plain' });
is($ret, $stream, 'headers() returns $self for chaining');
# Content-type method
$ret = $stream->content_type('application/json');
is($ret, $stream, 'content_type() returns $self for chaining');
};
# ============================================================
# Test 5: Stream state constants
# ============================================================
subtest 'Stream state constants' => sub {
plan tests => 4;
is(Hypersonic::Stream->STATE_INIT, 0, 'STATE_INIT = 0');
is(Hypersonic::Stream->STATE_STARTED, 1, 'STATE_STARTED = 1');
is(Hypersonic::Stream->STATE_FINISHED, 2, 'STATE_FINISHED = 2');
is(Hypersonic::Stream->STATE_ABORTED, 3, 'STATE_ABORTED = 3');
};
# ============================================================
# Test 6: Stream accessors
# ============================================================
subtest 'Stream accessors' => sub {
plan tests => 3;
my $stream = Hypersonic::Stream->new(
fd => 10,
protocol => 'http2',
);
is($stream->fd, 10, 'fd accessor');
is($stream->protocol, 'http2', 'protocol accessor');
is($stream->chunks_sent, 0, 'chunks_sent starts at 0');
};
# ============================================================
# Test 7: Stream state tracking
# ============================================================
subtest 'Stream state tracking' => sub {
plan tests => 2;
my $stream = Hypersonic::Stream->new(fd => 200);
is($stream->state, 0, 'state starts at INIT');
ok(!$stream->is_finished, 'not finished initially');
};
# ============================================================
# Test 8-10: Streaming handler detection
# ============================================================
subtest 'is_streaming_handler with explicit flag' => sub {
plan tests => 2;
my $handler = sub { };
ok(
Hypersonic::Stream->is_streaming_handler($handler, { streaming => 1 }),
'explicit streaming => 1 detected'
);
ok(
!Hypersonic::Stream->is_streaming_handler($handler, { streaming => 0 }),
'explicit streaming => 0 not detected'
);
};
subtest 'is_streaming_handler with no options' => sub {
plan tests => 1;
my $handler = sub { my ($req) = @_; return { status => 200 }; };
ok(
!Hypersonic::Stream->is_streaming_handler($handler, {}),
'regular handler not detected as streaming'
);
};
subtest 'is_streaming_handler with code analysis' => sub {
plan tests => 1;
# Handler that uses $stream->
my $handler = sub {
my ($req, $stream) = @_;
$stream->write("test");
};
# Note: code analysis may or may not detect this depending on B::Deparse
ok(1, 'code analysis attempted');
};
# ============================================================
# Test 11-14: Route registration with streaming flag
# ============================================================
subtest 'Route with streaming flag' => sub {
plan tests => 4;
my $app = Hypersonic->new();
$app->get('/stream' => sub {
my ($req, $stream) = @_;
$stream->write("test");
$stream->end();
}, { streaming => 1 });
my $route = $app->{routes}[0];
ok($route->{streaming}, 'route has streaming flag');
ok($route->{dynamic}, 'streaming route is dynamic');
is($route->{features}{streaming}, 1, 'features has streaming');
is($route->{path}, '/stream', 'path correct');
};
subtest 'Route without streaming flag' => sub {
plan tests => 2;
my $app = Hypersonic->new();
$app->get('/normal' => sub {
return { status => 200, body => 'hello' };
});
my $route = $app->{routes}[0];
ok(!$route->{streaming}, 'regular route has no streaming flag');
is($route->{features}{streaming}, 0, 'features has streaming = 0');
};
subtest 'Route analysis detects streaming' => sub {
plan tests => 2;
my $app = Hypersonic->new();
$app->get('/stream' => sub {
my ($req, $stream) = @_;
$stream->write("data");
$stream->end();
}, { streaming => 1 });
my %analysis = (needs_streaming => 0);
for my $route (@{$app->{routes}}) {
if ($route->{features}{streaming}) {
$analysis{needs_streaming} = 1;
}
}
ok($analysis{needs_streaming}, 'analysis detects streaming routes');
# Non-streaming app
my $app2 = Hypersonic->new();
$app2->get('/normal' => sub { return 'hello' });
%analysis = (needs_streaming => 0);
for my $route (@{$app2->{routes}}) {
if ($route->{features}{streaming}) {
$analysis{needs_streaming} = 1;
}
}
ok(!$analysis{needs_streaming}, 'analysis does not detect streaming for normal routes');
};
subtest 'Multiple routes mixed streaming' => sub {
plan tests => 3;
my $app = Hypersonic->new();
$app->get('/normal' => sub { return 'hello' });
$app->get('/stream' => sub { }, { streaming => 1 });
$app->post('/data' => sub { }, { dynamic => 1 });
my @streaming = grep { $_->{streaming} } @{$app->{routes}};
my @dynamic = grep { $_->{dynamic} } @{$app->{routes}};
is(scalar(@streaming), 1, 'one streaming route');
is(scalar(@dynamic), 2, 'two dynamic routes (streaming is dynamic)');
is($app->{routes}[1]{streaming}, 1, 'correct route is streaming');
};
# ============================================================
# Test 15: Code generation produces valid C
# ============================================================
subtest 'Stream code generation' => sub {
plan tests => 6;
my $builder = XS::JIT::Builder->new;
Hypersonic::Stream->generate_c_code($builder);
my $code = $builder->code;
like($code, qr/STREAM_STATE_INIT/, 'defines STREAM_STATE_INIT');
like($code, qr/STREAM_STATE_STARTED/, 'defines STREAM_STATE_STARTED');
like($code, qr/STREAM_STATE_FINISHED/, 'defines STREAM_STATE_FINISHED');
like($code, qr/StreamState/, 'defines StreamState struct');
like($code, qr/stream_registry/, 'has stream registry');
like($code, qr/xs_stream_new/, 'has xs_stream_new');
};
# ============================================================
# Test 16-18: Hypersonic integration
# ============================================================
subtest 'Hypersonic compile with streaming route' => sub {
plan tests => 2;
my $app = Hypersonic->new();
$app->get('/events' => sub {
my ($req, $stream) = @_;
$stream->write("event: test\n");
$stream->end();
}, { streaming => 1 });
$app->get('/' => sub { return 'hello' });
eval { $app->compile() };
ok(!$@, 'compile succeeds with streaming route') or diag($@);
ok($app->{route_analysis}{needs_streaming}, 'analysis has needs_streaming flag');
};
subtest 'Hypersonic compile without streaming route' => sub {
plan tests => 2;
my $app = Hypersonic->new();
$app->get('/' => sub { return 'hello' });
$app->get('/about' => sub { return 'about' });
eval { $app->compile() };
ok(!$@, 'compile succeeds without streaming route') or diag($@);
ok(!$app->{route_analysis}{needs_streaming}, 'analysis has no needs_streaming flag');
};
subtest 'Feature flags in analysis' => sub {
plan tests => 3;
my $app = Hypersonic->new();
$app->get('/stream' => sub { }, { streaming => 1 });
$app->get('/json' => sub { }, { dynamic => 1, parse_json => 1 });
my %analysis = (
needs_streaming => 0,
needs_json => 0,
);
for my $route (@{$app->{routes}}) {
my $f = $route->{features} // {};
$analysis{needs_streaming} = 1 if $f->{streaming};
$analysis{needs_json} = 1 if $f->{parse_json};
}
ok($analysis{needs_streaming}, 'streaming detected');
ok($analysis{needs_json}, 'json detected');
# Verify they're independent
my $app2 = Hypersonic->new();
$app2->get('/stream' => sub { }, { streaming => 1 });
%analysis = (needs_streaming => 0, needs_json => 0);
for my $route (@{$app2->{routes}}) {
my $f = $route->{features} // {};
$analysis{needs_streaming} = 1 if $f->{streaming};
$analysis{needs_json} = 1 if $f->{parse_json};
}
ok(!$analysis{needs_json}, 'json not detected when not used');
};
done_testing();
( run in 0.616 second using v1.01-cache-2.11-cpan-140bd7fdf52 )