// SPDX-License-Identifier: GPL-2.0-or-later // Copyright (C) 2026 Bryan Joshua Pedini package main import ( "io" "log/slog" "net/http" "net/http/httptest" "testing" "github.com/gorilla/mux" ) // newTestRouter builds a WebServer with a minimal Config and wires the real // Routes()/middleware stack, without any DB (nil Store/DB) — for the pure // handler tests that never reach the Store (auth, unknown server_id, etc). func newTestRouter(cfg *Config) (*WebServer, *mux.Router) { ws := &WebServer{ Config: cfg, Logger: slog.New(slog.NewTextHandler(io.Discard, nil)), } r := mux.NewRouter() r.Use(ws.loggingMiddleware) ws.Routes(r) return ws, r } func testConfig() *Config { return &Config{ PDNSAPIKey: "test-secret-key", ListenAddr: ":3000", TablePrefix: "test_pdnsapi_", PDNSServerID: "localhost", DefaultTTL: 120, LogLevel: "info", PUID: 1000, PGID: 1000, } } func TestAuthMiddleware(t *testing.T) { cfg := testConfig() _, r := newTestRouter(cfg) srv := httptest.NewServer(r) defer srv.Close() tests := []struct { name string headerKey string sendHeader bool wantStatus int wantBody bool }{ {"missing key", "", false, http.StatusUnauthorized, true}, {"wrong key", "wrong-key", true, http.StatusUnauthorized, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req, err := http.NewRequest("GET", srv.URL+"/api/v1/servers/localhost/zones", nil) if err != nil { t.Fatal(err) } if tt.sendHeader { req.Header.Set("X-API-Key", tt.headerKey) } resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != tt.wantStatus { t.Fatalf("status = %d, want %d", resp.StatusCode, tt.wantStatus) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } wantBody := `{"error":"Unauthorized"}` + "\n" if string(body) != wantBody { t.Fatalf("body = %q, want %q", string(body), wantBody) } }) } } func TestAuthMiddleware_CorrectKeyPassesThrough(t *testing.T) { // Correct key should reach the handler (which will fail past auth since // there is no Store — but it must NOT be a 401). Unknown server_id also // checked before Store is touched, so use that to prove auth passed. cfg := testConfig() _, r := newTestRouter(cfg) srv := httptest.NewServer(r) defer srv.Close() req, err := http.NewRequest("GET", srv.URL+"/api/v1/servers/not-localhost/zones", nil) if err != nil { t.Fatal(err) } req.Header.Set("X-API-Key", cfg.PDNSAPIKey) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode == http.StatusUnauthorized { t.Fatalf("correct key was rejected with 401") } if resp.StatusCode != http.StatusNotFound { t.Fatalf("status = %d, want %d (unknown server_id, proves auth passed)", resp.StatusCode, http.StatusNotFound) } } func TestUnknownServerID(t *testing.T) { cfg := testConfig() _, r := newTestRouter(cfg) srv := httptest.NewServer(r) defer srv.Close() paths := []struct { method string path string }{ {"GET", "/api/v1/servers/bogus/zones"}, {"GET", "/api/v1/servers/bogus/zones/example.com."}, {"PATCH", "/api/v1/servers/bogus/zones/example.com."}, {"PUT", "/api/v1/servers/bogus/zones/example.com./notify"}, } for _, p := range paths { t.Run(p.method+" "+p.path, func(t *testing.T) { req, err := http.NewRequest(p.method, srv.URL+p.path, nil) if err != nil { t.Fatal(err) } req.Header.Set("X-API-Key", cfg.PDNSAPIKey) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusNotFound { t.Fatalf("status = %d, want 404", resp.StatusCode) } body, _ := io.ReadAll(resp.Body) want := `{"error":"Not Found"}` + "\n" if string(body) != want { t.Fatalf("body = %q, want %q", string(body), want) } }) } } func TestWriteJSONError(t *testing.T) { w := httptest.NewRecorder() writeJSONError(w, http.StatusTeapot, "custom message") if w.Code != http.StatusTeapot { t.Fatalf("status = %d, want %d", w.Code, http.StatusTeapot) } if ct := w.Header().Get("Content-Type"); ct != "application/json" { t.Fatalf("Content-Type = %q, want application/json", ct) } want := `{"error":"custom message"}` + "\n" if w.Body.String() != want { t.Fatalf("body = %q, want %q", w.Body.String(), want) } }