@@ -1234,3 +1234,310 @@ async fn do_fetch(
12341234 body : ResponseBody :: Stream ( rx) ,
12351235 } )
12361236}
1237+
1238+ #[ cfg( test) ]
1239+ mod tests {
1240+ use super :: * ;
1241+
1242+ // ============================================================================
1243+ // generate_request_id tests
1244+ // ============================================================================
1245+
1246+ #[ test]
1247+ fn test_generate_request_id_format ( ) {
1248+ let id = generate_request_id ( "test" ) ;
1249+ // Format: prefix_hex (e.g., test_00000000000188fc94e525937a8)
1250+ // Total length is 32 chars
1251+ assert ! (
1252+ id. starts_with( "test_" ) ,
1253+ "ID should start with prefix_: {}" ,
1254+ id
1255+ ) ;
1256+ assert_eq ! ( id. len( ) , 32 , "ID should be 32 chars: {}" , id) ;
1257+
1258+ // Split at underscore
1259+ let parts: Vec < & str > = id. splitn ( 2 , '_' ) . collect ( ) ;
1260+ assert_eq ! ( parts. len( ) , 2 , "ID should have 2 parts: {}" , id) ;
1261+ assert_eq ! ( parts[ 0 ] , "test" ) ;
1262+ // Hex part should be 27 chars (31 - len("test"))
1263+ assert_eq ! (
1264+ parts[ 1 ] . len( ) ,
1265+ 27 ,
1266+ "Hex part should be 27 chars: {}" ,
1267+ parts[ 1 ]
1268+ ) ;
1269+ // Should be valid hex
1270+ assert ! (
1271+ u128 :: from_str_radix( parts[ 1 ] , 16 ) . is_ok( ) ,
1272+ "Hex part should be valid hex: {}" ,
1273+ parts[ 1 ]
1274+ ) ;
1275+ }
1276+
1277+ #[ test]
1278+ fn test_generate_request_id_unique ( ) {
1279+ let id1 = generate_request_id ( "test" ) ;
1280+ let id2 = generate_request_id ( "test" ) ;
1281+ // IDs are based on nanoseconds, so they should be different
1282+ // (unless generated in same nanosecond, which is very unlikely)
1283+ assert_ne ! ( id1, id2, "IDs should be unique" ) ;
1284+ }
1285+
1286+ // ============================================================================
1287+ // try_internal_worker_route tests
1288+ //
1289+ // Note: These tests require WORKER_DOMAINS env var to be set.
1290+ // In production, this is typically "workers.rocks,workers.dev.localhost"
1291+ // Tests that require routing will skip if WORKER_DOMAINS is empty.
1292+ // ============================================================================
1293+
1294+ fn has_worker_domains ( ) -> bool {
1295+ !WORKER_DOMAINS . is_empty ( )
1296+ }
1297+
1298+ #[ test]
1299+ fn test_internal_route_workers_rocks ( ) {
1300+ if !has_worker_domains ( ) {
1301+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1302+ return ;
1303+ }
1304+
1305+ // Use first configured domain
1306+ let domain = & WORKER_DOMAINS [ 0 ] ;
1307+ let url = format ! ( "https://my-worker.{}/api/test?foo=bar" , domain) ;
1308+
1309+ let request = HttpRequest {
1310+ method : HttpMethod :: Get ,
1311+ url,
1312+ headers : HashMap :: new ( ) ,
1313+ body : RequestBody :: None ,
1314+ } ;
1315+
1316+ let routed = try_internal_worker_route ( & request) ;
1317+ assert ! ( routed. is_some( ) , "Should route configured domain URLs" ) ;
1318+
1319+ let routed = routed. unwrap ( ) ;
1320+ assert_eq ! ( routed. url, "http://127.0.0.1:8080/api/test?foo=bar" ) ;
1321+ assert_eq ! (
1322+ routed. headers. get( "x-worker-name" ) ,
1323+ Some ( & "my-worker" . to_string( ) )
1324+ ) ;
1325+ assert ! ( routed. headers. contains_key( "x-request-id" ) ) ;
1326+ }
1327+
1328+ #[ test]
1329+ fn test_internal_route_preserves_method ( ) {
1330+ if !has_worker_domains ( ) {
1331+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1332+ return ;
1333+ }
1334+
1335+ let domain = & WORKER_DOMAINS [ 0 ] ;
1336+ let request = HttpRequest {
1337+ method : HttpMethod :: Post ,
1338+ url : format ! ( "https://api.{}/data" , domain) ,
1339+ headers : HashMap :: new ( ) ,
1340+ body : RequestBody :: None ,
1341+ } ;
1342+
1343+ let routed = try_internal_worker_route ( & request) . unwrap ( ) ;
1344+ assert_eq ! ( routed. method, HttpMethod :: Post ) ;
1345+ }
1346+
1347+ #[ test]
1348+ fn test_internal_route_preserves_body ( ) {
1349+ if !has_worker_domains ( ) {
1350+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1351+ return ;
1352+ }
1353+
1354+ let domain = & WORKER_DOMAINS [ 0 ] ;
1355+ let body_data = b"test body data" . to_vec ( ) ;
1356+ let request = HttpRequest {
1357+ method : HttpMethod :: Post ,
1358+ url : format ! ( "https://api.{}/data" , domain) ,
1359+ headers : HashMap :: new ( ) ,
1360+ body : RequestBody :: Bytes ( body_data. clone ( ) . into ( ) ) ,
1361+ } ;
1362+
1363+ let routed = try_internal_worker_route ( & request) . unwrap ( ) ;
1364+ match routed. body {
1365+ RequestBody :: Bytes ( b) => assert_eq ! ( b. as_ref( ) , body_data. as_slice( ) ) ,
1366+ _ => panic ! ( "Expected Bytes body" ) ,
1367+ }
1368+ }
1369+
1370+ #[ test]
1371+ fn test_internal_route_no_match_external ( ) {
1372+ // This test always works - external URLs should never match
1373+ let request = HttpRequest {
1374+ method : HttpMethod :: Get ,
1375+ url : "https://example.com/api" . to_string ( ) ,
1376+ headers : HashMap :: new ( ) ,
1377+ body : RequestBody :: None ,
1378+ } ;
1379+
1380+ let routed = try_internal_worker_route ( & request) ;
1381+ assert ! ( routed. is_none( ) , "Should not route external URLs" ) ;
1382+ }
1383+
1384+ #[ test]
1385+ fn test_internal_route_no_match_partial ( ) {
1386+ if WORKER_DOMAINS . is_empty ( ) {
1387+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1388+ return ;
1389+ }
1390+
1391+ // Should not match bare domain without subdomain
1392+ let domain = & WORKER_DOMAINS [ 0 ] ;
1393+ let request = HttpRequest {
1394+ method : HttpMethod :: Get ,
1395+ url : format ! ( "https://{}/api" , domain) ,
1396+ headers : HashMap :: new ( ) ,
1397+ body : RequestBody :: None ,
1398+ } ;
1399+
1400+ let routed = try_internal_worker_route ( & request) ;
1401+ assert ! ( routed. is_none( ) , "Should not route bare domain" ) ;
1402+ }
1403+
1404+ #[ test]
1405+ fn test_internal_route_stream_body_not_supported ( ) {
1406+ if !has_worker_domains ( ) {
1407+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1408+ return ;
1409+ }
1410+
1411+ let domain = & WORKER_DOMAINS [ 0 ] ;
1412+ let ( _tx, rx) = tokio:: sync:: mpsc:: channel ( 1 ) ;
1413+ let request = HttpRequest {
1414+ method : HttpMethod :: Post ,
1415+ url : format ! ( "https://api.{}/data" , domain) ,
1416+ headers : HashMap :: new ( ) ,
1417+ body : RequestBody :: Stream ( rx) ,
1418+ } ;
1419+
1420+ let routed = try_internal_worker_route ( & request) ;
1421+ assert ! ( routed. is_none( ) , "Should not route streaming bodies" ) ;
1422+ }
1423+
1424+ #[ test]
1425+ fn test_internal_route_path_only ( ) {
1426+ if !has_worker_domains ( ) {
1427+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1428+ return ;
1429+ }
1430+
1431+ let domain = & WORKER_DOMAINS [ 0 ] ;
1432+ let request = HttpRequest {
1433+ method : HttpMethod :: Get ,
1434+ url : format ! ( "https://worker.{}/" , domain) ,
1435+ headers : HashMap :: new ( ) ,
1436+ body : RequestBody :: None ,
1437+ } ;
1438+
1439+ let routed = try_internal_worker_route ( & request) . unwrap ( ) ;
1440+ assert_eq ! ( routed. url, "http://127.0.0.1:8080/" ) ;
1441+ }
1442+
1443+ #[ test]
1444+ fn test_internal_route_complex_path ( ) {
1445+ if !has_worker_domains ( ) {
1446+ eprintln ! ( "Skipping: WORKER_DOMAINS not set" ) ;
1447+ return ;
1448+ }
1449+
1450+ let domain = & WORKER_DOMAINS [ 0 ] ;
1451+ let request = HttpRequest {
1452+ method : HttpMethod :: Get ,
1453+ url : format ! (
1454+ "https://app.{}/api/v1/users/123?include=profile&format=json" ,
1455+ domain
1456+ ) ,
1457+ headers : HashMap :: new ( ) ,
1458+ body : RequestBody :: None ,
1459+ } ;
1460+
1461+ let routed = try_internal_worker_route ( & request) . unwrap ( ) ;
1462+ assert_eq ! (
1463+ routed. url,
1464+ "http://127.0.0.1:8080/api/v1/users/123?include=profile&format=json"
1465+ ) ;
1466+ assert_eq ! (
1467+ routed. headers. get( "x-worker-name" ) ,
1468+ Some ( & "app" . to_string( ) )
1469+ ) ;
1470+ }
1471+
1472+ // ============================================================================
1473+ // wrap_query_as_json tests (database feature only)
1474+ // ============================================================================
1475+
1476+ #[ cfg( feature = "database" ) ]
1477+ mod database_tests {
1478+ use super :: super :: * ;
1479+
1480+ #[ test]
1481+ fn test_wrap_select_query ( ) {
1482+ let sql = "SELECT * FROM users WHERE id = $1" ;
1483+ let wrapped = wrap_query_as_json ( sql, QueryMode :: Select ) ;
1484+ assert ! ( wrapped. contains( "jsonb_agg" ) ) ;
1485+ assert ! ( wrapped. contains( "row_to_json" ) ) ;
1486+ assert ! ( wrapped. contains( sql) ) ;
1487+ }
1488+
1489+ #[ test]
1490+ fn test_wrap_select_trims_semicolon ( ) {
1491+ let sql = "SELECT * FROM users;" ;
1492+ let wrapped = wrap_query_as_json ( sql, QueryMode :: Select ) ;
1493+ assert ! ( !wrapped. contains( ";;" ) , "Should not have double semicolons" ) ;
1494+ }
1495+
1496+ #[ test]
1497+ fn test_wrap_returning_mutation ( ) {
1498+ let sql = "INSERT INTO users (name) VALUES ($1) RETURNING *" ;
1499+ let wrapped = wrap_query_as_json ( sql, QueryMode :: ReturningMutation ) ;
1500+ assert ! ( wrapped. starts_with( "WITH t AS" ) ) ;
1501+ assert ! ( wrapped. contains( sql. trim_end_matches( ';' ) ) ) ;
1502+ }
1503+
1504+ #[ test]
1505+ #[ should_panic( expected = "Mutation queries should not be wrapped" ) ]
1506+ fn test_wrap_mutation_panics ( ) {
1507+ let sql = "DELETE FROM users WHERE id = $1" ;
1508+ wrap_query_as_json ( sql, QueryMode :: Mutation ) ;
1509+ }
1510+ }
1511+
1512+ // ============================================================================
1513+ // RunnerOperations construction tests
1514+ // ============================================================================
1515+
1516+ #[ test]
1517+ fn test_runner_operations_builder ( ) {
1518+ let ops = RunnerOperations :: new ( ) ;
1519+ assert ! ( ops. bindings. assets. is_empty( ) ) ;
1520+ assert ! ( ops. bindings. storage. is_empty( ) ) ;
1521+ assert ! ( ops. bindings. kv. is_empty( ) ) ;
1522+ }
1523+
1524+ #[ test]
1525+ fn test_runner_operations_with_user ( ) {
1526+ let ops = RunnerOperations :: new ( ) . with_user_id ( "user-123" . to_string ( ) ) ;
1527+ assert_eq ! ( ops. user_id, Some ( "user-123" . to_string( ) ) ) ;
1528+ }
1529+
1530+ #[ test]
1531+ fn test_runner_operations_with_worker ( ) {
1532+ let ops = RunnerOperations :: new ( ) . with_worker_id ( "worker-456" . to_string ( ) ) ;
1533+ assert_eq ! ( ops. worker_id, Some ( "worker-456" . to_string( ) ) ) ;
1534+ }
1535+
1536+ #[ test]
1537+ fn test_runner_operations_stats_initial ( ) {
1538+ let ops = RunnerOperations :: new ( ) ;
1539+ assert_eq ! ( ops. stats. fetch_count. load( Ordering :: Relaxed ) , 0 ) ;
1540+ assert_eq ! ( ops. stats. fetch_bytes_in. load( Ordering :: Relaxed ) , 0 ) ;
1541+ assert_eq ! ( ops. stats. fetch_bytes_out. load( Ordering :: Relaxed ) , 0 ) ;
1542+ }
1543+ }
0 commit comments