Files
coredns-powerdns-api-wrapper/auth_test.go
2026-07-10 23:53:43 +02:00

175 lines
4.4 KiB
Go

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