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

376 lines
9.1 KiB
Go

package routing
import (
"path/filepath"
"testing"
)
// ============== Router Initialization Tests ==============
func TestRouter_Initialization(t *testing.T) {
router := NewRouter()
if router.StaticDir() != "./static" {
t.Errorf("Expected static dir ./static, got %s", router.StaticDir())
}
if len(router.Routes()) != 0 {
t.Error("Expected empty routes")
}
if len(router.ExactRoutes()) != 0 {
t.Error("Expected empty exact routes")
}
if router.DefaultRoute() != nil {
t.Error("Expected nil default route")
}
}
func TestRouter_CustomStaticDir(t *testing.T) {
router := NewRouter(WithStaticDir("/custom/path"))
if router.StaticDir() != "/custom/path" {
t.Errorf("Expected static dir /custom/path, got %s", router.StaticDir())
}
}
// ============== Route Adding Tests ==============
func TestRouter_AddExactRoute(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"return": "200 OK"}
router.AddRoute("=/health", config)
exactRoutes := router.ExactRoutes()
if _, ok := exactRoutes["/health"]; !ok {
t.Error("Expected /health in exact routes")
}
}
func TestRouter_AddDefaultRoute(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"spa_fallback": true, "root": "./static"}
router.AddRoute("__default__", config)
if router.DefaultRoute() == nil {
t.Error("Expected default route to be set")
}
}
func TestRouter_AddRegexRoute(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"root": "./static"}
router.AddRoute("~^/api/", config)
if len(router.Routes()) != 1 {
t.Errorf("Expected 1 regex route, got %d", len(router.Routes()))
}
}
func TestRouter_AddCaseInsensitiveRegexRoute(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"root": "./static", "cache_control": "public, max-age=3600"}
router.AddRoute("~*\\.(css|js)$", config)
if len(router.Routes()) != 1 {
t.Errorf("Expected 1 regex route, got %d", len(router.Routes()))
}
if router.Routes()[0].CaseSensitive {
t.Error("Expected case-insensitive route")
}
}
func TestRouter_InvalidRegexPattern(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"root": "./static"}
// Invalid regex - unmatched bracket
router.AddRoute("~^/api/[invalid", config)
// Should not add invalid pattern
if len(router.Routes()) != 0 {
t.Error("Should not add invalid regex pattern")
}
}
// ============== Route Matching Tests ==============
func TestRouter_MatchExactRoute(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"return": "200 OK"}
router.AddRoute("=/health", config)
match := router.Match("/health")
if match == nil {
t.Fatal("Expected match for /health")
}
if match.Config["return"] != "200 OK" {
t.Error("Expected return config")
}
if len(match.Params) != 0 {
t.Error("Expected empty params for exact match")
}
}
func TestRouter_MatchExactRouteNoMatch(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"return": "200 OK"}
router.AddRoute("=/health", config)
match := router.Match("/healthcheck")
if match != nil {
t.Error("Exact route should not match /healthcheck")
}
}
func TestRouter_MatchRegexRoute(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"proxy_pass": "http://localhost:9001"}
router.AddRoute("~^/api/v\\d+/", config)
match := router.Match("/api/v1/users")
if match == nil {
t.Fatal("Expected match for /api/v1/users")
}
if match.Config["proxy_pass"] != "http://localhost:9001" {
t.Error("Expected proxy_pass config")
}
}
func TestRouter_MatchRegexRouteWithGroups(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"proxy_pass": "http://localhost:9001"}
router.AddRoute("~^/api/v(?P<version>\\d+)/", config)
match := router.Match("/api/v2/data")
if match == nil {
t.Fatal("Expected match for /api/v2/data")
}
if match.Params["version"] != "2" {
t.Errorf("Expected version=2, got %s", match.Params["version"])
}
}
func TestRouter_MatchCaseInsensitiveRegex(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"root": "./static", "cache_control": "public, max-age=3600"}
router.AddRoute("~*\\.(CSS|JS)$", config)
// Should match lowercase
match1 := router.Match("/styles/main.css")
if match1 == nil {
t.Error("Should match lowercase .css")
}
// Should match uppercase
match2 := router.Match("/scripts/app.JS")
if match2 == nil {
t.Error("Should match uppercase .JS")
}
}
func TestRouter_MatchCaseSensitiveRegex(t *testing.T) {
router := NewRouter()
config := map[string]interface{}{"root": "./static"}
router.AddRoute("~\\.(css)$", config)
// Should match lowercase
match1 := router.Match("/styles/main.css")
if match1 == nil {
t.Error("Should match lowercase .css")
}
// Should NOT match uppercase
match2 := router.Match("/styles/main.CSS")
if match2 != nil {
t.Error("Should not match uppercase .CSS for case-sensitive regex")
}
}
func TestRouter_MatchDefaultRoute(t *testing.T) {
router := NewRouter()
router.AddRoute("=/health", map[string]interface{}{"return": "200 OK"})
router.AddRoute("__default__", map[string]interface{}{"spa_fallback": true})
match := router.Match("/unknown/path")
if match == nil {
t.Fatal("Expected default route match")
}
if match.Config["spa_fallback"] != true {
t.Error("Expected spa_fallback config from default route")
}
}
// ============== Priority Tests ==============
func TestRouter_PriorityExactOverRegex(t *testing.T) {
router := NewRouter()
router.AddRoute("=/api/status", map[string]interface{}{"return": "200 Exact"})
router.AddRoute("~^/api/", map[string]interface{}{"proxy_pass": "http://localhost:9001"})
match := router.Match("/api/status")
if match == nil {
t.Fatal("Expected match")
}
if match.Config["return"] != "200 Exact" {
t.Error("Exact match should have priority over regex")
}
}
func TestRouter_PriorityRegexOverDefault(t *testing.T) {
router := NewRouter()
router.AddRoute("~^/api/", map[string]interface{}{"proxy_pass": "http://localhost:9001"})
router.AddRoute("__default__", map[string]interface{}{"spa_fallback": true})
match := router.Match("/api/v1/users")
if match == nil {
t.Fatal("Expected match")
}
if match.Config["proxy_pass"] != "http://localhost:9001" {
t.Error("Regex match should have priority over default")
}
}
// ============== CreateRouterFromConfig Tests ==============
func TestCreateRouterFromConfig(t *testing.T) {
config := map[string]interface{}{
"regex_locations": map[string]interface{}{
"=/health": map[string]interface{}{
"return": "200 OK",
"content_type": "text/plain",
},
"~^/api/": map[string]interface{}{
"proxy_pass": "http://localhost:9001",
},
"__default__": map[string]interface{}{
"spa_fallback": true,
"root": "./static",
},
},
}
router := CreateRouterFromConfig(config)
// Check exact route
if _, ok := router.ExactRoutes()["/health"]; !ok {
t.Error("Expected /health exact route")
}
// Check regex route
if len(router.Routes()) != 1 {
t.Errorf("Expected 1 regex route, got %d", len(router.Routes()))
}
// Check default route
if router.DefaultRoute() == nil {
t.Error("Expected default route")
}
}
// ============== Static Dir Path Tests ==============
func TestRouter_StaticDirPath(t *testing.T) {
router := NewRouter(WithStaticDir("/var/www/html"))
expected, _ := filepath.Abs("/var/www/html")
actual, _ := filepath.Abs(router.StaticDir())
if actual != expected {
t.Errorf("Expected static dir %s, got %s", expected, actual)
}
}
// ============== Concurrent Access Tests ==============
func TestRouter_ConcurrentAccess(t *testing.T) {
router := NewRouter()
// Add routes concurrently
done := make(chan bool, 10)
for i := 0; i < 10; i++ {
go func(n int) {
router.AddRoute("~^/api/v"+string(rune('0'+n))+"/", map[string]interface{}{
"proxy_pass": "http://localhost:900" + string(rune('0'+n)),
})
done <- true
}(i)
}
// Wait for all goroutines
for i := 0; i < 10; i++ {
<-done
}
// Match routes concurrently
for i := 0; i < 10; i++ {
go func(n int) {
router.Match("/api/v" + string(rune('0'+n)) + "/users")
done <- true
}(i)
}
for i := 0; i < 10; i++ {
<-done
}
}
// ============== Benchmarks ==============
func BenchmarkRouter_MatchExact(b *testing.B) {
router := NewRouter()
router.AddRoute("=/health", map[string]interface{}{"return": "200 OK"})
b.ResetTimer()
for i := 0; i < b.N; i++ {
router.Match("/health")
}
}
func BenchmarkRouter_MatchRegex(b *testing.B) {
router := NewRouter()
router.AddRoute("~^/api/v(?P<version>\\d+)/", map[string]interface{}{"proxy_pass": "http://localhost:9001"})
b.ResetTimer()
for i := 0; i < b.N; i++ {
router.Match("/api/v1/users/123")
}
}
func BenchmarkRouter_MatchWithManyRoutes(b *testing.B) {
router := NewRouter()
// Add many routes
for i := 0; i < 50; i++ {
router.AddRoute("~^/api/v"+string(rune('0'+i%10))+"/service"+string(rune('0'+i/10))+"/",
map[string]interface{}{"proxy_pass": "http://localhost:9001"})
}
router.AddRoute("__default__", map[string]interface{}{"spa_fallback": true})
b.ResetTimer()
for i := 0; i < b.N; i++ {
router.Match("/api/v5/service3/users/123")
}
}