forked from aegis/pyserveX
461 lines
10 KiB
Go
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")
|
|
}
|
|
}
|