Hypersonic

 view release on metacpan or  search on metacpan

t/0027-chunked-encoding.t  view on Meta::CPAN

# ============================================================
subtest 'build_chunk formats correctly' => sub {
    plan tests => 4;
    
    # Simple text
    my $chunk = Hypersonic::Protocol::HTTP1->build_chunk("Hello");
    is($chunk, "5\r\nHello\r\n", 'simple chunk format');
    
    # Longer text
    $chunk = Hypersonic::Protocol::HTTP1->build_chunk("Hello World!");
    is($chunk, "c\r\nHello World!\r\n", '12-byte chunk uses hex c');
    
    # Empty data
    $chunk = Hypersonic::Protocol::HTTP1->build_chunk("");
    is($chunk, '', 'empty data returns empty string');
    
    # Undefined data
    $chunk = Hypersonic::Protocol::HTTP1->build_chunk(undef);
    is($chunk, '', 'undef data returns empty string');
};

subtest 'build_final_chunk format' => sub {
    plan tests => 1;
    
    my $final = Hypersonic::Protocol::HTTP1->build_final_chunk();
    is($final, "0\r\n\r\n", 'final chunk is 0\\r\\n\\r\\n');
};

subtest 'chunk size hex encoding' => sub {
    plan tests => 4;
    
    # Various sizes to test hex encoding
    my $chunk = Hypersonic::Protocol::HTTP1->build_chunk("x" x 15);
    like($chunk, qr/^f\r\n/, '15 bytes = hex f');
    
    $chunk = Hypersonic::Protocol::HTTP1->build_chunk("x" x 16);
    like($chunk, qr/^10\r\n/, '16 bytes = hex 10');
    
    $chunk = Hypersonic::Protocol::HTTP1->build_chunk("x" x 255);
    like($chunk, qr/^ff\r\n/, '255 bytes = hex ff');
    
    $chunk = Hypersonic::Protocol::HTTP1->build_chunk("x" x 256);
    like($chunk, qr/^100\r\n/, '256 bytes = hex 100');
};

# ============================================================
# Test 6-8: JIT code generation for chunked encoding
# ============================================================
subtest 'gen_chunked_start generates correct code' => sub {
    plan tests => 5;
    
    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;
    
    Hypersonic::Protocol::HTTP1->gen_chunked_start($builder);
    my $code = $builder->code;
    
    like($code, qr/void send_chunked_headers\(/, 'function defined');
    like($code, qr/Transfer-Encoding: chunked/, 'chunked header included');
    like($code, qr/Content-Type: %s/, 'content-type placeholder');
    like($code, qr/Connection: keep-alive/, 'keep-alive for streaming');
    like($code, qr/send\(fd, headers/, 'sends headers');
};

subtest 'gen_chunked_write uses writev' => sub {
    plan tests => 4;
    
    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;
    
    Hypersonic::Protocol::HTTP1->gen_chunked_write($builder);
    my $code = $builder->code;
    
    like($code, qr/void send_chunk\(/, 'function defined');
    like($code, qr/%zx/, 'hex format for size');
    like($code, qr/struct iovec iov\[3\]/, '3-part iovec');
    like($code, qr/writev\(fd, iov, 3\)/, 'writev for efficiency');
};

subtest 'gen_chunked_end sends final chunk' => sub {
    plan tests => 2;
    
    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;
    
    Hypersonic::Protocol::HTTP1->gen_chunked_end($builder);
    my $code = $builder->code;
    
    like($code, qr/void send_chunk_end\(/, 'function defined');
    like($code, qr/0\\r\\n\\r\\n/, 'final chunk format');
};

# ============================================================
# Test 9-11: Stream.pm chunked integration
# ============================================================
subtest 'Stream generates chunked headers' => sub {
    plan tests => 3;

    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;

    Hypersonic::Stream->gen_stream_start_c($builder);
    my $code = $builder->code;

    like($code, qr/Transfer-Encoding: chunked/, 'chunked header');
    like($code, qr/HTTP\/1\.1 %d %s/, 'HTTP/1.1 status line');
    like($code, qr/Content-Type: %s/, 'content-type included');
};

subtest 'Stream generates chunk writes with writev' => sub {
    plan tests => 2;

    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;

    Hypersonic::Stream->gen_stream_write_chunk_c($builder);
    my $code = $builder->code;

    like($code, qr/writev/, 'uses writev');
    like($code, qr/iovec/, 'uses iovec struct');
};

subtest 'Stream generates final chunk' => sub {
    plan tests => 1;

    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;

    Hypersonic::Stream->gen_stream_end_c($builder);
    my $code = $builder->code;

    like($code, qr/0\\r\\n\\r\\n/, 'final chunk format');
};

# ============================================================
# Test 12-13: RFC 7230 compliance
# ============================================================
subtest 'Chunk format follows RFC 7230' => sub {
    plan tests => 3;
    
    # RFC 7230 Section 4.1:
    # chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
    # chunk-size = 1*HEXDIG
    
    my $chunk = Hypersonic::Protocol::HTTP1->build_chunk("test");
    
    # Must start with hex digits
    like($chunk, qr/^[0-9a-f]+\r\n/, 'starts with hex size + CRLF');
    
    # Must end with CRLF
    like($chunk, qr/\r\n$/, 'ends with CRLF');
    
    # Data in the middle
    like($chunk, qr/^4\r\ntest\r\n$/, 'complete format correct');
};

subtest 'Final chunk follows RFC 7230' => sub {
    plan tests => 2;
    
    # RFC 7230 Section 4.1:
    # last-chunk = 1*("0") [ chunk-ext ] CRLF
    # trailer-part = *( header-field CRLF )
    # CRLF
    
    my $final = Hypersonic::Protocol::HTTP1->build_final_chunk();
    
    # "0" CRLF CRLF (we don't send trailers)
    is($final, "0\r\n\r\n", 'final chunk correct');
    is(length($final), 5, 'final chunk is 5 bytes');
};

# ============================================================
# Test 14-15: Integration with Hypersonic
# ============================================================
subtest 'Hypersonic compiles streaming routes' => sub {
    plan tests => 3;
    
    my $app = Hypersonic->new();
    
    $app->get('/events' => sub {
        my ($req, $stream) = @_;
        $stream->content_type('text/event-stream');
        $stream->write("data: hello\n\n");
        $stream->end();
    }, { streaming => 1 });
    
    $app->get('/' => sub { return 'hello' });
    
    eval { $app->compile() };
    ok(!$@, 'compile succeeds') or diag($@);
    
    ok($app->{route_analysis}{needs_streaming}, 'streaming detected');
    ok($app->{compiled}, 'app is compiled');
};

subtest 'Generated code includes chunked functions' => sub {
    plan tests => 3;
    
    require XS::JIT::Builder;
    my $builder = XS::JIT::Builder->new;
    
    # Generate all HTTP1 chunked code
    Hypersonic::Protocol::HTTP1->gen_chunked_start($builder);
    Hypersonic::Protocol::HTTP1->gen_chunked_write($builder);
    Hypersonic::Protocol::HTTP1->gen_chunked_end($builder);
    
    my $code = $builder->code;
    
    like($code, qr/send_chunked_headers/, 'has chunked_headers');
    like($code, qr/send_chunk\(/, 'has send_chunk');
    like($code, qr/send_chunk_end/, 'has send_chunk_end');
};

done_testing();



( run in 0.671 second using v1.01-cache-2.11-cpan-140bd7fdf52 )