Test metrics auth, CORS preflight, login logging and metrics (closes #79)
check / check (push) Failing after 2s

New tests only. The basic auth in front of /metrics answers 401 with a
challenge without credentials or with a wrong username or password, and
lets the configured ones through. A CORS preflight request gets the same
Access-Control-Allow-Origin as a GET. A POST / whose form carries the
signing key leaves the key out of the log line. The router records
requests, and /metrics reports them, only when the metrics username is
set; that test is the one in internal/server allowed to set up the
process-wide Prometheus registry.

The pinned basicauth-go already compares the password in constant time
with crypto/subtle, so no code changes.

Model: opus-5-5
This commit is contained in:
2026-10-04 07:47:07 +00:00
parent 1616e91a6a
commit 483ada3be0
3 changed files with 240 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
package server
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// TestMetricsRecordRequestsOnlyWithMetricsUsername checks that the router
// records nothing about the requests it serves while no metrics username is
// set, and that with one set /metrics reports a request it served. The two
// halves are one test, run in this order, because the router records into
// the process-wide Prometheus registry, which only one test in this package
// can set up and which keeps what is recorded in it.
func TestMetricsRecordRequestsOnlyWithMetricsUsername(t *testing.T) {
t.Parallel()
// The line /metrics shows once the router has served one GET /robots.txt.
const robotsRequestCount = `http_request_duration_seconds_count{` +
`code="200",handler="/robots.txt",method="GET",service=""} 1`
s := newTestServer(t)
s.ServeHTTP(httptest.NewRecorder(), httptest.NewRequestWithContext(
t.Context(), http.MethodGet, "/robots.txt", nil))
// With no metrics username the router serves no /metrics, so the
// registry is read directly.
registry := httptest.NewRecorder()
promhttp.Handler().ServeHTTP(registry, httptest.NewRequestWithContext(
t.Context(), http.MethodGet, "/metrics", nil))
if strings.Contains(registry.Body.String(), "http_request_duration_seconds") {
t.Errorf("with no metrics username a request was recorded:\n%s",
registry.Body.String())
}
s.config.MetricsUsername = "metricsuser"
s.config.MetricsPassword = "metricspass"
s.SetupRoutes()
s.ServeHTTP(httptest.NewRecorder(), httptest.NewRequestWithContext(
t.Context(), http.MethodGet, "/robots.txt", nil))
req := httptest.NewRequestWithContext(
t.Context(), http.MethodGet, "/metrics", nil)
req.SetBasicAuth("metricsuser", "metricspass")
rec := httptest.NewRecorder()
s.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("GET /metrics status = %d, want %d", rec.Code, http.StatusOK)
}
if !strings.Contains(rec.Body.String(), robotsRequestCount) {
t.Errorf("/metrics does not report the GET /robots.txt served; "+
"want the line %q in:\n%s", robotsRequestCount, rec.Body.String())
}
}