verae-mcp-timestamping-serv.../internal/tools/receipt_format_test.go
George Lambert 3ec1f0516b
Some checks failed
test-and-deploy / test (push) Failing after 12m55s
test-and-deploy / deploy-138 (push) Has been cancelled
feat(mcp): optional receipt_format on stamp/verify (text/PDF/JSON/MD/CSV/TSV)
Add receipt_format to stamp and verify. Omitted → text; receipt|PDF-Receipt → PDF;
JSON-Receipt → JSON; MD|MD-Receipt; CSV|CSV-Receipt; TSV|TSV-Receipt.

Stamp always returns receipts[] and adds artifacts[] in the chosen format.
Stdio writes ~/.verae/receipts (0600); public MCP returns content_text/content_base64 only.
Verify accepts inline receipt or receipt_path (stdio), parses id, and re-emits an artifact.

Vendors pdfcert (+ slim statuslang, gofpdf) for CERT-01 PDF. Also lands welcome tool
and email-username helpers already wired into register/public paths.
2026-10-20 16:02:20 -04:00

187 lines
4.8 KiB
Go

package tools
import (
"encoding/base64"
"os"
"path/filepath"
"strings"
"testing"
)
func TestNormalizeReceiptFormat(t *testing.T) {
cases := []struct {
in string
want string
err bool
}{
{"", KindText, false},
{" ", KindText, false},
{"text", KindText, false},
{"receipt", KindPDF, false},
{"PDF-Receipt", KindPDF, false},
{"pdf-receipt", KindPDF, false},
{"PDF", KindPDF, false},
{"JSON-Receipt", KindJSON, false},
{"json", KindJSON, false},
{"MD", KindMD, false},
{"MD-Receipt", KindMD, false},
{"markdown", KindMD, false},
{"CSV", KindCSV, false},
{"CSV-Receipt", KindCSV, false},
{"csv", KindCSV, false},
{"TSV", KindTSV, false},
{"TSV-Receipt", KindTSV, false},
{"tsv", KindTSV, false},
{"docx", "", true},
{"xml", "", true},
}
for _, tc := range cases {
got, err := normalizeReceiptFormat(tc.in)
if tc.err {
if err == nil {
t.Fatalf("normalizeReceiptFormat(%q) expected error", tc.in)
}
continue
}
if err != nil {
t.Fatalf("normalizeReceiptFormat(%q): %v", tc.in, err)
}
if got != tc.want {
t.Fatalf("normalizeReceiptFormat(%q)=%q want %q", tc.in, got, tc.want)
}
}
}
func sampleDTO() ReceiptDTO {
return ReceiptDTO{
ReceiptID: "QmWRJoKLTs56xFQLkhtvSQnWiQvebXGRqRdtbsjodm5HVX",
Hash: "01ec26e0bcdaf0271af7bb7810b1eae22b3220bc3c59b9cc7abfa1519edcbae1",
Scope: "global",
CreatedAt: "2026-09-23T14:46:08Z",
Merkle: "merkle-root-example",
}
}
func TestBuildArtifactFormats(t *testing.T) {
dto := sampleDTO()
home := t.TempDir()
t.Setenv("HOME", home)
for _, kind := range []string{KindText, KindJSON, KindMD, KindCSV, KindTSV, KindPDF} {
art, err := buildArtifact(dto, kind, true)
if err != nil {
t.Fatalf("buildArtifact(%s): %v", kind, err)
}
if art.Format != kind {
t.Fatalf("format=%q want %q", art.Format, kind)
}
if art.MediaType != mediaTypeForKind(kind) {
t.Fatalf("%s media_type=%q", kind, art.MediaType)
}
if art.Path == "" {
t.Fatalf("%s: expected path under HOME", kind)
}
if _, err := os.Stat(art.Path); err != nil {
t.Fatalf("%s path missing: %v", kind, err)
}
switch kind {
case KindPDF:
if art.ContentB64 == "" {
t.Fatal("pdf: empty content_base64")
}
b, err := base64.StdEncoding.DecodeString(art.ContentB64)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(string(b), "%PDF") {
t.Fatalf("pdf does not start with %%PDF")
}
case KindJSON:
if !strings.Contains(art.ContentText, `"format": "JSON-Receipt"`) {
t.Fatalf("json missing format marker: %s", art.ContentText[:min(80, len(art.ContentText))])
}
if !strings.Contains(art.ContentText, dto.ReceiptID) {
t.Fatal("json missing receipt_id")
}
case KindMD:
if !strings.Contains(art.ContentText, "| receipt_id |") {
t.Fatalf("md missing table header: %s", art.ContentText)
}
case KindCSV:
if !strings.HasPrefix(art.ContentText, "receipt_id,") {
t.Fatalf("csv header: %q", art.ContentText)
}
if !strings.Contains(art.ContentText, dto.ReceiptID) {
t.Fatal("csv missing id")
}
case KindTSV:
if !strings.HasPrefix(art.ContentText, "receipt_id\t") {
t.Fatalf("tsv header: %q", art.ContentText)
}
case KindText:
if !strings.Contains(art.ContentText, "receipt_id: "+dto.ReceiptID) {
t.Fatal("text missing receipt_id line")
}
}
}
}
func TestBuildArtifactPublicNoDisk(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
art, err := buildArtifact(sampleDTO(), KindJSON, false)
if err != nil {
t.Fatal(err)
}
if art.Path != "" {
t.Fatalf("public must not set path, got %q", art.Path)
}
ents, _ := os.ReadDir(filepath.Join(home, ".verae", "receipts"))
if len(ents) != 0 {
t.Fatalf("public wrote %d files under receipts", len(ents))
}
if art.ContentText == "" {
t.Fatal("expected content_text")
}
}
func TestParseReceiptIDRoundTrip(t *testing.T) {
dto := sampleDTO()
cases := []string{KindText, KindJSON, KindMD, KindCSV, KindTSV}
for _, kind := range cases {
art, err := buildArtifact(dto, kind, false)
if err != nil {
t.Fatal(err)
}
id, err := parseReceiptIDFromBody(kind, art.ContentText)
if err != nil {
t.Fatalf("parse %s: %v\nbody:\n%s", kind, err, art.ContentText)
}
if id != dto.ReceiptID {
t.Fatalf("parse %s: got %q want %q", kind, id, dto.ReceiptID)
}
}
// PDF via base64
art, err := buildArtifact(dto, KindPDF, false)
if err != nil {
t.Fatal(err)
}
id, err := parseReceiptIDFromBody(KindPDF, art.ContentB64)
if err != nil {
t.Fatalf("parse pdf: %v", err)
}
if id != dto.ReceiptID {
t.Fatalf("parse pdf: got %q want %q", id, dto.ReceiptID)
}
}
func TestAttachArtifacts(t *testing.T) {
out := ReceiptsOut{Receipts: []ReceiptDTO{sampleDTO()}}
got, err := attachArtifacts(out, KindCSV, false)
if err != nil {
t.Fatal(err)
}
if len(got.Artifacts) != 1 || got.Artifacts[0].Format != KindCSV {
t.Fatalf("%+v", got.Artifacts)
}
}