ProofedMesh/services/admin-api/internal/attest/orchestrator_test.go
CTO Agent cacdf7e286
Some checks failed
validate / Rego (opa fmt + opa test) (push) Successful in 13s
validate / JSON Schema (ajv) (push) Has been cancelled
validate / Go (vet + test) (push) Has been cancelled
attest+tlog: ADR-0005 Signing/TLog API contract, HTTP clients + orchestrator in admin-api (go vet clean, 11 new tests pass)
2026-10-25 17:51:33 +00:00

131 lines
3.8 KiB
Go

package attest
import (
"context"
"errors"
"testing"
"time"
"basicstack.de/proofedmesh/services/admin-api/internal/model"
)
type fakeSigner struct {
err error
wantKeyRef string
tookStmt Statement
returnEnv Envelope
returnHash string
}
func (f *fakeSigner) SignDSSE(_ context.Context, keyRef string, stmt Statement) (Envelope, string, error) {
if f.err != nil {
return Envelope{}, "", f.err
}
f.tookStmt = stmt
if f.wantKeyRef != "" && keyRef != f.wantKeyRef {
return Envelope{}, "", errors.New("unexpected keyRef")
}
return f.returnEnv, f.returnHash, nil
}
type fakeSubmitter struct {
err error
gotEnv Envelope
gotHash string
returnRct LogReceipt
}
func (f *fakeSubmitter) Submit(_ context.Context, env Envelope, ch string) (LogReceipt, error) {
if f.err != nil {
return LogReceipt{}, f.err
}
f.gotEnv = env
f.gotHash = ch
if f.returnRct.ContentHash == "" {
f.returnRct.ContentHash = ch
}
return f.returnRct, nil
}
func TestOrchestrator_HappyPath(t *testing.T) {
env := Envelope{PayloadType: "application/vnd.in-toto+json", Payload: "aGVsbG8"}
ch, _ := EnvelopeContentHash(env)
s := &fakeSigner{wantKeyRef: "k", returnEnv: env, returnHash: ch}
sub := &fakeSubmitter{returnRct: LogReceipt{LogIndex: 7, ContentHash: ch, LoggedAt: time.Now()}}
orch := Orchestrator{Signer: s, Submitter: sub, KeyRef: "k"}
gotHash, receipt, err := orch.SignAndLog(context.Background(), RateChangeEvent{
Kind: ChangeCreate,
Rate: sampleRateMinimal(),
PerformedBy: PerformedBy{Issuer: "https://auth.basicstack.de", Subject: "u", SessionID: "s"},
PerformedAt: time.Now(),
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if gotHash != ch {
t.Errorf("hash mismatch: %q vs %q", gotHash, ch)
}
if receipt.LogIndex != 7 {
t.Errorf("receipt log_index: %d", receipt.LogIndex)
}
if sub.gotHash != ch {
t.Errorf("submitter saw different hash: %q vs %q", sub.gotHash, ch)
}
if s.tookStmt.PredicateType != "https://basicstack.de/attestations/rate_change/v1" {
t.Errorf("predicateType in statement: %q", s.tookStmt.PredicateType)
}
}
func TestOrchestrator_SignerErrorStops(t *testing.T) {
orch := Orchestrator{
Signer: &fakeSigner{err: errors.New("bao down")},
Submitter: &fakeSubmitter{},
KeyRef: "k",
}
_, _, err := orch.SignAndLog(context.Background(), RateChangeEvent{Rate: sampleRateMinimal()})
if err == nil {
t.Fatal("expected error")
}
}
func TestOrchestrator_SubmitterErrorPropagates(t *testing.T) {
env := Envelope{PayloadType: "application/vnd.in-toto+json"}
ch, _ := EnvelopeContentHash(env)
orch := Orchestrator{
Signer: &fakeSigner{wantKeyRef: "k", returnEnv: env, returnHash: ch},
Submitter: &fakeSubmitter{err: errors.New("trillian down")},
KeyRef: "k",
}
gotHash, _, err := orch.SignAndLog(context.Background(), RateChangeEvent{Rate: sampleRateMinimal()})
if err == nil {
t.Fatal("expected error")
}
// The hash from the signer is still returned so a reconciler can retry.
if gotHash != ch {
t.Errorf("expected signer hash to leak through on submitter error, got %q", gotHash)
}
}
func TestOrchestrator_RejectsMisconfiguration(t *testing.T) {
cases := []Orchestrator{
{Signer: nil, Submitter: &fakeSubmitter{}, KeyRef: "k"},
{Signer: &fakeSigner{}, Submitter: nil, KeyRef: "k"},
{Signer: &fakeSigner{}, Submitter: &fakeSubmitter{}, KeyRef: ""},
}
for i, o := range cases {
if _, _, err := o.SignAndLog(context.Background(), RateChangeEvent{}); err == nil {
t.Errorf("case %d: expected error for misconfigured orchestrator", i)
}
}
}
func sampleRateMinimal() model.Rate {
t, _ := 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: t, SourceRegulation: "test", CreatedBy: "admin",
}
}