ProofedMesh/services/admin-api/internal/tlog/interop_test.go
CTO 69b3f3012f
All checks were successful
validate / Rego (opa fmt + opa test) (push) Successful in 14s
validate / JSON Schema (ajv) (push) Successful in 20s
validate / Go (vet + test) (push) Successful in 20s
services: add transparency-log Go service (ADR-0005 POST /api/v1/log/entries)
- services/transparency-log/pkg/backend: Backend interface with in-memory
  append-log (RFC 6962 Merkle tree) + Trillian wrapper (prod stub).
- Idempotenz-Pflicht: Wiederaufruf mit identischem content_hash liefert
  200 + gleichen log_index + aktuellen inclusion_proof.
- content_hash-Verifikation serverseitig; Mismatch → 409.
- pkg/httpapi: ADR-0005-konformer Handler (201/200/400/404/409/503).
- cmd/transparency-log: minimaler HTTP-Server, memory default,
  --backend trillian als Stub (503).
- Interop-Test in services/admin-api/internal/tlog/interop_test.go
  fährt die echte transparency-log pkg gegen die admin-api
  HTTPSubmitter — Happy-Path, Idempotenz und content_hash-Mismatch
  abgedeckt.
- go test: 10 Tests transparency-log + 2 Interop-Tests admin-api, go
  vet clean in beiden Modulen.

Folge-Subtask: Trillian-Deployment + Keys (siehe ADR-0005 Open Points).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:08:53 +00:00

97 lines
3.2 KiB
Go

package tlog
// Interop test: the real admin-api HTTPSubmitter talks to the real
// transparency-log HTTP handler + in-memory backend from the sibling
// services/transparency-log module. This proves end-to-end that the
// ADR-0005 wire shape agreed on both sides (field names, status codes,
// idempotency semantics, content_hash algorithm) is actually compatible.
//
// The transparency-log module is pulled in via a replace directive in the
// top-level go.mod.
import (
"context"
"net/http/httptest"
"testing"
"basicstack.de/proofedmesh/services/admin-api/internal/attest"
tlbackend "basicstack.de/proofedmesh/services/transparency-log/pkg/backend"
tlhttp "basicstack.de/proofedmesh/services/transparency-log/pkg/httpapi"
)
func TestInterop_HTTPSubmitter_HappyPathAndIdempotency(t *testing.T) {
backendMem := tlbackend.NewMemory()
server := httptest.NewServer((&tlhttp.Server{Backend: backendMem}).Handler())
defer server.Close()
sub := HTTPSubmitter{BaseURL: server.URL, TokenSource: attest.StaticToken("dev")}
env := attest.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "aGVsbG8",
Signatures: []attest.EnvelopeSignature{{KeyID: "k", Sig: "c2ln"}},
}
ch, err := attest.EnvelopeContentHash(env)
if err != nil {
t.Fatalf("hash: %v", err)
}
r1, err := sub.Submit(context.Background(), env, ch)
if err != nil {
t.Fatalf("first submit: %v", err)
}
if r1.LogIndex != 0 {
t.Errorf("first log_index: want 0, got %d", r1.LogIndex)
}
if r1.ContentHash != ch {
t.Errorf("content_hash echoed wrong: %q vs %q", r1.ContentHash, ch)
}
if r1.InclusionProof.TreeSize != 1 {
t.Errorf("tree_size: %d", r1.InclusionProof.TreeSize)
}
// Idempotent repeat.
r2, err := sub.Submit(context.Background(), env, ch)
if err != nil {
t.Fatalf("second submit: %v", err)
}
if r2.LogIndex != r1.LogIndex {
t.Errorf("idempotent log_index drifted: %d -> %d", r1.LogIndex, r2.LogIndex)
}
if r2.InclusionProof.TreeRootHash != r1.InclusionProof.TreeRootHash {
t.Errorf("tree_root drifted without new entries")
}
// Second distinct envelope — tree grows.
envB := env
envB.Payload = "d29ybGQ"
chB, _ := attest.EnvelopeContentHash(envB)
rB, err := sub.Submit(context.Background(), envB, chB)
if err != nil {
t.Fatalf("submit B: %v", err)
}
if rB.LogIndex != 1 {
t.Errorf("second entry log_index: want 1, got %d", rB.LogIndex)
}
if rB.InclusionProof.TreeSize != 2 {
t.Errorf("tree_size after B: %d", rB.InclusionProof.TreeSize)
}
}
func TestInterop_HTTPSubmitter_ContentHashMismatchFails(t *testing.T) {
server := httptest.NewServer((&tlhttp.Server{Backend: tlbackend.NewMemory()}).Handler())
defer server.Close()
sub := HTTPSubmitter{BaseURL: server.URL, TokenSource: attest.StaticToken("dev")}
env := attest.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "aGVsbG8",
Signatures: []attest.EnvelopeSignature{{KeyID: "k", Sig: "c2ln"}},
}
// Wrong hash on purpose. Server must respond 409; the client surfaces
// that as an error.
_, err := sub.Submit(context.Background(), env, "sha256:00000000000000000000000000000000000000000000000000000000deadbeef")
if err == nil {
t.Fatalf("expected error on content_hash mismatch, got nil")
}
}