Test that maintenance mode refuses image requests (closes #71)
check / check (push) Failing after 2m20s
check / check (push) Failing after 2m20s
Failing tests, ahead of the change: with maintenance_mode on, both image routes must answer 503 with a Retry-After header and the JSON error body, while the health check keeps answering 200 and reporting maintenance_mode, and the login page and /metrics keep answering 200. The test server now also builds the health check, which these tests request. Model: opus-5-5
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"sneak.berlin/go/pixa/internal/database"
|
||||
"sneak.berlin/go/pixa/internal/globals"
|
||||
"sneak.berlin/go/pixa/internal/handlers"
|
||||
"sneak.berlin/go/pixa/internal/healthcheck"
|
||||
"sneak.berlin/go/pixa/internal/logger"
|
||||
"sneak.berlin/go/pixa/internal/middleware"
|
||||
)
|
||||
@@ -70,8 +71,15 @@ func newTestServer(t *testing.T) *Server {
|
||||
t.Fatalf("database.New() error = %v", err)
|
||||
}
|
||||
|
||||
hc, err := healthcheck.New(lc, healthcheck.Params{
|
||||
Globals: &globals.Globals{}, Config: cfg, Logger: log, Database: db,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("healthcheck.New() error = %v", err)
|
||||
}
|
||||
|
||||
h, err := handlers.New(lc, handlers.Params{
|
||||
Logger: log, Database: db, Config: cfg,
|
||||
Logger: log, Healthcheck: hc, Database: db, Config: cfg,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("handlers.New() error = %v", err)
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"sneak.berlin/go/pixa/internal/healthcheck"
|
||||
)
|
||||
|
||||
// TestMaintenanceModeRefusesImageRequests verifies that while maintenance
|
||||
// mode is on, both image routes answer 503 Service Unavailable with a
|
||||
// Retry-After header and the JSON error body the image handlers send.
|
||||
func TestMaintenanceModeRefusesImageRequests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := newTestServer(t)
|
||||
s.config.MaintenanceMode = true
|
||||
|
||||
requests := []struct {
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{http.MethodGet, "/v1/image/cdn.example.com/cat.jpg/100x100.jpeg"},
|
||||
{http.MethodHead, "/v1/image/cdn.example.com/cat.jpg/100x100.jpeg"},
|
||||
{http.MethodGet, "/v1/e/token/cat.jpg"},
|
||||
}
|
||||
|
||||
for _, tc := range requests {
|
||||
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
s.ServeHTTP(rec, httptest.NewRequestWithContext(
|
||||
t.Context(), tc.method, tc.path, nil))
|
||||
t.Logf("status %d, body %s", rec.Code, rec.Body.String())
|
||||
|
||||
if rec.Code != http.StatusServiceUnavailable {
|
||||
t.Fatalf("status = %d, want %d",
|
||||
rec.Code, http.StatusServiceUnavailable)
|
||||
}
|
||||
|
||||
retryAfter := rec.Header().Get("Retry-After")
|
||||
|
||||
seconds, err := strconv.Atoi(retryAfter)
|
||||
if err != nil || seconds <= 0 {
|
||||
t.Errorf("Retry-After = %q, want a positive number of seconds",
|
||||
retryAfter)
|
||||
}
|
||||
|
||||
// A HEAD response carries no body.
|
||||
if tc.method == http.MethodHead {
|
||||
return
|
||||
}
|
||||
|
||||
var body struct {
|
||||
Error string `json:"error"`
|
||||
Status int `json:"status"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(rec.Body).Decode(&body)
|
||||
if err != nil {
|
||||
t.Fatalf("body is not JSON: %v", err)
|
||||
}
|
||||
|
||||
if body.Error == "" || body.Status != http.StatusServiceUnavailable ||
|
||||
body.Timestamp == "" {
|
||||
t.Errorf("body = %+v, want an error, status %d and a timestamp",
|
||||
body, http.StatusServiceUnavailable)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMaintenanceModeKeepsOtherRoutes verifies that while maintenance mode
|
||||
// is on, the health check still answers 200 and reports it, since the
|
||||
// image's Docker HEALTHCHECK and upaas read it, and the login page and
|
||||
// /metrics still answer 200.
|
||||
func TestMaintenanceModeKeepsOtherRoutes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
s := newTestServer(t)
|
||||
s.config.MaintenanceMode = true
|
||||
|
||||
// /metrics is routed only when its username is set.
|
||||
s.config.MetricsUsername = "metrics"
|
||||
s.config.MetricsPassword = "metrics-password"
|
||||
s.SetupRoutes()
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
s.ServeHTTP(rec, httptest.NewRequestWithContext(t.Context(),
|
||||
http.MethodGet, "/.well-known/healthcheck.json", nil))
|
||||
t.Logf("health check status %d, body %s", rec.Code, rec.Body.String())
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("health check status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
var health healthcheck.Response
|
||||
|
||||
err := json.NewDecoder(rec.Body).Decode(&health)
|
||||
if err != nil || !health.Maintenance {
|
||||
t.Errorf("health check maintenance_mode = %v (error %v), want true",
|
||||
health.Maintenance, err)
|
||||
}
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
s.ServeHTTP(rec, clientRequest(t, http.MethodGet, nil, firstClient, ""))
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Errorf("login page status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
|
||||
req := httptest.NewRequestWithContext(t.Context(),
|
||||
http.MethodGet, "/metrics", nil)
|
||||
req.SetBasicAuth(s.config.MetricsUsername, s.config.MetricsPassword)
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
s.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Errorf("/metrics status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user