diff --git a/t/integration.t b/t/integration.t index 2ffbb97..9fb67c9 100644 --- a/t/integration.t +++ b/t/integration.t @@ -6,6 +6,18 @@ my $t; eval { $t = Test::Mojo->new('Urupam::App'); 1 } or plan skip_all => "Test server not available: $@"; +sub wait_promise { + my ($promise) = @_; + my ( $value, $error ); + $promise->then( sub { $value = shift } ) + ->catch( sub { $error = shift } ) + ->wait; + return ( $value, $error ); +} + +my ( $pong, $ping_err ) = wait_promise( $t->app->db->ping ); +plan skip_all => "Redis not available: $ping_err" if $ping_err; + my $CODE_PATTERN = qr/^[0-9a-zA-Z\-_]+$/; my $CODE_LENGTH = 12; my $MAX_URL_LENGTH = 2048; @@ -18,6 +30,12 @@ sub validate_short_code_format { && $code =~ $CODE_PATTERN; } +sub expected_normalized_url { + my ($url) = @_; + return $url if defined $url && $url =~ m{^https?://}i; + return defined $url ? "http://$url" : undef; +} + sub post_shorten { my ($url) = @_; my $tx = $t->post_ok( '/api/v1/urls' => json => { url => $url } ); @@ -52,6 +70,8 @@ sub validate_shorten_response { "$label: short code valid" ); is( length( $json->{short_code} ), $CODE_LENGTH, "$label: short code length correct" ); + is( $json->{original_url}, $url, "$label: original URL matches" ) + if defined $url; like( $json->{short_url}, qr/^https?:\/\/[^\/]+\/$json->{short_code}$/, @@ -81,39 +101,20 @@ sub validate_get_response { return 1; } -sub skip_if_error { - my ( $res, $context ) = @_; - if ( $res->{code} != 200 && $res->{code} != 400 && $res->{code} != 404 ) { - diag( "$context skipped: " . $res->{error} ); - return 1; - } - return 0; -} - subtest 'POST /api/v1/urls - Real validator success cases' => sub { for my $url ( 'https://www.example.com', 'http://www.perl.org' ) { my $res = post_shorten($url); - if ( $res->{code} == 200 ) { - validate_shorten_response( $res, $url, "URL: $url" ); - } - else { - diag( "Test skipped for $url: " . $res->{error} ); - } + is( $res->{code}, 200, "URL accepted: $url" ); + validate_shorten_response( $res, $url, "URL: $url" ); } }; subtest 'POST /api/v1/urls - Real validator URL normalization' => sub { for my $input ( 'www.example.com', 'example.com' ) { my $res = post_shorten($input); - if ( $res->{code} == 200 ) { - like( $res->{json}->{original_url}, - qr/^https?:\/\//, "URL normalized: $input" ); - ok( validate_short_code_format( $res->{json}->{short_code} ), - "Code generated for: $input" ); - } - else { - diag( "Normalization test skipped for $input: " . $res->{error} ); - } + is( $res->{code}, 200, "URL normalized: $input" ); + my $expected = expected_normalized_url($input); + validate_shorten_response( $res, $expected, "URL normalized: $input" ); } }; @@ -139,21 +140,11 @@ subtest 'POST /api/v1/urls - Real validator network errors (async)' => sub { my $url ( 'http://nonexistent-domain-12345.invalid', 'http://192.0.2.1' ) { my $res = post_shorten($url); - ok( - $res->{code} == 200 || $res->{code} == 400, - "Network URL accepted or rejected by format: $url" - ); + is( $res->{code}, 200, "Network URL accepted asynchronously: $url" ); + validate_shorten_response( $res, $url, "URL: $url" ); } }; -subtest 'POST /api/v1/urls - Real validator SSL certificate validation' => sub { - my $res = post_shorten('https://www.example.com'); - ok( - $res->{code} == 200 || $res->{code} == 400, - 'SSL validation runs async for HTTPS URL' - ); -}; - subtest 'POST /api/v1/urls - Real validator invalid URL format' => sub { for my $case ( { url => 'ftp://example.com', error => 'Invalid URL format' }, @@ -165,10 +156,13 @@ subtest 'POST /api/v1/urls - Real validator invalid URL format' => sub { is( $res->{error}, $case->{error}, "Correct error for: $case->{url}" ); } +}; + +subtest 'POST /api/v1/urls - Real validator bare hostname' => sub { my $res = post_shorten('not-a-url'); is( $res->{code}, 200, 'Bare hostname accepted: not-a-url' ); - like( $res->{json}->{original_url}, - qr{^http://not-a-url$}, 'Bare hostname normalized with scheme' ); + validate_shorten_response( $res, 'http://not-a-url', + 'Bare hostname normalized' ); }; subtest 'POST /api/v1/urls - Real validator URL length validation' => sub { @@ -191,15 +185,8 @@ subtest 'POST /api/v1/urls - Real validator URL edge cases' => sub { ) { my $res = post_shorten($url); - if ( $res->{code} == 200 ) { - ok( validate_short_code_format( $res->{json}->{short_code} ), - "Edge case handled: $url" ); - like( $res->{json}->{original_url}, - qr/^https?:\/\//, "URL format preserved: $url" ); - } - else { - diag( "Edge case test skipped for $url: " . $res->{error} ); - } + is( $res->{code}, 200, "Edge case handled: $url" ); + validate_shorten_response( $res, $url, "Edge case handled: $url" ); } }; @@ -207,56 +194,40 @@ subtest 'POST /api/v1/urls - Real database persistence and retrieval' => sub { my $url = 'https://www.example.com'; my $res1 = post_shorten($url); - if ( $res1->{code} == 200 ) { - my $code = $res1->{json}->{short_code}; - ok( validate_short_code_format($code), 'Code generated and stored' ); + is( $res1->{code}, 200, 'Database write succeeded' ); + my $code = $res1->{json}->{short_code}; + ok( validate_short_code_format($code), 'Code generated and stored' ); - my $res2 = get_url($code); - if ( $res2->{code} == 200 ) { - validate_get_response( $res2, $url, $code, 'Database retrieval' ); - pass('Database persistence verified'); - } - else { - diag( "Database retrieval failed: " . $res2->{error} ); - } - } - else { - diag( "Database persistence test skipped: " . $res1->{error} ); - } + my $res2 = get_url($code); + is( $res2->{code}, 200, 'Database read succeeded' ); + validate_get_response( $res2, $url, $code, 'Database retrieval' ); + pass('Database persistence verified'); }; subtest 'POST /api/v1/urls - Real database duplicate URL handling' => sub { my $url = 'https://www.example.com'; my $res1 = post_shorten($url); - if ( $res1->{code} == 200 ) { - my $code1 = $res1->{json}->{short_code}; - ok( validate_short_code_format($code1), 'First code generated' ); + is( $res1->{code}, 200, 'First create succeeded' ); + my $code1 = $res1->{json}->{short_code}; + ok( validate_short_code_format($code1), 'First code generated' ); - my $res2 = post_shorten($url); - if ( $res2->{code} == 200 ) { - my $code2 = $res2->{json}->{short_code}; - ok( validate_short_code_format($code2), 'Second code generated' ); - ok( $code1 ne $code2, 'Duplicate URLs generate different codes' ); + my $res2 = post_shorten($url); + is( $res2->{code}, 200, 'Second create succeeded' ); + my $code2 = $res2->{json}->{short_code}; + ok( validate_short_code_format($code2), 'Second code generated' ); + ok( $code1 ne $code2, 'Duplicate URLs generate different codes' ); - my $get1 = get_url($code1); - my $get2 = get_url($code2); + my $get1 = get_url($code1); + my $get2 = get_url($code2); - if ( $get1->{code} == 200 && $get2->{code} == 200 ) { - is( $get1->{json}->{original_url}, - $url, 'First code retrieves original URL' ); - is( $get2->{json}->{original_url}, - $url, 'Second code retrieves original URL' ); - pass('Both codes persist and retrieve same URL'); - } - } - else { - diag( "Duplicate URL test skipped: " . $res2->{error} ); - } - } - else { - diag( "Duplicate URL test skipped: " . $res1->{error} ); - } + is( $get1->{code}, 200, 'First code retrieves' ); + is( $get2->{code}, 200, 'Second code retrieves' ); + is( $get1->{json}->{original_url}, + $url, 'First code retrieves original URL' ); + is( $get2->{json}->{original_url}, + $url, 'Second code retrieves original URL' ); + pass('Both codes persist and retrieve same URL'); }; subtest 'GET /api/v1/urls/:short_code - Real database error cases' => sub { @@ -280,43 +251,4 @@ subtest 'GET /api/v1/urls/:short_code - Real database error cases' => sub { ); }; -subtest 'End-to-end: Full flow with real components' => sub { - for my $url ( 'https://www.example.com', 'http://www.perl.org' ) { - my $res1 = post_shorten($url); - - if ( $res1->{code} == 200 ) { - my $code = $res1->{json}->{short_code}; - ok( validate_short_code_format($code), "Code generated for: $url" ); - - my $res2 = get_url($code); - if ( $res2->{code} == 200 ) { - validate_get_response( $res2, $url, $code, "End-to-end: $url" ); - } - else { - diag( "End-to-end GET failed for $url: " . $res2->{error} ); - } - } - else { - diag( "End-to-end POST failed for $url: " . $res1->{error} ); - } - } -}; - -subtest 'Real database connection test' => sub { - my $res = post_shorten('https://www.example.com'); - - if ( $res->{code} == 200 ) { - pass('Database connection successful (Redis accessible)'); - my $get_res = get_url( $res->{json}->{short_code} ); - pass('Database read operation successful') if $get_res->{code} == 200; - } - elsif ( $res->{code} == 400 && $res->{error} =~ /Database error/i ) { - diag( "Database connection test: Redis may not be available - " - . $res->{error} ); - } - else { - diag( "Database connection test skipped: " . $res->{error} ); - } -}; - done_testing();