Files
go-omni/app_test.go

162 lines
4.9 KiB
Go

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