package omni import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" ) func TestAppReturnsStandardResponse(t *testing.T) { app := New() app.GET("/health", func(ctx *Context) error { return ctx.OK(map[string]string{"status": "ok"}) }) recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/health", nil) app.Engine().ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("expected status 200, got %d", recorder.Code) } var response Response if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("decode response: %v", err) } if response.Code != "OK" || response.Message != "success" { t.Fatalf("unexpected response: %+v", response) } if recorder.Header().Get("X-Request-ID") == "" || response.RequestID == "" { t.Fatal("expected request ID in header and response") } if !strings.Contains(recorder.Body.String(), `"requestId"`) || strings.Contains(recorder.Body.String(), `"request_id"`) { t.Fatalf("expected lowerCamelCase response fields: %s", recorder.Body.String()) } } func TestAppReturnsAPIError(t *testing.T) { app := New() app.GET("/users/:id", func(ctx *Context) error { return NotFound("USER_NOT_FOUND", "user was not found") }) recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/users/1", nil) app.Engine().ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Fatalf("expected status 404, got %d", recorder.Code) } var response Response if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("decode response: %v", err) } if response.Code != "USER_NOT_FOUND" || response.Message != "user was not found" { t.Fatalf("unexpected response: %+v", response) } } func TestAppRecoversFromPanic(t *testing.T) { app := New() app.GET("/panic", func(ctx *Context) error { panic("boom") }) recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/panic", nil) app.Engine().ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { t.Fatalf("expected status 500, got %d", recorder.Code) } var response Response if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { t.Fatalf("decode response: %v", err) } if response.Code != "INTERNAL_ERROR" { t.Fatalf("unexpected response: %+v", response) } } func TestAppRunContextShutsDownGracefully(t *testing.T) { app := New(WithShutdownTimeout(time.Second)) ctx, cancel := context.WithCancel(context.Background()) done := make(chan error, 1) go func() { done <- app.RunContext(ctx, "127.0.0.1:0") }() time.Sleep(50 * time.Millisecond) cancel() select { case err := <-done: if err != nil { t.Fatalf("graceful shutdown returned an error: %v", err) } case <-time.After(2 * time.Second): t.Fatal("server did not stop after context cancellation") } } func TestAppClosesResourcesInReverseOrder(t *testing.T) { app := New() var closed []string app.OnShutdown(func() error { closed = append(closed, "database") return nil }).OnShutdown(func() error { closed = append(closed, "logger") return nil }) if err := app.Close(); err != nil { t.Fatalf("close app: %v", err) } if err := app.Close(); err != nil { t.Fatalf("close app twice: %v", err) } if got := strings.Join(closed, ","); got != "logger,database" { t.Fatalf("unexpected close order: %s", got) } } func TestAppUsesConfiguredAddressWhenNoOverrideIsProvided(t *testing.T) { settings := Config{Address: "127.0.0.1:18080", RequestIDHeader: "X-Request-ID", ShutdownTimeout: time.Second} app := New(WithConfig(settings)) if got := app.NewServer("").Addr; got != settings.Address { t.Fatalf("expected configured address %q, got %q", settings.Address, got) } } func TestAppConfigCopiesTrustedProxies(t *testing.T) { proxies := []string{"127.0.0.1"} app := New(WithTrustedProxies(proxies...)) proxies[0] = "10.0.0.0/8" settings := app.Config() if len(settings.TrustedProxies) != 1 || settings.TrustedProxies[0] != "127.0.0.1" { t.Fatalf("trusted proxy config was not copied: %+v", settings.TrustedProxies) } settings.TrustedProxies[0] = "192.0.2.0/24" if app.Config().TrustedProxies[0] != "127.0.0.1" { t.Fatal("App.Config should return a copy") } } func TestAppTrustedProxyClientIPReachesHandlers(t *testing.T) { app := New(WithTrustedProxies("127.0.0.1")) app.GET("/client-ip", func(ctx *Context) error { return ctx.OK(ctx.ClientIP()) }) request := httptest.NewRequest(http.MethodGet, "/client-ip", nil) request.RemoteAddr = "127.0.0.1:443" request.Header.Set("X-Forwarded-For", "198.51.100.7") recorder := httptest.NewRecorder() app.Engine().ServeHTTP(recorder, request) var response Response if err := json.Unmarshal(recorder.Body.Bytes(), &response); err != nil { t.Fatal(err) } if response.Data != "198.51.100.7" { t.Fatalf("expected trusted proxy client IP, got %#v", response.Data) } }