konduktor/go/internal/pathmatcher/pathmatcher_test.go
Илья Глазунов 8f5b9a5cd1 go implementation
2025-12-11 16:52:13 +03:00

461 lines
10 KiB
Go

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")
}
}