106 lines
3.4 KiB
Go
106 lines
3.4 KiB
Go
package http
|
|
|
|
import (
|
|
"io"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"chess/internal/server/service"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
func TestClientIPTrustsProxyHeaderOnlyFromTrustedPeers(t *testing.T) {
|
|
// app.Test connections originate from 0.0.0.0.
|
|
for name, test := range map[string]struct {
|
|
opts Options
|
|
want string
|
|
}{
|
|
"no proxy configured": {Options{ProxyHeader: "X-Real-IP"}, "0.0.0.0"},
|
|
"untrusted peer": {Options{TrustedProxies: []string{"10.0.0.1"}, ProxyHeader: "X-Real-IP"}, "0.0.0.0"},
|
|
"trusted peer": {Options{TrustedProxies: []string{"0.0.0.0/32"}, ProxyHeader: "X-Real-IP"}, "203.0.113.9"},
|
|
"trusted, header only": {Options{TrustedProxies: []string{"0.0.0.0"}, ProxyHeader: "X-Real-IP"}, "203.0.113.9"},
|
|
} {
|
|
app := fiber.New(appConfig(test.opts))
|
|
app.Get("/ip", func(c *fiber.Ctx) error { return c.SendString(clientIP(c)) })
|
|
request := httptest.NewRequest("GET", "/ip", nil)
|
|
request.Header.Set("X-Real-IP", "203.0.113.9")
|
|
request.Header.Set("X-Forwarded-For", "198.51.100.1") // never consulted
|
|
response, err := app.Test(request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body, _ := io.ReadAll(response.Body)
|
|
if string(body) != test.want {
|
|
t.Errorf("%s: client IP = %q, want %q", name, body, test.want)
|
|
}
|
|
}
|
|
|
|
// A trusted proxy that omits the header must not collapse clients into one key.
|
|
app := fiber.New(appConfig(Options{TrustedProxies: []string{"0.0.0.0"}, ProxyHeader: "X-Real-IP"}))
|
|
app.Get("/ip", func(c *fiber.Ctx) error { return c.SendString(clientIP(c)) })
|
|
response, err := app.Test(httptest.NewRequest("GET", "/ip", nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body, _ := io.ReadAll(response.Body); string(body) != "0.0.0.0" {
|
|
t.Errorf("missing proxy header: client IP = %q, want peer address", body)
|
|
}
|
|
}
|
|
|
|
func TestIsValidUUIDRequiresCanonicalForm(t *testing.T) {
|
|
for value, want := range map[string]bool{
|
|
"5579b47e-4d3b-4eb3-abbd-846fe94cb955": true,
|
|
"{5579b47e-4d3b-4eb3-abbd-846fe94cb955}": false,
|
|
"urn:uuid:5579b47e-4d3b-4eb3-abbd-846fe94cb955": false,
|
|
"5579b47e4d3b4eb3abbd846fe94cb955": false,
|
|
"not-a-uuid": false,
|
|
} {
|
|
if got := isValidUUID(value); got != want {
|
|
t.Errorf("isValidUUID(%q) = %v, want %v", value, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAPIRoutesAreUnversioned(t *testing.T) {
|
|
svc, err := service.New(nil, []byte("test-secret-test-secret-test-secret"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Route registration only; no handler runs, so no processor is needed.
|
|
app := NewFiberApp(nil, svc, Options{DevMode: true})
|
|
|
|
registered := make(map[string]bool)
|
|
for _, route := range app.GetRoutes(true) {
|
|
registered[route.Method+" "+route.Path] = true
|
|
if strings.HasPrefix(route.Path, "/api/v1") {
|
|
t.Errorf("versioned route still registered: %s %s", route.Method, route.Path)
|
|
}
|
|
}
|
|
for _, want := range []string{
|
|
"GET /health",
|
|
"POST /api/auth/register",
|
|
"POST /api/auth/login",
|
|
"GET /api/auth/me",
|
|
"POST /api/auth/logout",
|
|
"POST /api/games",
|
|
"GET /api/games/:gameId",
|
|
"GET /api/games/:gameId/history",
|
|
"POST /api/games/:gameId/moves",
|
|
"GET /api/users/me/games",
|
|
} {
|
|
if !registered[want] {
|
|
t.Errorf("route %s is not registered", want)
|
|
}
|
|
}
|
|
|
|
response, err := app.Test(httptest.NewRequest("GET", "/api/v1/games/5579b47e-4d3b-4eb3-abbd-846fe94cb955", nil))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if response.StatusCode != fiber.StatusNotFound {
|
|
t.Fatalf("GET /api/v1/... status = %d, want 404", response.StatusCode)
|
|
}
|
|
}
|