// test_route_extraction_property.v - Property test for route extraction completeness // **Feature: swagger-ui, Property 6: Route Extraction Completeness** // **Validates: Requirements 5.1, 5.2, 5.3** module main import hono import net.http import rand // Test data generators fn generate_random_path() string { paths := [ '/', '/users', '/users/:id', '/api/v1/items', '/api/v1/items/:itemId', '/posts/:postId/comments/:commentId', '/products/:productId/reviews', '/admin/settings', '/health', '/api/users/:userId/orders/:orderId/items', ] return paths[rand.intn(paths.len) or { 0 }] } fn generate_random_method() string { methods := ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] return methods[rand.intn(methods.len) or { 0 }] } // Simple handler for testing fn test_handler(mut c hono.Context) http.Response { return c.text('OK') } // Property 6: Route Extraction Completeness // *For any* Hono application with registered routes (including sub-applications), // calling get_routes() SHALL return all routes with correct paths, methods, and path parameters. fn test_property_route_extraction_completeness() { println('Property 6: Route Extraction Completeness') println('Testing that get_routes() returns all registered routes with correct information...') // Test 1: Basic routes extraction { mut app := hono.Hono.new() // Register routes with different methods app.get('/users', test_handler) app.post('/users', test_handler) app.get('/users/:id', test_handler) app.put('/users/:id', test_handler) app.delete('/users/:id', test_handler) routes := app.get_routes() // Verify all routes are extracted assert routes.len == 5, 'Expected 5 routes, got ${routes.len}' // Verify methods are correct mut methods := map[string]int{} for route in routes { methods[route.method]++ } assert methods['GET'] == 2, 'Expected 2 GET routes' assert methods['POST'] == 1, 'Expected 1 POST route' assert methods['PUT'] == 1, 'Expected 1 PUT route' assert methods['DELETE'] == 1, 'Expected 1 DELETE route' println(' ✅ Basic routes extraction: PASSED') } // Test 2: Path parameters extraction { mut app := hono.Hono.new() app.get('/users/:userId', test_handler) app.get('/posts/:postId/comments/:commentId', test_handler) app.get('/api/v1/items/:itemId/reviews/:reviewId', test_handler) routes := app.get_routes() // Find routes and verify path parameters for route in routes { if route.path == '/users/:userId' { assert route.path_params.len == 1, 'Expected 1 path param for /users/:userId' assert 'userId' in route.path_params, 'Expected userId in path params' } else if route.path == '/posts/:postId/comments/:commentId' { assert route.path_params.len == 2, 'Expected 2 path params for /posts/:postId/comments/:commentId' assert 'postId' in route.path_params, 'Expected postId in path params' assert 'commentId' in route.path_params, 'Expected commentId in path params' } else if route.path == '/api/v1/items/:itemId/reviews/:reviewId' { assert route.path_params.len == 2, 'Expected 2 path params' assert 'itemId' in route.path_params, 'Expected itemId in path params' assert 'reviewId' in route.path_params, 'Expected reviewId in path params' } } println(' ✅ Path parameters extraction: PASSED') } // Test 3: All HTTP methods support { mut app := hono.Hono.new() app.get('/test', test_handler) app.post('/test', test_handler) app.put('/test', test_handler) app.delete('/test', test_handler) app.patch('/test', test_handler) app.head('/test', test_handler) app.options('/test', test_handler) routes := app.get_routes() // Verify all 7 HTTP methods are present mut found_methods := map[string]bool{} for route in routes { found_methods[route.method] = true } assert found_methods['GET'], 'GET method not found' assert found_methods['POST'], 'POST method not found' assert found_methods['PUT'], 'PUT method not found' assert found_methods['DELETE'], 'DELETE method not found' assert found_methods['PATCH'], 'PATCH method not found' assert found_methods['HEAD'], 'HEAD method not found' assert found_methods['OPTIONS'], 'OPTIONS method not found' println(' ✅ All HTTP methods support: PASSED') } // Test 4: Sub-application routes extraction { mut app := hono.Hono.new() mut subapp := hono.Hono.new() // Register routes in main app app.get('/main', test_handler) // Register routes in sub-app subapp.get('/items', test_handler) subapp.get('/items/:id', test_handler) subapp.post('/items', test_handler) // Mount sub-app app.route('/api', mut subapp) routes := app.get_routes() // Verify main app route mut found_main := false for route in routes { if route.path == '/main' && route.method == 'GET' { found_main = true break } } assert found_main, 'Main app route /main not found' // Verify sub-app routes are included with prefix mut found_api_items := false mut found_api_items_id := false mut found_api_items_post := false for route in routes { if route.path == '/api/items' && route.method == 'GET' { found_api_items = true } if route.path == '/api/items/:id' && route.method == 'GET' { found_api_items_id = true // Verify path params are extracted correctly assert 'id' in route.path_params, 'Expected id in path params for /api/items/:id' } if route.path == '/api/items' && route.method == 'POST' { found_api_items_post = true } } assert found_api_items, 'Sub-app route /api/items GET not found' assert found_api_items_id, 'Sub-app route /api/items/:id GET not found' assert found_api_items_post, 'Sub-app route /api/items POST not found' println(' ✅ Sub-application routes extraction: PASSED') } // Test 5: Empty path parameters for static routes { mut app := hono.Hono.new() app.get('/static/path', test_handler) app.get('/another/static', test_handler) routes := app.get_routes() for route in routes { assert route.path_params.len == 0, 'Static route should have no path params' } println(' ✅ Static routes have no path params: PASSED') } // Test 6: Route handler is preserved { mut app := hono.Hono.new() app.get('/test', test_handler) routes := app.get_routes() assert routes.len == 1, 'Expected 1 route' // Handler should not be nil (we can't directly compare functions, but we can check it exists) // The handler field is of type IHandler which is an interface println(' ✅ Route handler preservation: PASSED') } println('\n✅ Property 6: Route Extraction Completeness - ALL TESTS PASSED') } // Expected route structure for testing struct ExpectedRoute { path string method string } // Property test with randomized inputs fn test_property_route_extraction_randomized() { println('\nProperty 6 (Randomized): Route Extraction with Random Inputs') iterations := 100 mut passed := 0 for i in 0 .. iterations { mut app := hono.Hono.new() // Generate random number of routes (1-10) num_routes := rand.intn(10) or { 1 } + 1 mut expected_routes := []ExpectedRoute{} for _ in 0 .. num_routes { path := generate_random_path() method := generate_random_method() // Register route based on method match method { 'GET' { app.get(path, test_handler) } 'POST' { app.post(path, test_handler) } 'PUT' { app.put(path, test_handler) } 'DELETE' { app.delete(path, test_handler) } 'PATCH' { app.patch(path, test_handler) } 'HEAD' { app.head(path, test_handler) } 'OPTIONS' { app.options(path, test_handler) } else {} } expected_routes << ExpectedRoute{ path: path method: method } } // Get routes and verify routes := app.get_routes() // Verify count matches if routes.len != expected_routes.len { println(' ❌ Iteration ${i}: Expected ${expected_routes.len} routes, got ${routes.len}') continue } // Verify all expected routes are present mut all_found := true for expected in expected_routes { mut found := false for route in routes { if route.path == expected.path && route.method == expected.method { found = true // Verify path params are correctly extracted expected_params := extract_expected_params(expected.path) if route.path_params.len != expected_params.len { all_found = false break } for param in expected_params { if param !in route.path_params { all_found = false break } } break } } if !found { all_found = false break } } if all_found { passed++ } } success_rate := f64(passed) / f64(iterations) * 100.0 println(' Passed: ${passed}/${iterations} (${success_rate:.1}%)') // Allow some tolerance for edge cases assert passed >= iterations * 95 / 100, 'Property test failed: less than 95% success rate' println('✅ Property 6 (Randomized): PASSED') } // Helper function to extract expected path parameters from a path fn extract_expected_params(path string) []string { mut params := []string{} segments := path.split('/') for segment in segments { if segment.starts_with(':') && segment.len > 1 { params << segment[1..] } } return params } fn main() { println('=' .repeat(70)) println('Property-Based Test: Route Extraction Completeness') println('=' .repeat(70)) test_property_route_extraction_completeness() test_property_route_extraction_randomized() println('\n' + '=' .repeat(70)) println('All Property Tests Completed Successfully!') println('=' .repeat(70)) }