package attest import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "basicstack.de/proofedmesh/services/admin-api/internal/model" ) func sampleRate(t *testing.T) model.Rate { t.Helper() validFrom, _ := time.Parse("2006-01-02", "2026-01-01") return model.Rate{ RateID: "PDR-1", Country: "DE", Currency: "EUR", FullDay: "28.00", PartialDayMin8h: "14.00", ArrivalDay: "14.00", DepartureDay: "14.00", ValidFrom: validFrom, SourceRegulation: "BMF test", CreatedBy: "admin", } } func TestHTTPSigner_HappyPath(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/sign/dsse" { t.Errorf("unexpected path %s", r.URL.Path) } if auth := r.Header.Get("Authorization"); auth != "Bearer test-token" { t.Errorf("auth header: %q", auth) } var req signRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { t.Fatalf("decode: %v", err) } if req.KeyRef != "trust-api/test-key" { t.Errorf("key_ref: %q", req.KeyRef) } if req.Statement.PredicateType != "https://basicstack.de/attestations/rate_change/v1" { t.Errorf("predicate type: %q", req.Statement.PredicateType) } // Fake envelope + content_hash env := Envelope{ PayloadType: "application/vnd.in-toto+json", Payload: "ZmFrZS1wYXlsb2Fk", // "fake-payload" Signatures: []EnvelopeSignature{{KeyID: req.KeyRef, Sig: "ZmFrZS1zaWc"}}, } ch, _ := EnvelopeContentHash(env) resp := signResponse{ Envelope: env, ContentHash: ch, SignedAt: time.Now().UTC(), } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(resp) })) defer srv.Close() s := HTTPSigner{BaseURL: srv.URL, TokenSource: StaticToken("test-token")} stmt, _ := buildStatement(RateChangeEvent{ Kind: ChangeCreate, Rate: sampleRate(t), PerformedBy: PerformedBy{Issuer: "https://auth.basicstack.de", Subject: "admin-sub"}, PerformedAt: time.Now().UTC(), }) env, ch, err := s.SignDSSE(context.Background(), "trust-api/test-key", stmt) if err != nil { t.Fatalf("sign: %v", err) } if !strings.HasPrefix(ch, "sha256:") || len(ch) != 71 { t.Errorf("content_hash shape: %q", ch) } if env.PayloadType != "application/vnd.in-toto+json" { t.Errorf("payload_type: %q", env.PayloadType) } } func TestHTTPSigner_SurfacesServerError(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"error":{"code":"signing_denied","message":"OPA deny: no such key"}}`, http.StatusForbidden) })) defer srv.Close() s := HTTPSigner{BaseURL: srv.URL, TokenSource: StaticToken("t")} stmt, _ := buildStatement(RateChangeEvent{Kind: ChangeCreate, Rate: sampleRate(t)}) _, _, err := s.SignDSSE(context.Background(), "forbidden-key", stmt) if err == nil { t.Fatal("expected error from 403") } if !strings.Contains(err.Error(), "signing_denied") { t.Errorf("expected server message in error, got: %v", err) } } func TestHTTPSigner_RejectsEmptyBaseURL(t *testing.T) { s := HTTPSigner{} _, _, err := s.SignDSSE(context.Background(), "k", Statement{}) if err == nil { t.Fatal("expected error for empty BaseURL") } } func TestBuildStatement_StableRateHash(t *testing.T) { r := sampleRate(t) ev := RateChangeEvent{Kind: ChangeCreate, Rate: r, PerformedAt: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)} a, err := buildStatement(ev) if err != nil { t.Fatal(err) } b, err := buildStatement(ev) if err != nil { t.Fatal(err) } if a.Subject[0].Digest["sha256"] != b.Subject[0].Digest["sha256"] { t.Fatalf("rate hash is not stable:\n a=%s\n b=%s", a.Subject[0].Digest["sha256"], b.Subject[0].Digest["sha256"]) } // Changing the amount changes the hash. r.FullDay = "99.00" c, _ := buildStatement(RateChangeEvent{Kind: ChangeCreate, Rate: r, PerformedAt: ev.PerformedAt}) if a.Subject[0].Digest["sha256"] == c.Subject[0].Digest["sha256"] { t.Fatal("rate hash collided on amount change") } }