//! Integration tests for the tile decoding pipeline //! //! These tests verify the end-to-end functionality of tile decoding, //! from raw MVT/JSON data to renderable tile buffers. use nigig_map::tile_decode::{ build_tile_buffers_from_body, mbtiles_tile_to_overpass_response, }; use nigig_map::geometry::TileKey; use nigig_map::style::CompiledMapTheme; /// Test decoding a simple Overpass JSON response with nodes and ways #[test] fn test_decode_simple_overpass_response() { let json = r#"{ "version": 0.6, "elements": [ { "type": "node", "id": 1, "lat": 52.3676, "lon": 4.9041, "tags": { "name": "Amsterdam Central" } }, { "type": "way", "id": 2, "nodes": [1, 3, 4, 1], "tags": { "building": "yes", "name": "Test Building" } }, { "type": "node", "id": 3, "lat": 52.3680, "lon": 4.9045 }, { "type": "node", "id": 4, "lat": 52.3678, "lon": 4.9048 } ] }"#; let tile_key = TileKey { z: 14, x: 8536, y: 5534 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Failed to decode: {:?}", result.err()); let buffers = result.unwrap(); assert!(buffers.feature_count > 0, "Should have decoded at least one feature"); assert!(!buffers.fill_vertices.is_empty() || !buffers.stroke_vertices.is_empty(), "Should have generated geometry"); } /// Test decoding with security limits (should reject oversized input) #[test] fn test_security_limit_json_size() { // Create a JSON that exceeds the element count limit (250,000). // Build valid JSON with comma-separated elements. let mut elements = String::new(); for i in 0..260_000 { if i > 0 { elements.push(','); } elements.push_str(&format!( r#"{{"type":"node","id":{},"lat":0.0,"lon":0.0}}"#, i )); } let large_json = format!(r#"{{"elements":[{}]}}"#, elements); let tile_key = TileKey { z: 14, x: 0, y: 0 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, &large_json, &theme); assert!(result.is_err(), "Should reject oversized JSON"); assert!(result.unwrap_err().contains("too large") || result.unwrap_err().contains("too many"), "Error should mention size limit"); } /// Test decoding with invalid JSON #[test] fn test_decode_invalid_json() { let invalid_json = r#"{"version": 0.6, "elements": [invalid}"#; let tile_key = TileKey { z: 14, x: 0, y: 0 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, invalid_json, &theme); assert!(result.is_err(), "Should reject invalid JSON"); } /// Test decoding empty elements array #[test] fn test_decode_empty_elements() { let json = r#"{"version": 0.6, "elements": []}"#; let tile_key = TileKey { z: 14, x: 0, y: 0 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Should handle empty elements"); let buffers = result.unwrap(); assert_eq!(buffers.feature_count, 0, "Should have no features"); } /// Test decoding various OSM feature types #[test] fn test_decode_various_feature_types() { let json = r#"{ "version": 0.6, "elements": [ { "type": "node", "id": 1, "lat": 52.3676, "lon": 4.9041, "tags": {"amenity": "restaurant", "name": "Test Restaurant"} }, { "type": "way", "id": 2, "nodes": [10, 11, 12, 10], "tags": {"highway": "residential", "name": "Test Street"} }, { "type": "way", "id": 3, "nodes": [20, 21, 22, 23, 20], "tags": {"natural": "water", "name": "Test Lake"} }, { "type": "node", "id": 10, "lat": 52.367, "lon": 4.904 }, { "type": "node", "id": 11, "lat": 52.368, "lon": 4.905 }, { "type": "node", "id": 12, "lat": 52.367, "lon": 4.905 }, { "type": "node", "id": 20, "lat": 52.369, "lon": 4.906 }, { "type": "node", "id": 21, "lat": 52.370, "lon": 4.907 }, { "type": "node", "id": 22, "lat": 52.369, "lon": 4.908 }, { "type": "node", "id": 23, "lat": 52.368, "lon": 4.907 } ] }"#; let tile_key = TileKey { z: 14, x: 8536, y: 5534 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Failed to decode: {:?}", result.err()); let buffers = result.unwrap(); assert!(buffers.feature_count >= 3, "Should decode restaurant, street, and lake"); // Should have POIs (restaurant) assert!(!buffers.pois.is_empty(), "Should have POIs"); // Should have labels assert!(!buffers.labels.is_empty(), "Should have labels"); } /// Test decoding with different zoom levels #[test] fn test_decode_different_zoom_levels() { let json = r#"{ "version": 0.6, "elements": [ { "type": "way", "id": 1, "nodes": [10, 11, 12, 10], "tags": {"building": "yes"} }, { "type": "node", "id": 10, "lat": 52.367, "lon": 4.904 }, { "type": "node", "id": 11, "lat": 52.368, "lon": 4.905 }, { "type": "node", "id": 12, "lat": 52.367, "lon": 4.905 } ] }"#; let theme = CompiledMapTheme::default(); // Test at different zoom levels for zoom in [10, 12, 14, 16] { let tile_key = TileKey { z: zoom, x: 0, y: 0 }; let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Failed at zoom {}: {:?}", zoom, result.err()); } } /// Test that malformed way references are handled gracefully #[test] fn test_decode_malformed_way_references() { let json = r#"{ "version": 0.6, "elements": [ { "type": "way", "id": 1, "nodes": [999, 1000, 1001], "tags": {"highway": "residential"} } ] }"#; let tile_key = TileKey { z: 14, x: 0, y: 0 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Should handle missing nodes gracefully"); let buffers = result.unwrap(); // Way should be skipped due to missing nodes assert_eq!(buffers.feature_count, 0, "Should skip malformed way"); } /// Test decoding with Unicode characters in tags #[test] fn test_decode_unicode_tags() { let json = r#"{ "version": 0.6, "elements": [ { "type": "node", "id": 1, "lat": 52.3676, "lon": 4.9041, "tags": { "name": "阿姆斯特丹", "name:ja": "アムステルダム", "name:ar": "أمستردام" } } ] }"#; let tile_key = TileKey { z: 14, x: 0, y: 0 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Should handle Unicode tags"); } /// Test MBTiles to Overpass conversion #[test] fn test_mbtiles_to_overpass_conversion() { // This test would require actual MVT binary data // For now, we'll skip it or use a mock // In a real implementation, we'd load a test MVT file // Placeholder test let tile_key = TileKey { z: 14, x: 0, y: 0 }; let mock_mvt_data = vec![]; // Would be actual MVT binary if !mock_mvt_data.is_empty() { let result = mbtiles_tile_to_overpass_response(tile_key, &mock_mvt_data); assert!(result.is_ok(), "Should convert MVT to Overpass format"); } } /// Test that geometry is correctly tessellated #[test] fn test_geometry_tessellation() { let json = r#"{ "version": 0.6, "elements": [ { "type": "way", "id": 1, "nodes": [10, 11, 12, 13, 10], "tags": {"building": "yes"} }, { "type": "node", "id": 10, "lat": 52.367, "lon": 4.904 }, { "type": "node", "id": 11, "lat": 52.368, "lon": 4.904 }, { "type": "node", "id": 12, "lat": 52.368, "lon": 4.905 }, { "type": "node", "id": 13, "lat": 52.367, "lon": 4.905 } ] }"#; let tile_key = TileKey { z: 14, x: 8536, y: 5534 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Failed to decode: {:?}", result.err()); let buffers = result.unwrap(); // Building should generate fill geometry assert!(!buffers.fill_vertices.is_empty(), "Should have fill vertices"); assert!(!buffers.fill_indices.is_empty(), "Should have fill indices"); // Vertices should be in groups of 7 (x, y, color, nx, ny, shape_id, zbias) assert_eq!(buffers.fill_vertices.len() % 7, 0, "Fill vertices should be properly formatted"); } /// Test stroke geometry generation #[test] fn test_stroke_geometry_generation() { let json = r#"{ "version": 0.6, "elements": [ { "type": "way", "id": 1, "nodes": [10, 11, 12], "tags": {"highway": "primary"} }, { "type": "node", "id": 10, "lat": 52.367, "lon": 4.904 }, { "type": "node", "id": 11, "lat": 52.368, "lon": 4.905 }, { "type": "node", "id": 12, "lat": 52.369, "lon": 4.906 } ] }"#; let tile_key = TileKey { z: 14, x: 8536, y: 5534 }; let theme = CompiledMapTheme::default(); let result = build_tile_buffers_from_body(tile_key, json, &theme); assert!(result.is_ok(), "Failed to decode: {:?}", result.err()); let buffers = result.unwrap(); // Highway should generate stroke geometry assert!(!buffers.stroke_vertices.is_empty(), "Should have stroke vertices"); assert!(!buffers.stroke_indices.is_empty(), "Should have stroke indices"); // Vertices should be in groups of 7 assert_eq!(buffers.stroke_vertices.len() % 7, 0, "Stroke vertices should be properly formatted"); }