test: update test with new route architecture

This commit is contained in:
2025-12-24 18:46:09 +01:00
parent 7c967b179e
commit 795d3d8e9f

View File

@@ -101,10 +101,10 @@ sub reset_mocks {
); );
} }
subtest 'POST /api/shorten - invalid JSON' => sub { subtest 'POST /api/v1/urls - invalid JSON' => sub {
reset_mocks(); reset_mocks();
$t->post_ok( $t->post_ok(
'/api/shorten' => { 'Content-Type' => 'application/json' } => '/api/v1/urls' => { 'Content-Type' => 'application/json' } =>
'invalid json' ) 'invalid json' )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Invalid JSON format' ); ->json_is( '/error' => 'Invalid JSON format' );
@@ -112,49 +112,49 @@ subtest 'POST /api/shorten - invalid JSON' => sub {
ok( !$url_service_called, 'URL service not called for invalid JSON' ); ok( !$url_service_called, 'URL service not called for invalid JSON' );
}; };
subtest 'POST /api/shorten - invalid JSON types' => sub { subtest 'POST /api/v1/urls - invalid JSON types' => sub {
reset_mocks(); reset_mocks();
$t->post_ok( '/api/shorten' => json => [] ) $t->post_ok( '/api/v1/urls' => json => [] )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Invalid JSON format' ); ->json_is( '/error' => 'Invalid JSON format' );
$t->post_ok( '/api/shorten' => json => 'not a hash' ) $t->post_ok( '/api/v1/urls' => json => 'not a hash' )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Invalid JSON format' ); ->json_is( '/error' => 'Invalid JSON format' );
ok( !$validator_called, 'Validator not called for invalid JSON types' ); ok( !$validator_called, 'Validator not called for invalid JSON types' );
ok( !$url_service_called, 'URL service not called for invalid JSON types' ); ok( !$url_service_called, 'URL service not called for invalid JSON types' );
}; };
subtest 'POST /api/shorten - missing URL' => sub { subtest 'POST /api/v1/urls - missing URL' => sub {
reset_mocks(); reset_mocks();
$t->post_ok( '/api/shorten' => json => {} ) $t->post_ok( '/api/v1/urls' => json => {} )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'URL is required' ); ->json_is( '/error' => 'URL is required' );
ok( !$validator_called, 'Validator not called for missing URL' ); ok( !$validator_called, 'Validator not called for missing URL' );
ok( !$url_service_called, 'URL service not called for missing URL' ); ok( !$url_service_called, 'URL service not called for missing URL' );
}; };
subtest 'POST /api/shorten - whitespace URL' => sub { subtest 'POST /api/v1/urls - whitespace URL' => sub {
reset_mocks(); reset_mocks();
$t->post_ok( '/api/shorten' => json => { url => ' ' } ) $t->post_ok( '/api/v1/urls' => json => { url => ' ' } )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'URL is required' ); ->json_is( '/error' => 'URL is required' );
ok( !$validator_called, 'Validator not called for whitespace URL' ); ok( !$validator_called, 'Validator not called for whitespace URL' );
ok( !$url_service_called, 'URL service not called for whitespace URL' ); ok( !$url_service_called, 'URL service not called for whitespace URL' );
}; };
subtest 'POST /api/shorten - whitespace-only with tabs/newlines' => sub { subtest 'POST /api/v1/urls - whitespace-only with tabs/newlines' => sub {
reset_mocks(); reset_mocks();
$t->post_ok( '/api/shorten' => json => { url => "\n\t " } ) $t->post_ok( '/api/v1/urls' => json => { url => "\n\t " } )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'URL is required' ); ->json_is( '/error' => 'URL is required' );
ok( !$validator_called, 'Validator not called for tab/newline URL' ); ok( !$validator_called, 'Validator not called for tab/newline URL' );
ok( !$url_service_called, 'URL service not called for tab/newline URL' ); ok( !$url_service_called, 'URL service not called for tab/newline URL' );
}; };
subtest 'POST /api/shorten - success path' => sub { subtest 'POST /api/v1/urls - success path' => sub {
reset_mocks(); reset_mocks();
my $tx = $t->post_ok( my $tx = $t->post_ok(
'/api/shorten' => json => { url => ' https://example.com/path ' } ); '/api/v1/urls' => json => { url => ' https://example.com/path ' } );
my $base_url = $t->ua->server->url->clone->path('')->to_abs; my $base_url = $t->ua->server->url->clone->path('')->to_abs;
$tx->status_is(200)->json_is( '/success' => 1 ); $tx->status_is(200)->json_is( '/success' => 1 );
$tx->json_is( '/short_code' => 'AbCdEf123456' ); $tx->json_is( '/short_code' => 'AbCdEf123456' );
@@ -167,7 +167,7 @@ subtest 'POST /api/shorten - success path' => sub {
is( $created_url, 'https://example.com/path', 'URL passed to URL service' ); is( $created_url, 'https://example.com/path', 'URL passed to URL service' );
}; };
subtest 'POST /api/shorten - validator normalization' => sub { subtest 'POST /api/v1/urls - validator normalization' => sub {
reset_mocks(); reset_mocks();
$validator->validate_url_cb( $validator->validate_url_cb(
sub { sub {
@@ -175,14 +175,14 @@ subtest 'POST /api/shorten - validator normalization' => sub {
return Mojo::Promise->resolve('http://normalized.test/path'); return Mojo::Promise->resolve('http://normalized.test/path');
} }
); );
$t->post_ok( '/api/shorten' => json => { url => 'normalized.test/path' } ) $t->post_ok( '/api/v1/urls' => json => { url => 'normalized.test/path' } )
->status_is(200) ->status_is(200)
->json_is( '/short_code' => 'AbCdEf123456' ); ->json_is( '/short_code' => 'AbCdEf123456' );
is( $created_url, 'http://normalized.test/path', is( $created_url, 'http://normalized.test/path',
'URL service receives normalized URL' ); 'URL service receives normalized URL' );
}; };
subtest 'POST /api/shorten - validator error' => sub { subtest 'POST /api/v1/urls - validator error' => sub {
reset_mocks(); reset_mocks();
$validator->validate_url_cb( $validator->validate_url_cb(
sub { sub {
@@ -190,14 +190,14 @@ subtest 'POST /api/shorten - validator error' => sub {
return Mojo::Promise->reject('SSL certificate error: bad cert'); return Mojo::Promise->reject('SSL certificate error: bad cert');
} }
); );
$t->post_ok( '/api/shorten' => json => { url => 'https://example.com' } ) $t->post_ok( '/api/v1/urls' => json => { url => 'https://example.com' } )
->status_is(422) ->status_is(422)
->json_is( '/error' => 'SSL certificate error: bad cert' ); ->json_is( '/error' => 'SSL certificate error: bad cert' );
ok( $validator_called, 'Validator called' ); ok( $validator_called, 'Validator called' );
ok( !$url_service_called, 'URL service not called after validator error' ); ok( !$url_service_called, 'URL service not called after validator error' );
}; };
subtest 'POST /api/shorten - error sanitization' => sub { subtest 'POST /api/v1/urls - error sanitization' => sub {
reset_mocks(); reset_mocks();
$url_service->create_short_url_cb( $url_service->create_short_url_cb(
sub { sub {
@@ -205,14 +205,14 @@ subtest 'POST /api/shorten - error sanitization' => sub {
return Mojo::Promise->reject("Database error: <bad>\n\t!!"); return Mojo::Promise->reject("Database error: <bad>\n\t!!");
} }
); );
$t->post_ok( '/api/shorten' => json => { url => 'https://example.com' } ) $t->post_ok( '/api/v1/urls' => json => { url => 'https://example.com' } )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Database error: bad' ); ->json_is( '/error' => 'Database error: bad' );
ok( $validator_called, 'Validator called' ); ok( $validator_called, 'Validator called' );
ok( $url_service_called, 'URL service called' ); ok( $url_service_called, 'URL service called' );
}; };
subtest 'POST /api/shorten - error sanitization truncation' => sub { subtest 'POST /api/v1/urls - error sanitization truncation' => sub {
reset_mocks(); reset_mocks();
my $long_error = 'Database error: ' . ( 'a' x 210 ); my $long_error = 'Database error: ' . ( 'a' x 210 );
my $expected = substr( $long_error, 0, 200 ) . '...'; my $expected = substr( $long_error, 0, 200 ) . '...';
@@ -222,14 +222,14 @@ subtest 'POST /api/shorten - error sanitization truncation' => sub {
return Mojo::Promise->reject($long_error); return Mojo::Promise->reject($long_error);
} }
); );
$t->post_ok( '/api/shorten' => json => { url => 'https://example.com' } ) $t->post_ok( '/api/v1/urls' => json => { url => 'https://example.com' } )
->status_is(400) ->status_is(400)
->json_is( '/error' => $expected ); ->json_is( '/error' => $expected );
ok( $validator_called, 'Validator called' ); ok( $validator_called, 'Validator called' );
ok( $url_service_called, 'URL service called' ); ok( $url_service_called, 'URL service called' );
}; };
subtest 'POST /api/shorten - URL service error' => sub { subtest 'POST /api/v1/urls - URL service error' => sub {
reset_mocks(); reset_mocks();
$url_service->create_short_url_cb( $url_service->create_short_url_cb(
sub { sub {
@@ -237,14 +237,14 @@ subtest 'POST /api/shorten - URL service error' => sub {
return Mojo::Promise->reject('Database error: connection failed'); return Mojo::Promise->reject('Database error: connection failed');
} }
); );
$t->post_ok( '/api/shorten' => json => { url => 'https://example.com' } ) $t->post_ok( '/api/v1/urls' => json => { url => 'https://example.com' } )
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Database error: connection failed' ); ->json_is( '/error' => 'Database error: connection failed' );
ok( $validator_called, 'Validator called' ); ok( $validator_called, 'Validator called' );
ok( $url_service_called, 'URL service called' ); ok( $url_service_called, 'URL service called' );
}; };
subtest 'POST /api/shorten - status mapping for network error' => sub { subtest 'POST /api/v1/urls - status mapping for network error' => sub {
reset_mocks(); reset_mocks();
$validator->validate_url_cb( $validator->validate_url_cb(
sub { sub {
@@ -253,39 +253,35 @@ subtest 'POST /api/shorten - status mapping for network error' => sub {
'Cannot reach URL: Connection refused'); 'Cannot reach URL: Connection refused');
} }
); );
$t->post_ok( '/api/shorten' => json => { url => 'https://example.com' } ) $t->post_ok( '/api/v1/urls' => json => { url => 'https://example.com' } )
->status_is(422) ->status_is(422)
->json_is( '/error' => 'Cannot reach URL: Connection refused' ); ->json_is( '/error' => 'Cannot reach URL: Connection refused' );
ok( $validator_called, 'Validator called' ); ok( $validator_called, 'Validator called' );
ok( !$url_service_called, 'URL service not called after validator error' ); ok( !$url_service_called, 'URL service not called after validator error' );
}; };
subtest 'GET /api/url - invalid short code format' => sub { subtest 'GET /api/v1/urls/:short_code - invalid short code format' => sub {
reset_mocks(); reset_mocks();
$validator->validate_short_code_cb( sub { 0 } ); $validator->validate_short_code_cb( sub { 0 } );
$t->get_ok('/api/url?short_code=bad@code') $t->get_ok('/api/v1/urls/bad@code')
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Invalid short code format' ); ->json_is( '/error' => 'Invalid short code format' );
ok( !$url_service_called, 'URL service not called for invalid short code' ); ok( !$url_service_called, 'URL service not called for invalid short code' );
}; };
subtest 'GET /api/url - missing short_code param' => sub { subtest 'GET /api/v1/urls - missing short_code param' => sub {
reset_mocks(); reset_mocks();
$t->get_ok('/api/url') $t->get_ok('/api/v1/urls')->status_is(404);
->status_is(400)
->json_is( '/error' => 'Invalid short code format' );
ok( !$url_service_called, 'URL service not called for missing short_code' ); ok( !$url_service_called, 'URL service not called for missing short_code' );
}; };
subtest 'GET /api/url - empty short_code param' => sub { subtest 'GET /api/v1/urls/:short_code - empty short_code param' => sub {
reset_mocks(); reset_mocks();
$t->get_ok('/api/url?short_code=') $t->get_ok('/api/v1/urls/')->status_is(404);
->status_is(400)
->json_is( '/error' => 'Invalid short code format' );
ok( !$url_service_called, 'URL service not called for empty short_code' ); ok( !$url_service_called, 'URL service not called for empty short_code' );
}; };
subtest 'GET /api/url - not found' => sub { subtest 'GET /api/v1/urls/:short_code - not found' => sub {
reset_mocks(); reset_mocks();
$url_service->get_original_url_cb( $url_service->get_original_url_cb(
sub { sub {
@@ -293,16 +289,16 @@ subtest 'GET /api/url - not found' => sub {
return Mojo::Promise->resolve(undef); return Mojo::Promise->resolve(undef);
} }
); );
$t->get_ok('/api/url?short_code=AbCdEf123456') $t->get_ok('/api/v1/urls/AbCdEf123456')
->status_is(404) ->status_is(404)
->json_is( '/error' => 'Short code not found' ); ->json_is( '/error' => 'Short code not found' );
ok( $url_service_called, 'URL service called' ); ok( $url_service_called, 'URL service called' );
}; };
subtest 'GET /api/url - success path' => sub { subtest 'GET /api/v1/urls/:short_code - success path' => sub {
reset_mocks(); reset_mocks();
my $base_url = $t->ua->server->url->clone->path('')->to_abs; my $base_url = $t->ua->server->url->clone->path('')->to_abs;
$t->get_ok('/api/url?short_code=AbCdEf123456') $t->get_ok('/api/v1/urls/AbCdEf123456')
->status_is(200) ->status_is(200)
->json_is( '/success' => 1 ) ->json_is( '/success' => 1 )
->json_is( '/short_code' => 'AbCdEf123456' ) ->json_is( '/short_code' => 'AbCdEf123456' )
@@ -313,7 +309,7 @@ subtest 'GET /api/url - success path' => sub {
is( $get_code, 'AbCdEf123456', 'Short code passed to URL service' ); is( $get_code, 'AbCdEf123456', 'Short code passed to URL service' );
}; };
subtest 'GET /api/url - URL service error' => sub { subtest 'GET /api/v1/urls/:short_code - URL service error' => sub {
reset_mocks(); reset_mocks();
$url_service->get_original_url_cb( $url_service->get_original_url_cb(
sub { sub {
@@ -321,13 +317,13 @@ subtest 'GET /api/url - URL service error' => sub {
return Mojo::Promise->reject('DNS resolution failed: timeout'); return Mojo::Promise->reject('DNS resolution failed: timeout');
} }
); );
$t->get_ok('/api/url?short_code=AbCdEf123456') $t->get_ok('/api/v1/urls/AbCdEf123456')
->status_is(422) ->status_is(422)
->json_is( '/error' => 'DNS resolution failed: timeout' ); ->json_is( '/error' => 'DNS resolution failed: timeout' );
ok( $url_service_called, 'URL service called' ); ok( $url_service_called, 'URL service called' );
}; };
subtest 'GET /api/url - non-422 error mapping' => sub { subtest 'GET /api/v1/urls/:short_code - non-422 error mapping' => sub {
reset_mocks(); reset_mocks();
$url_service->get_original_url_cb( $url_service->get_original_url_cb(
sub { sub {
@@ -335,7 +331,7 @@ subtest 'GET /api/url - non-422 error mapping' => sub {
return Mojo::Promise->reject('Database error: connection failed'); return Mojo::Promise->reject('Database error: connection failed');
} }
); );
$t->get_ok('/api/url?short_code=AbCdEf123456') $t->get_ok('/api/v1/urls/AbCdEf123456')
->status_is(400) ->status_is(400)
->json_is( '/error' => 'Database error: connection failed' ); ->json_is( '/error' => 'Database error: connection failed' );
ok( $url_service_called, 'URL service called' ); ok( $url_service_called, 'URL service called' );