package pathmatcher import ( "testing" ) // ============== MountedPath Tests ============== func TestMountedPath_RootMountMatchesEverything(t *testing.T) { mount := NewMountedPath("") tests := []string{"/", "/api", "/api/users", "/anything/at/all"} for _, path := range tests { if !mount.Matches(path) { t.Errorf("Root mount should match %s", path) } } } func TestMountedPath_SlashRootMountMatchesEverything(t *testing.T) { mount := NewMountedPath("/") tests := []string{"/", "/api", "/api/users"} for _, path := range tests { if !mount.Matches(path) { t.Errorf("'/' mount should match %s", path) } } } func TestMountedPath_ExactPathMatch(t *testing.T) { mount := NewMountedPath("/api") tests := []struct { path string expected bool }{ {"/api", true}, {"/api/", true}, {"/api/users", true}, } for _, tt := range tests { if got := mount.Matches(tt.path); got != tt.expected { t.Errorf("Matches(%s) = %v, want %v", tt.path, got, tt.expected) } } } func TestMountedPath_NoFalsePrefixMatch(t *testing.T) { mount := NewMountedPath("/api") tests := []string{"/api-v2", "/api2", "/apiv2"} for _, path := range tests { if mount.Matches(path) { t.Errorf("/api should not match %s", path) } } } func TestMountedPath_ShorterPathNoMatch(t *testing.T) { mount := NewMountedPath("/api/v1") tests := []string{"/api", "/ap", "/"} for _, path := range tests { if mount.Matches(path) { t.Errorf("/api/v1 should not match shorter path %s", path) } } } func TestMountedPath_TrailingSlashNormalized(t *testing.T) { mount1 := NewMountedPath("/api/") mount2 := NewMountedPath("/api") if mount1.Path() != "/api" { t.Errorf("Expected path /api, got %s", mount1.Path()) } if mount2.Path() != "/api" { t.Errorf("Expected path /api, got %s", mount2.Path()) } if !mount1.Matches("/api/users") { t.Error("mount1 should match /api/users") } if !mount2.Matches("/api/users") { t.Error("mount2 should match /api/users") } } func TestMountedPath_GetModifiedPathStripsPrefix(t *testing.T) { mount := NewMountedPath("/api") tests := []struct { input string expected string }{ {"/api", "/"}, {"/api/", "/"}, {"/api/users", "/users"}, {"/api/users/123", "/users/123"}, } for _, tt := range tests { if got := mount.GetModifiedPath(tt.input); got != tt.expected { t.Errorf("GetModifiedPath(%s) = %s, want %s", tt.input, got, tt.expected) } } } func TestMountedPath_GetModifiedPathNoStrip(t *testing.T) { mount := NewMountedPath("/api", WithStripPath(false)) tests := []struct { input string expected string }{ {"/api/users", "/api/users"}, {"/api", "/api"}, } for _, tt := range tests { if got := mount.GetModifiedPath(tt.input); got != tt.expected { t.Errorf("GetModifiedPath(%s) = %s, want %s", tt.input, got, tt.expected) } } } func TestMountedPath_RootMountModifiedPath(t *testing.T) { mount := NewMountedPath("") tests := []struct { input string expected string }{ {"/api/users", "/api/users"}, {"/", "/"}, } for _, tt := range tests { if got := mount.GetModifiedPath(tt.input); got != tt.expected { t.Errorf("GetModifiedPath(%s) = %s, want %s", tt.input, got, tt.expected) } } } func TestMountedPath_NameProperty(t *testing.T) { mount1 := NewMountedPath("/api") mount2 := NewMountedPath("/api", WithName("API Mount")) if mount1.Name() != "/api" { t.Errorf("Expected name /api, got %s", mount1.Name()) } if mount2.Name() != "API Mount" { t.Errorf("Expected name 'API Mount', got %s", mount2.Name()) } } // ============== MountManager Tests ============== func TestMountManager_EmptyManager(t *testing.T) { manager := NewMountManager() if got := manager.GetMount("/api"); got != nil { t.Error("Empty manager should return nil") } if got := manager.MountCount(); got != 0 { t.Errorf("Expected mount count 0, got %d", got) } } func TestMountManager_AddMount(t *testing.T) { manager := NewMountManager() mount := NewMountedPath("/api") manager.AddMount(mount) if manager.MountCount() != 1 { t.Errorf("Expected mount count 1, got %d", manager.MountCount()) } if got := manager.GetMount("/api/users"); got != mount { t.Error("GetMount should return the added mount") } } func TestMountManager_LongestPrefixMatching(t *testing.T) { manager := NewMountManager() apiMount := NewMountedPath("/api", WithName("api")) apiV1Mount := NewMountedPath("/api/v1", WithName("api_v1")) apiV2Mount := NewMountedPath("/api/v2", WithName("api_v2")) manager.AddMount(apiMount) manager.AddMount(apiV2Mount) manager.AddMount(apiV1Mount) tests := []struct { path string expectedName string }{ {"/api/v1/users", "api_v1"}, {"/api/v2/items", "api_v2"}, {"/api/v3/other", "api"}, {"/api", "api"}, } for _, tt := range tests { got := manager.GetMount(tt.path) if got == nil { t.Errorf("GetMount(%s) returned nil, want mount with name %s", tt.path, tt.expectedName) continue } if got.Name() != tt.expectedName { t.Errorf("GetMount(%s).Name() = %s, want %s", tt.path, got.Name(), tt.expectedName) } } } func TestMountManager_RemoveMount(t *testing.T) { manager := NewMountManager() manager.AddMount(NewMountedPath("/api")) manager.AddMount(NewMountedPath("/admin")) if manager.MountCount() != 2 { t.Errorf("Expected mount count 2, got %d", manager.MountCount()) } result := manager.RemoveMount("/api") if !result { t.Error("RemoveMount should return true") } if manager.MountCount() != 1 { t.Errorf("Expected mount count 1, got %d", manager.MountCount()) } if manager.GetMount("/api/users") != nil { t.Error("GetMount(/api/users) should return nil after removal") } if manager.GetMount("/admin/users") == nil { t.Error("GetMount(/admin/users) should still work") } } func TestMountManager_RemoveNonexistentMount(t *testing.T) { manager := NewMountManager() result := manager.RemoveMount("/api") if result { t.Error("RemoveMount should return false for nonexistent mount") } } func TestMountManager_ListMounts(t *testing.T) { manager := NewMountManager() manager.AddMount(NewMountedPath("/api", WithName("API"))) manager.AddMount(NewMountedPath("/admin", WithName("Admin"))) mounts := manager.ListMounts() if len(mounts) != 2 { t.Errorf("Expected 2 mounts, got %d", len(mounts)) } for _, m := range mounts { if _, ok := m["path"]; !ok { t.Error("Mount should have 'path' key") } if _, ok := m["name"]; !ok { t.Error("Mount should have 'name' key") } if _, ok := m["strip_path"]; !ok { t.Error("Mount should have 'strip_path' key") } } } func TestMountManager_MountsReturnsCopy(t *testing.T) { manager := NewMountManager() manager.AddMount(NewMountedPath("/api")) mounts1 := manager.Mounts() mounts2 := manager.Mounts() if &mounts1[0] == &mounts2[0] { t.Error("Mounts() should return different slices") } } // ============== Utility Functions Tests ============== func TestPathMatchesPrefix_Basic(t *testing.T) { tests := []struct { path string prefix string expected bool }{ {"/api/users", "/api", true}, {"/api", "/api", true}, {"/api-v2", "/api", false}, {"/ap", "/api", false}, } for _, tt := range tests { if got := PathMatchesPrefix(tt.path, tt.prefix); got != tt.expected { t.Errorf("PathMatchesPrefix(%s, %s) = %v, want %v", tt.path, tt.prefix, got, tt.expected) } } } func TestPathMatchesPrefix_Root(t *testing.T) { tests := []struct { path string prefix string expected bool }{ {"/anything", "", true}, {"/anything", "/", true}, } for _, tt := range tests { if got := PathMatchesPrefix(tt.path, tt.prefix); got != tt.expected { t.Errorf("PathMatchesPrefix(%s, %s) = %v, want %v", tt.path, tt.prefix, got, tt.expected) } } } func TestStripPathPrefix_Basic(t *testing.T) { tests := []struct { path string prefix string expected string }{ {"/api/users", "/api", "/users"}, {"/api", "/api", "/"}, {"/api/", "/api", "/"}, } for _, tt := range tests { if got := StripPathPrefix(tt.path, tt.prefix); got != tt.expected { t.Errorf("StripPathPrefix(%s, %s) = %s, want %s", tt.path, tt.prefix, got, tt.expected) } } } func TestStripPathPrefix_Root(t *testing.T) { tests := []struct { path string prefix string expected string }{ {"/api/users", "", "/api/users"}, {"/api/users", "/", "/api/users"}, } for _, tt := range tests { if got := StripPathPrefix(tt.path, tt.prefix); got != tt.expected { t.Errorf("StripPathPrefix(%s, %s) = %s, want %s", tt.path, tt.prefix, got, tt.expected) } } } func TestMatchAndModifyPath_Combined(t *testing.T) { tests := []struct { path string prefix string stripPath bool wantMatches bool wantModified string }{ {"/api/users", "/api", true, true, "/users"}, {"/api", "/api", true, true, "/"}, {"/other", "/api", true, false, ""}, {"/api/users", "/api", false, true, "/api/users"}, } for _, tt := range tests { matches, modified := MatchAndModifyPath(tt.path, tt.prefix, tt.stripPath) if matches != tt.wantMatches { t.Errorf("MatchAndModifyPath(%s, %s, %v) matches = %v, want %v", tt.path, tt.prefix, tt.stripPath, matches, tt.wantMatches) } if modified != tt.wantModified { t.Errorf("MatchAndModifyPath(%s, %s, %v) modified = %s, want %s", tt.path, tt.prefix, tt.stripPath, modified, tt.wantModified) } } } // ============== Performance Tests ============== func TestPerformance_ManyMatches(t *testing.T) { mount := NewMountedPath("/api/v1/users") for i := 0; i < 10000; i++ { if !mount.Matches("/api/v1/users/123/posts") { t.Fatal("Should match") } if mount.Matches("/other/path") { t.Fatal("Should not match") } } } func TestPerformance_ManyMounts(t *testing.T) { manager := NewMountManager() for i := 0; i < 100; i++ { manager.AddMount(NewMountedPath("/api/v" + string(rune('0'+i%10)) + string(rune('0'+i/10)))) } if manager.MountCount() != 100 { t.Errorf("Expected 100 mounts, got %d", manager.MountCount()) } } // ============== Benchmarks ============== func BenchmarkMountedPath_Matches(b *testing.B) { mount := NewMountedPath("/api/v1") b.ResetTimer() for i := 0; i < b.N; i++ { mount.Matches("/api/v1/users/123") } } func BenchmarkMountManager_GetMount(b *testing.B) { manager := NewMountManager() for i := 0; i < 20; i++ { manager.AddMount(NewMountedPath("/api/v" + string(rune('0'+i%10)))) } b.ResetTimer() for i := 0; i < b.N; i++ { manager.GetMount("/api/v5/users/123") } } func BenchmarkPathMatchesPrefix(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { PathMatchesPrefix("/api/v1/users/123", "/api/v1") } }