Content assembly (BuildContent) is separate from PDF drawing (Render), so the actual business logic — what goes into the evidence dossier, in what form, with which mandatory fields — is unit-testable without parsing PDF bytes. BuildContent refuses to produce a dossier missing its evidentiary fields (timestamp token, asset/metadata hash, platform) rather than emitting one with silently empty proof sections. Every dossier carries the "this is not legal advice" disclaimer required by CLAUDE.md's guardrails. Uses github.com/go-pdf/fpdf (actively maintained fork of jung-kurt/ gofpdf, no dependencies beyond the Go stdlib) for rendering. Its core fonts use cp1252 internally, so a small cp1252.map (copied from the fpdf module, embedded via go:embed) drives UnicodeTranslator — German umlauts render correctly without needing an external font file at runtime, keeping Deklarix a single binary. Verified visually with pdftotext/pdfinfo against a generated sample. Tests build a real, structurally valid RFC-3161 token offline (a throwaway self-signed cert + timestamp.Timestamp.CreateResponse), so BuildContent/Render/Generate are fully tested without hitting a real TSA — unlike the network-gated integration test in internal/evidence. Also adds evidence.TimestampTime(), extracted from the parsing logic already used by the TSA client, since the dossier needs to show the timestamped time to a human reader. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
52 lines
1.3 KiB
Go
52 lines
1.3 KiB
Go
package dossier_test
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
|
|
"github.com/netcell-it/deklarix/internal/dossier"
|
|
)
|
|
|
|
func TestRenderProducesValidPDF(t *testing.T) {
|
|
content, err := dossier.BuildContent(validData(t))
|
|
if err != nil {
|
|
t.Fatalf("BuildContent: %v", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := dossier.Render(&buf, content); err != nil {
|
|
t.Fatalf("Render: %v", err)
|
|
}
|
|
|
|
out := buf.Bytes()
|
|
if !bytes.HasPrefix(out, []byte("%PDF-")) {
|
|
t.Fatalf("output does not start with %%PDF- header: %q", out[:min(20, len(out))])
|
|
}
|
|
if !bytes.Contains(out, []byte("%%EOF")) {
|
|
t.Fatal("output does not contain the expected PDF EOF trailer")
|
|
}
|
|
if len(out) < 500 {
|
|
t.Fatalf("output suspiciously small (%d bytes) for a multi-section dossier", len(out))
|
|
}
|
|
}
|
|
|
|
func TestGenerateProducesValidPDF(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
if err := dossier.Generate(&buf, validData(t)); err != nil {
|
|
t.Fatalf("Generate: %v", err)
|
|
}
|
|
if !bytes.HasPrefix(buf.Bytes(), []byte("%PDF-")) {
|
|
t.Fatal("Generate output does not start with the expected PDF header")
|
|
}
|
|
}
|
|
|
|
func TestGeneratePropagatesBuildContentErrors(t *testing.T) {
|
|
data := validData(t)
|
|
data.Submission.Platform = ""
|
|
|
|
var buf bytes.Buffer
|
|
if err := dossier.Generate(&buf, data); err == nil {
|
|
t.Fatal("expected Generate to propagate a BuildContent error, got nil")
|
|
}
|
|
}
|