Files
Maven/cmd/mavweb/problems_test.go
claude 35c6ff5a71 Make delivery and integration failures explicit
Persist reminder presentations and retry state, atomically complete collapsed deliveries, fall back across away reaches, and block permanent failures visibly (V-715, V-678). Fail closed when enabled integrations lack credentials and keep remote arms explicitly dark (V-691). Give mavweb one sanitized, request-correlated error contract (V-689). Owner explicitly requested direct commits to master.
2026-08-13 02:50:59 +04:00

136 lines
4.0 KiB
Go

package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"go/ast"
"go/parser"
"go/token"
"io"
"log"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func TestWriteProblemSanitizesAndCorrelates(t *testing.T) {
var logs bytes.Buffer
old := problemLogger
problemLogger = logForTest(&logs)
t.Cleanup(func() { problemLogger = old })
const id = "TESTREQUESTID"
r := httptest.NewRequest(http.MethodPost, "/tools", nil)
r = r.WithContext(context.WithValue(r.Context(), requestIDKey{}, id))
w := httptest.NewRecorder()
internal := errors.New("dial unix /run/private/mavend.sock: bearer secret-token")
writeProblem(w, r, http.StatusBadGateway, problemToolsChange, "enable failed", internal)
if w.Code != http.StatusBadGateway {
t.Fatalf("status = %d, want 502", w.Code)
}
if got := w.Header().Get("X-Request-ID"); got != id {
t.Fatalf("X-Request-ID = %q, want %q", got, id)
}
if got := w.Header().Get("Content-Type"); !strings.HasPrefix(got, "application/problem+json") {
t.Fatalf("Content-Type = %q", got)
}
var got problemResponse
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatalf("decode problem: %v", err)
}
if got.Error != "enable failed" || got.Code != problemToolsChange || got.RequestID != id {
t.Fatalf("problem = %+v", got)
}
if strings.Contains(w.Body.String(), "private") || strings.Contains(w.Body.String(), "secret-token") {
t.Fatalf("HTTP response disclosed the wrapped error: %s", w.Body.String())
}
for _, want := range []string{id, string(problemToolsChange), internal.Error()} {
if !strings.Contains(logs.String(), want) {
t.Errorf("server log missing %q: %s", want, logs.String())
}
}
}
func TestRequestIDMiddlewareMintsAndIgnoresCallerID(t *testing.T) {
var seen string
h := withRequestID(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
seen = requestIDFromContext(r.Context())
w.WriteHeader(http.StatusNoContent)
}))
r := httptest.NewRequest(http.MethodGet, "/api/ping", nil)
r.Header.Set("X-Request-ID", "caller-chosen")
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
if seen == "" || seen == "caller-chosen" {
t.Fatalf("request ID = %q; want a server-generated value", seen)
}
if got := w.Header().Get("X-Request-ID"); got != seen {
t.Fatalf("response request ID = %q, context ID = %q", got, seen)
}
}
func TestEcosystemPanelPropagatesRequestID(t *testing.T) {
const id = "WEBREQUESTCORRELATION"
var correlation, requester string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
correlation = r.Header.Get("X-Correlation-ID")
requester = r.Header.Get("X-Requested-By")
_, _ = io.WriteString(w, `[]`)
}))
defer srv.Close()
ctx := context.WithValue(context.Background(), requestIDKey{}, id)
var rows []ecoEntity
if got := getEco(ctx, srv.URL, "/entities", &rows); got != "" {
t.Fatalf("getEco error = %q", got)
}
if correlation != id || requester != "mavweb" {
t.Fatalf("correlation = %q, requester = %q", correlation, requester)
}
}
// The contract is architectural, not a convention people must remember. Keep
// a syntax-level guard so a new handler cannot bypass writeProblem by adding
// another http.Error call.
func TestProductionHandlersUseOneProblemWriter(t *testing.T) {
entries, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() || !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
continue
}
file, err := parser.ParseFile(token.NewFileSet(), filepath.Clean(name), nil, 0)
if err != nil {
t.Fatalf("parse %s: %v", name, err)
}
ast.Inspect(file, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok || sel.Sel.Name != "Error" {
return true
}
pkg, ok := sel.X.(*ast.Ident)
if ok && pkg.Name == "http" {
t.Errorf("%s contains http.Error; use writeProblem", name)
}
return true
})
}
}
func logForTest(w io.Writer) *log.Logger {
return log.New(w, "", 0)
}