diff options
Diffstat (limited to 'internal/api/server_hardening_test.go')
| -rw-r--r-- | internal/api/server_hardening_test.go | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/internal/api/server_hardening_test.go b/internal/api/server_hardening_test.go new file mode 100644 index 0000000..bb9f24c --- /dev/null +++ b/internal/api/server_hardening_test.go @@ -0,0 +1,119 @@ +package api + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/Fuwn/plutia/internal/config" + "github.com/Fuwn/plutia/internal/storage" + "github.com/Fuwn/plutia/internal/types" +) + +func TestResolveRateLimitPerIP(t *testing.T) { + store, err := storage.OpenPebble(t.TempDir()) + if err != nil { + t.Fatalf("open pebble: %v", err) + } + defer store.Close() + if err := store.PutState(types.StateV1{Version: 1, DID: "did:plc:alice", DIDDocument: []byte(`{"id":"did:plc:alice"}`), ChainTipHash: "tip", LatestOpSeq: 1, UpdatedAt: time.Now().UTC()}); err != nil { + t.Fatalf("put state: %v", err) + } + + cfg := config.Default() + cfg.RequestTimeout = 10 * time.Second + cfg.RateLimit.ResolveRPS = 1 + cfg.RateLimit.ResolveBurst = 1 + cfg.RateLimit.ProofRPS = 1 + cfg.RateLimit.ProofBurst = 1 + h := NewServer(cfg, store, nil, nil).Handler() + + req1 := httptest.NewRequest(http.MethodGet, "/did/did:plc:alice", nil) + req1.RemoteAddr = "203.0.113.7:12345" + rr1 := httptest.NewRecorder() + h.ServeHTTP(rr1, req1) + if rr1.Code != http.StatusOK { + t.Fatalf("first request status: got %d want %d", rr1.Code, http.StatusOK) + } + + req2 := httptest.NewRequest(http.MethodGet, "/did/did:plc:alice", nil) + req2.RemoteAddr = "203.0.113.7:12345" + rr2 := httptest.NewRecorder() + h.ServeHTTP(rr2, req2) + if rr2.Code != http.StatusTooManyRequests { + t.Fatalf("second request status: got %d want %d", rr2.Code, http.StatusTooManyRequests) + } +} + +func TestStatusIncludesBuildInfo(t *testing.T) { + store, err := storage.OpenPebble(t.TempDir()) + if err != nil { + t.Fatalf("open pebble: %v", err) + } + defer store.Close() + + h := NewServer(config.Default(), store, nil, nil, WithBuildInfo(BuildInfo{ + Version: "v0.1.0", + Commit: "abc123", + BuildDate: "2026-02-26T00:00:00Z", + GoVersion: "go1.test", + })).Handler() + + req := httptest.NewRequest(http.MethodGet, "/status", nil) + rr := httptest.NewRecorder() + h.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("status code: got %d want %d", rr.Code, http.StatusOK) + } + + var payload map[string]any + if err := json.Unmarshal(rr.Body.Bytes(), &payload); err != nil { + t.Fatalf("decode status: %v", err) + } + build, ok := payload["build"].(map[string]any) + if !ok { + t.Fatalf("missing build section: %v", payload) + } + if got := build["version"]; got != "v0.1.0" { + t.Fatalf("unexpected build version: %v", got) + } + if got := build["commit"]; got != "abc123" { + t.Fatalf("unexpected build commit: %v", got) + } +} + +func TestMetricsExposeRequiredSeries(t *testing.T) { + store, err := storage.OpenPebble(t.TempDir()) + if err != nil { + t.Fatalf("open pebble: %v", err) + } + defer store.Close() + + h := NewServer(config.Default(), store, nil, nil).Handler() + req := httptest.NewRequest(http.MethodGet, "/metrics", nil) + rr := httptest.NewRecorder() + h.ServeHTTP(rr, req) + if rr.Code != http.StatusOK { + t.Fatalf("metrics status: got %d want %d", rr.Code, http.StatusOK) + } + body, _ := io.ReadAll(rr.Body) + text := string(body) + for _, metric := range []string{ + "ingest_ops_total", + "ingest_ops_per_second", + "ingest_lag_ops", + "verify_failures_total", + "checkpoint_duration_seconds", + "checkpoint_sequence", + "disk_bytes_total", + "did_count", + } { + if !strings.Contains(text, metric) { + t.Fatalf("metrics output missing %q", metric) + } + } +} |