123 lines
4.1 KiB
Go
123 lines
4.1 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"basicstack.de/proofedmesh/services/admin-api/internal/attest"
|
|
"basicstack.de/proofedmesh/services/admin-api/internal/store"
|
|
)
|
|
|
|
type recordingSigner struct {
|
|
calls int
|
|
lastKey string
|
|
}
|
|
|
|
func (s *recordingSigner) SignDSSE(_ context.Context, keyRef string, stmt attest.Statement) (attest.Envelope, string, error) {
|
|
s.calls++
|
|
s.lastKey = keyRef
|
|
env := attest.Envelope{
|
|
PayloadType: "application/vnd.in-toto+json",
|
|
Payload: "ZmFrZS1wYXlsb2Fk",
|
|
Signatures: []attest.EnvelopeSignature{{KeyID: keyRef, Sig: "ZmFrZS1zaWc"}},
|
|
}
|
|
ch, _ := attest.EnvelopeContentHash(env)
|
|
return env, ch, nil
|
|
}
|
|
|
|
type recordingSubmitter struct {
|
|
calls int
|
|
last attest.LogReceipt
|
|
}
|
|
|
|
func (s *recordingSubmitter) Submit(_ context.Context, env attest.Envelope, ch string) (attest.LogReceipt, error) {
|
|
s.calls++
|
|
r := attest.LogReceipt{
|
|
LogIndex: int64(s.calls),
|
|
ContentHash: ch,
|
|
LoggedAt: time.Now().UTC(),
|
|
}
|
|
s.last = r
|
|
return r, nil
|
|
}
|
|
|
|
func TestCreate_WithAttestation_AttachesHash(t *testing.T) {
|
|
sig := &recordingSigner{}
|
|
sub := &recordingSubmitter{}
|
|
deps := Dependencies{
|
|
Store: store.NewMemory(),
|
|
Now: func() time.Time { return time.Date(2026, 10, 25, 12, 0, 0, 0, time.UTC) },
|
|
Attest: &attest.Orchestrator{Signer: sig, Submitter: sub, KeyRef: "trust-api/test-key"},
|
|
}
|
|
srv := httptest.NewServer(newMux(deps))
|
|
defer srv.Close()
|
|
|
|
body := `{"country":"DE","currency":"EUR","full_day":"28.00","partial_day_min8h":"14.00","arrival_day":"14.00","departure_day":"14.00","valid_from":"2026-01-01T00:00:00Z","source_regulation":"BMF 2026"}`
|
|
req, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("expected 201, got %d", resp.StatusCode)
|
|
}
|
|
var out map[string]map[string]any
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
ref, _ := out["rate"]["rate_change_attestation_ref"].(string)
|
|
if !strings.HasPrefix(ref, "sha256:") || len(ref) != 71 {
|
|
t.Fatalf("rate_change_attestation_ref not attached as sha256 hash: %q", ref)
|
|
}
|
|
if sig.calls != 1 || sub.calls != 1 {
|
|
t.Fatalf("expected one signer + one submitter call, got signer=%d submitter=%d", sig.calls, sub.calls)
|
|
}
|
|
if sig.lastKey != "trust-api/test-key" {
|
|
t.Errorf("key ref not passed through: %q", sig.lastKey)
|
|
}
|
|
}
|
|
|
|
type failingSubmitter struct{}
|
|
|
|
func (failingSubmitter) Submit(_ context.Context, _ attest.Envelope, _ string) (attest.LogReceipt, error) {
|
|
return attest.LogReceipt{}, &net503Error{}
|
|
}
|
|
|
|
type net503Error struct{}
|
|
|
|
func (*net503Error) Error() string { return "tlog: status 503: trillian down" }
|
|
|
|
func TestCreate_WithFailingTLog_RowPending(t *testing.T) {
|
|
deps := Dependencies{
|
|
Store: store.NewMemory(),
|
|
Now: func() time.Time { return time.Date(2026, 10, 25, 12, 0, 0, 0, time.UTC) },
|
|
Attest: &attest.Orchestrator{Signer: &recordingSigner{}, Submitter: failingSubmitter{}, KeyRef: "k"},
|
|
}
|
|
srv := httptest.NewServer(newMux(deps))
|
|
defer srv.Close()
|
|
|
|
body := `{"country":"DE","currency":"EUR","full_day":"28.00","partial_day_min8h":"14.00","arrival_day":"14.00","departure_day":"14.00","valid_from":"2026-01-01T00:00:00Z","source_regulation":"BMF 2026"}`
|
|
req, _ := http.NewRequest("POST", srv.URL+"/admin/per-diem-rates/", strings.NewReader(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer resp.Body.Close()
|
|
// Create still succeeds: Row exists, attestation pending.
|
|
if resp.StatusCode != http.StatusCreated {
|
|
t.Fatalf("expected 201 even when tlog is down, got %d", resp.StatusCode)
|
|
}
|
|
var out map[string]map[string]any
|
|
_ = json.NewDecoder(resp.Body).Decode(&out)
|
|
if _, hasRef := out["rate"]["rate_change_attestation_ref"]; hasRef {
|
|
if v := out["rate"]["rate_change_attestation_ref"]; v != nil {
|
|
t.Fatalf("expected no attestation ref when tlog is down, got %v", v)
|
|
}
|
|
}
|
|
}
|