ProofedMesh/services/admin-api/internal/attest/http_signer_test.go

127 lines
4 KiB
Go
Raw Normal View History

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")
}
}