/** * SafePathTest - Unit tests for SafePath * * Comprehensive tests for the SafePath factory class which creates * URL-encoded EntityPath instances. */ using Implexus.Core; public static int main(string[] args) { int passed = 0; int failed = 0; // ======================================== // 1. Basic Path Construction Tests // ======================================== // Test: Single segment path if (test_basic_single_segment()) { passed++; stdout.puts("PASS: test_basic_single_segment\n"); } else { failed++; stdout.puts("FAIL: test_basic_single_segment\n"); } // Test: Multi-segment path if (test_basic_multi_segment()) { passed++; stdout.puts("PASS: test_basic_multi_segment\n"); } else { failed++; stdout.puts("FAIL: test_basic_multi_segment\n"); } // Test: Empty segments handling if (test_basic_empty_segments()) { passed++; stdout.puts("PASS: test_basic_empty_segments\n"); } else { failed++; stdout.puts("FAIL: test_basic_empty_segments\n"); } // ======================================== // 2. URL Encoding Tests // ======================================== // Test: Spaces in segments if (test_encoding_spaces()) { passed++; stdout.puts("PASS: test_encoding_spaces\n"); } else { failed++; stdout.puts("FAIL: test_encoding_spaces\n"); } // Test: Special characters (/, ?, #, =, &) if (test_encoding_special_chars()) { passed++; stdout.puts("PASS: test_encoding_special_chars\n"); } else { failed++; stdout.puts("FAIL: test_encoding_special_chars\n"); } // Test: Unicode characters (Japanese, emoji) if (test_encoding_unicode()) { passed++; stdout.puts("PASS: test_encoding_unicode\n"); } else { failed++; stdout.puts("FAIL: test_encoding_unicode\n"); } // Test: Reserved characters that should be encoded if (test_encoding_reserved_chars()) { passed++; stdout.puts("PASS: test_encoding_reserved_chars\n"); } else { failed++; stdout.puts("FAIL: test_encoding_reserved_chars\n"); } // ======================================== // 3. Variadic API Tests // ======================================== // Test: Null as first argument (should return root path) if (test_variadic_null_first()) { passed++; stdout.puts("PASS: test_variadic_null_first\n"); } else { failed++; stdout.puts("FAIL: test_variadic_null_first\n"); } // Test: Single segment + null if (test_variadic_single_segment()) { passed++; stdout.puts("PASS: test_variadic_single_segment\n"); } else { failed++; stdout.puts("FAIL: test_variadic_single_segment\n"); } // Test: Multiple segments + null if (test_variadic_multiple_segments()) { passed++; stdout.puts("PASS: test_variadic_multiple_segments\n"); } else { failed++; stdout.puts("FAIL: test_variadic_multiple_segments\n"); } // ======================================== // 4. Array API Tests // ======================================== // Test: Normal array of segments if (test_array_normal()) { passed++; stdout.puts("PASS: test_array_normal\n"); } else { failed++; stdout.puts("FAIL: test_array_normal\n"); } // Test: Empty array (should return root path) if (test_array_empty()) { passed++; stdout.puts("PASS: test_array_empty\n"); } else { failed++; stdout.puts("FAIL: test_array_empty\n"); } // Test: Array with special characters if (test_array_special_chars()) { passed++; stdout.puts("PASS: test_array_special_chars\n"); } else { failed++; stdout.puts("FAIL: test_array_special_chars\n"); } // ======================================== // 5. Decode Tests // ======================================== // Test: Decoding %20 to space if (test_decode_space()) { passed++; stdout.puts("PASS: test_decode_space\n"); } else { failed++; stdout.puts("FAIL: test_decode_space\n"); } // Test: Decoding %2F to / if (test_decode_slash()) { passed++; stdout.puts("PASS: test_decode_slash\n"); } else { failed++; stdout.puts("FAIL: test_decode_slash\n"); } // Test: Decoding unicode percent-encoded strings if (test_decode_unicode()) { passed++; stdout.puts("PASS: test_decode_unicode\n"); } else { failed++; stdout.puts("FAIL: test_decode_unicode\n"); } // Test: Invalid encoding error handling if (test_decode_invalid_encoding()) { passed++; stdout.puts("PASS: test_decode_invalid_encoding\n"); } else { failed++; stdout.puts("FAIL: test_decode_invalid_encoding\n"); } // ======================================== // 6. Integration Tests // ======================================== // Test: Verify returned EntityPath works correctly if (test_integration_entity_path()) { passed++; stdout.puts("PASS: test_integration_entity_path\n"); } else { failed++; stdout.puts("FAIL: test_integration_entity_path\n"); } // Test: to_string() on resulting paths if (test_integration_to_string()) { passed++; stdout.puts("PASS: test_integration_to_string\n"); } else { failed++; stdout.puts("FAIL: test_integration_to_string\n"); } // Test: Path operations (parent, append_child) if (test_integration_path_operations()) { passed++; stdout.puts("PASS: test_integration_path_operations\n"); } else { failed++; stdout.puts("FAIL: test_integration_path_operations\n"); } // Test: Round-trip encode/decode if (test_round_trip()) { passed++; stdout.puts("PASS: test_round_trip\n"); } else { failed++; stdout.puts("FAIL: test_round_trip\n"); } stdout.printf("\nResults: %d passed, %d failed\n", passed, failed); return failed > 0 ? 1 : 0; } // ======================================== // 1. Basic Path Construction Tests // ======================================== // Test: Single segment path bool test_basic_single_segment() { var path = SafePath.path("catalogue", null); if (path.is_root) return false; if (path.depth != 1) return false; if (path.name != "catalogue") return false; if (path.to_string() != "/catalogue") return false; return true; } // Test: Multi-segment path bool test_basic_multi_segment() { var path = SafePath.path("catalogue", "category", "document", null); if (path.is_root) return false; if (path.depth != 3) return false; if (path.name != "document") return false; if (path.to_string() != "/catalogue/category/document") return false; return true; } // Test: Empty segments handling bool test_basic_empty_segments() { // Empty string segment should be preserved as empty encoded segment var path = SafePath.path("a", "", "c", null); // Empty segments should still be counted if (path.depth != 3) return false; return true; } // ======================================== // 2. URL Encoding Tests // ======================================== // Test: Spaces in segments (should be encoded as %20) bool test_encoding_spaces() { var path = SafePath.path("users", "john doe", null); string path_str = path.to_string(); // Space should be encoded as %20 if (!path_str.contains("%20")) return false; if (path_str.contains(" ")) return false; // No literal space if (path_str != "/users/john%20doe") return false; // Test multiple spaces var path2 = SafePath.path("hello world test", null); if (path2.to_string() != "/hello%20world%20test") return false; return true; } // Test: Special characters (/, ?, #, =, &) bool test_encoding_special_chars() { // Forward slash should be encoded var path1 = SafePath.path("a/b", null); if (!path1.to_string().contains("%2F")) return false; // Question mark should be encoded var path2 = SafePath.path("query?param", null); if (!path2.to_string().contains("%3F")) return false; // Hash should be encoded var path3 = SafePath.path("anchor#link", null); if (!path3.to_string().contains("%23")) return false; // Equals should be encoded var path4 = SafePath.path("key=value", null); if (!path4.to_string().contains("%3D")) return false; // Ampersand should be encoded var path5 = SafePath.path("a&b", null); if (!path5.to_string().contains("%26")) return false; return true; } // Test: Unicode characters (Japanese, emoji) // Note: GLib.Uri.escape_string preserves unicode characters as-is (IRIs) // This is correct behavior for modern URIs bool test_encoding_unicode() { // Japanese characters - preserved as-is in the path var path1 = SafePath.path("ユーザー", null); string str1 = path1.to_string(); // Should contain the unicode characters if (!str1.has_prefix("/")) return false; if (!str1.contains("ユーザー")) return false; // Unicode preserved // Emoji - also preserved var path2 = SafePath.path("hello🎉world", null); string str2 = path2.to_string(); if (!str2.contains("🎉")) return false; // Chinese characters - also preserved var path3 = SafePath.path("用户", null); string str3 = path3.to_string(); if (!str3.contains("用户")) return false; // But special URI characters within unicode strings should still be encoded var path4 = SafePath.path("ユーザー/名前", null); string str4 = path4.to_string(); if (!str4.contains("%2F")) return false; // Slash should be encoded return true; } // Test: Reserved characters that should be encoded bool test_encoding_reserved_chars() { // Test various reserved characters string[] reserved_chars = { ":", "/", "?", "#", "[", "]", "@", "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" }; foreach (var ch in reserved_chars) { var path = SafePath.path("test" + ch + "value", null); string result = path.to_string(); // Reserved chars should be encoded (contain %) // The character itself should not appear literally (except for special cases) if (ch != "/" && result.contains(ch) && !result.contains("%")) { return false; } } return true; } // ======================================== // 3. Variadic API Tests // ======================================== // Test: Null as first argument (should return root path) bool test_variadic_null_first() { var path = SafePath.path(null); if (!path.is_root) return false; if (path.depth != 0) return false; if (path.to_string() != "/") return false; return true; } // Test: Single segment + null bool test_variadic_single_segment() { var path = SafePath.path("catalogue", null); if (path.is_root) return false; if (path.depth != 1) return false; if (path.name != "catalogue") return false; return true; } // Test: Multiple segments + null bool test_variadic_multiple_segments() { var path = SafePath.path("a", "b", "c", "d", "e", null); if (path.depth != 5) return false; if (path.to_string() != "/a/b/c/d/e") return false; // Verify each segment if (path.name != "e") return false; if (path.parent.name != "d") return false; return true; } // ======================================== // 4. Array API Tests // ======================================== // Test: Normal array of segments bool test_array_normal() { string[] segments = { "catalogue", "category", "document" }; var path = SafePath.from_array(segments); if (path.depth != 3) return false; if (path.to_string() != "/catalogue/category/document") return false; if (path.name != "document") return false; return true; } // Test: Empty array (should return root path) bool test_array_empty() { string[] segments = { }; var path = SafePath.from_array(segments); if (!path.is_root) return false; if (path.depth != 0) return false; if (path.to_string() != "/") return false; return true; } // Test: Array with special characters bool test_array_special_chars() { string[] segments = { "user name", "doc/with/slashes", "query?test" }; var path = SafePath.from_array(segments); if (path.depth != 3) return false; string path_str = path.to_string(); // Verify encoding if (!path_str.contains("%20")) return false; // Space encoded if (!path_str.contains("%2F")) return false; // Slash encoded if (!path_str.contains("%3F")) return false; // Question mark encoded return true; } // ======================================== // 5. Decode Tests // ======================================== // Test: Decoding %20 to space bool test_decode_space() { try { string decoded = SafePath.decode_segment("john%20doe"); if (decoded != "john doe") return false; // Multiple spaces string decoded2 = SafePath.decode_segment("hello%20world%20test"); if (decoded2 != "hello world test") return false; return true; } catch (EntityError e) { stdout.printf("Unexpected error: %s\n", e.message); return false; } } // Test: Decoding %2F to / bool test_decode_slash() { try { string decoded = SafePath.decode_segment("path%2Fwith%2Fslashes"); if (decoded != "path/with/slashes") return false; return true; } catch (EntityError e) { stdout.printf("Unexpected error: %s\n", e.message); return false; } } // Test: Decoding unicode percent-encoded strings bool test_decode_unicode() { try { // Japanese characters encoded string decoded = SafePath.decode_segment("%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC"); if (decoded != "ユーザー") return false; // Emoji encoded string decoded2 = SafePath.decode_segment("hello%F0%9F%8E%89world"); if (decoded2 != "hello🎉world") return false; return true; } catch (EntityError e) { stdout.printf("Unexpected error: %s\n", e.message); return false; } } // Test: Invalid encoding error handling bool test_decode_invalid_encoding() { // Invalid percent encoding - incomplete sequence try { SafePath.decode_segment("invalid%"); return false; // Should have thrown } catch (EntityError e) { // Expected - continue to test more cases } // Invalid hex digits try { SafePath.decode_segment("invalid%GG"); return false; // Should have thrown } catch (EntityError e) { // Expected } // Incomplete percent sequence at end try { SafePath.decode_segment("test%2"); return false; // Should have thrown } catch (EntityError e) { // Expected } return true; } // ======================================== // 6. Integration Tests // ======================================== // Test: Verify returned EntityPath works correctly bool test_integration_entity_path() { var path = SafePath.path("catalogue", "category", null); // Test is_root if (path.is_root) return false; // Test depth if (path.depth != 2) return false; // Test name if (path.name != "category") return false; // Test parent var parent = path.parent; if (parent.depth != 1) return false; if (parent.name != "catalogue") return false; // Test is_ancestor_of / is_descendant_of if (!parent.is_ancestor_of(path)) return false; if (!path.is_descendant_of(parent)) return false; return true; } // Test: to_string() on resulting paths bool test_integration_to_string() { // Simple path var path1 = SafePath.path("a", null); if (path1.to_string() != "/a") return false; // Multi-segment path var path2 = SafePath.path("a", "b", "c", null); if (path2.to_string() != "/a/b/c") return false; // Root path var path3 = SafePath.path(null); if (path3.to_string() != "/") return false; // Path with encoded characters var path4 = SafePath.path("hello world", null); if (path4.to_string() != "/hello%20world") return false; // Array API string[] segments = { "x", "y", "z" }; var path5 = SafePath.from_array(segments); if (path5.to_string() != "/x/y/z") return false; return true; } // Test: Path operations (parent, append_child) bool test_integration_path_operations() { var path = SafePath.path("catalogue", "category", null); // Test parent var parent = path.parent; if (parent.to_string() != "/catalogue") return false; // Test append_child - note: EntityPath.append_child does NOT encode // It expects pre-encoded segment names var child = path.append_child("document"); if (child.to_string() != "/catalogue/category/document") return false; if (child.name != "document") return false; // Test parent chain if (!child.parent.equals(path)) return false; // To add a child with special chars, use SafePath to encode first // or manually encode the segment var child_with_space = path.append_child("my%20document"); if (!child_with_space.to_string().contains("%20")) return false; return true; } // Test: Round-trip encode/decode bool test_round_trip() { try { // Test with spaces string original1 = "john doe"; var path1 = SafePath.path(original1, null); string encoded_name1 = path1.name; string decoded1 = SafePath.decode_segment(encoded_name1); if (decoded1 != original1) return false; // Test with special characters string original2 = "a/b?c#d=e&f"; var path2 = SafePath.path(original2, null); string encoded_name2 = path2.name; string decoded2 = SafePath.decode_segment(encoded_name2); if (decoded2 != original2) return false; // Test with unicode string original3 = "ユーザー🎉"; var path3 = SafePath.path(original3, null); string encoded_name3 = path3.name; string decoded3 = SafePath.decode_segment(encoded_name3); if (decoded3 != original3) return false; return true; } catch (EntityError e) { stdout.printf("Unexpected error in round-trip: %s\n", e.message); return false; } }