| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- // Tests for the M13a W5 JWT gate in deliverd-fcm. We test
- // the gate wiring (which routes are protected, the role
- // policy, and the cross-channel safety net). The actual
- // DLQ handlers need a real Postgres pool; those paths are
- // covered by the M8 smoke and the m13a E2E.
- package main
- import (
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base64"
- "encoding/json"
- "io"
- "log/slog"
- "net/http"
- "net/http/httptest"
- "os"
- "testing"
- "time"
- )
- const testSecret = "test-secret-with-32-bytes-min-len-abc"
- func mintTestJWT(t *testing.T, secret, role string) string {
- t.Helper()
- hb, _ := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
- body, _ := json.Marshal(map[string]any{
- "sub": "u-test",
- "tid": "t-test",
- "role": role,
- "typ": "access",
- "iss": "broad-announce",
- "exp": time.Now().Add(15 * time.Minute).Unix(),
- "iat": time.Now().Unix(),
- })
- enc := base64.RawURLEncoding.EncodeToString(hb) + "." + base64.RawURLEncoding.EncodeToString(body)
- mac := hmac.New(sha256.New, []byte(secret))
- mac.Write([]byte(enc))
- return enc + "." + base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
- }
- func discardLogger() *slog.Logger {
- return slog.New(slog.NewTextHandler(io.Discard, nil))
- }
- func TestWireAdminRoutes_NoSecret_Disabled(t *testing.T) {
- os.Unsetenv("BA_AUTHD_JWT_SECRET")
- mux := http.NewServeMux()
- wireAdminRoutes(mux, nil, discardLogger())
- for _, tc := range []struct{ method, path string }{
- {"GET", "/v1/admin/dlq"},
- {"GET", "/v1/admin/dlq/1"},
- } {
- rr := httptest.NewRecorder()
- mux.ServeHTTP(rr, httptest.NewRequest(tc.method, tc.path, nil))
- if rr.Code != http.StatusNotFound {
- t.Errorf("%s %s with no secret: got %d, want 404", tc.method, tc.path, rr.Code)
- }
- }
- }
- func TestWireAdminRoutes_WithSecret_Gated(t *testing.T) {
- t.Setenv("BA_AUTHD_JWT_SECRET", testSecret)
- t.Setenv("BA_AUTHD_ISSUER", "broad-announce")
- mux := http.NewServeMux()
- wireAdminRoutes(mux, nil, discardLogger())
- for _, tc := range []struct{ method, path string }{
- {"GET", "/v1/admin/dlq"},
- {"GET", "/v1/admin/dlq/1"},
- } {
- rr := httptest.NewRecorder()
- mux.ServeHTTP(rr, httptest.NewRequest(tc.method, tc.path, nil))
- // The handler panics because pool is nil. We don't
- // care about the panic body — the test only checks
- // that the gate fired (401) before reaching the
- // handler.
- if rr.Code != http.StatusUnauthorized {
- t.Errorf("%s %s with secret+no-token: status = %d, want 401", tc.method, tc.path, rr.Code)
- }
- }
- }
- func TestAdminDLQ_AnyAuthenticatedUser_CanList(t *testing.T) {
- // Both GETs are read-only. viewer is fine.
- t.Setenv("BA_AUTHD_JWT_SECRET", testSecret)
- mux := http.NewServeMux()
- // Use the real RequireAuth middleware so we test the actual
- // route wiring path. We don't need a real pool — the
- // handler will panic on the first DB call, but the test
- // is satisfied as long as the request was authorized.
- wireAdminRoutes(mux, nil, discardLogger())
- for _, role := range []string{"viewer", "tenant_admin", "super_admin"} {
- t.Run(role, func(t *testing.T) {
- tok := mintTestJWT(t, testSecret, role)
- req := httptest.NewRequest("GET", "/v1/admin/dlq", nil)
- req.Header.Set("Authorization", "Bearer "+tok)
- rr := httptest.NewRecorder()
- func() {
- defer func() {
- // expected: pool is nil
- _ = recover()
- }()
- mux.ServeHTTP(rr, req)
- }()
- // We expect 500 (panic) NOT 401/403 — the gate
- // authorized the request, the handler then died
- // on nil pool.
- if rr.Code == http.StatusUnauthorized || rr.Code == http.StatusForbidden {
- t.Errorf("%s: status = %d, want gate passed (500/panic expected)", role, rr.Code)
- }
- })
- }
- }
|