The tray and AirDrop helper now live in Verae NetPrint.app so notifications can start, /api/stamp uses the same client as Print-Job and refuses mock receipts, and the default printer name includes the hostname with an mDNS rename when that name is taken. Co-authored-by: George Lambert <marchon@gmail.com>
214 lines
8.1 KiB
Go
214 lines
8.1 KiB
Go
package job
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/auth"
|
|
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/veraeapi"
|
|
)
|
|
|
|
func TestRunRejectsMockReceiptOutsideTestBuilds(t *testing.T) {
|
|
dir := t.TempDir()
|
|
src := filepath.Join(dir, "src.pdf")
|
|
if err := os.WriteFile(src, []byte("%PDF-1.4 hello"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client := &veraeapi.Client{
|
|
Channel: auth.ChannelZapier,
|
|
ZapierBase: "https://zap.test",
|
|
XVeraeToken: "xVerae-x",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
body := `{"receipt_id":"mock-cert-abc","merkle":"42","created_at":"2026-10-05T00:00:00Z"}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
},
|
|
}
|
|
_, err := Run(Options{SourcePDF: src, OutDir: filepath.Join(dir, "out"), DocumentName: "Contract.pdf", Channel: auth.ChannelZapier, Client: client})
|
|
if err == nil || !strings.Contains(err.Error(), "mock receipt") {
|
|
t.Fatal(err)
|
|
}
|
|
real := &veraeapi.Client{
|
|
Channel: auth.ChannelZapier,
|
|
ZapierBase: "https://zap.test",
|
|
XVeraeToken: "xVerae-x",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
body := `{"receipt_id":"bafyRealBlock","merkle":"42"}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
},
|
|
}
|
|
if _, err := Run(Options{SourcePDF: src, OutDir: filepath.Join(dir, "real"), DocumentName: "Contract.pdf", Channel: auth.ChannelZapier, Client: real}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestSanitizeAndStem(t *testing.T) {
|
|
if SanitizeReceiptID("bafy/xyz!") != "bafy-xyz" {
|
|
t.Fatal(SanitizeReceiptID("bafy/xyz!"))
|
|
}
|
|
if StemFromPath("/tmp/My Contract (1).pdf", "") != "My-Contract-1" {
|
|
t.Fatal(StemFromPath("/tmp/My Contract (1).pdf", ""))
|
|
}
|
|
}
|
|
|
|
func TestRunZapierWritesNamedFiles(t *testing.T) {
|
|
dir := t.TempDir()
|
|
src := filepath.Join(dir, "src.pdf")
|
|
if err := os.WriteFile(src, []byte("%PDF-1.4 hello"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out := filepath.Join(dir, "out")
|
|
pdf := []byte("%PDF-1.4 certificate-bytes")
|
|
b64 := base64.StdEncoding.EncodeToString(pdf)
|
|
|
|
client := &veraeapi.Client{
|
|
Channel: auth.ChannelZapier,
|
|
ZapierBase: "https://zap.test",
|
|
XVeraeToken: "xVerae-x",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
body := `{"receipt_id":"bafyReceipt99","pdfBase64":"` + b64 + `"}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
},
|
|
}
|
|
|
|
res, err := Run(Options{
|
|
SourcePDF: src,
|
|
OutDir: out,
|
|
DocumentName: "Contract.pdf",
|
|
Channel: auth.ChannelZapier,
|
|
Client: client,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
wantCert := filepath.Join(out, "Contract-verae-timestamp-bafyReceipt99.pdf")
|
|
if res.CertificatePDF != wantCert {
|
|
t.Fatalf("cert path %s want %s", res.CertificatePDF, wantCert)
|
|
}
|
|
if _, err := os.Stat(res.OriginalPDF); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, _ := os.ReadFile(res.CertificatePDF)
|
|
if !strings.HasPrefix(string(got), "%PDF") {
|
|
t.Fatal("certificate is not a PDF")
|
|
}
|
|
if !strings.Contains(string(got), "Verae Timestamp Registration Document") {
|
|
t.Fatal("missing branded title")
|
|
}
|
|
if strings.Contains(string(got), "mcp.verae.ai") || strings.Contains(string(got), "via MCP") {
|
|
t.Fatal("certificate must not mention mcp.verae.ai / via MCP")
|
|
}
|
|
if _, err := os.Stat(res.ReceiptJSON); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRunVeraeRendersLocalCert(t *testing.T) {
|
|
dir := t.TempDir()
|
|
src := filepath.Join(dir, "memo.pdf")
|
|
_ = os.WriteFile(src, []byte("%PDF-1.4 memo"), 0o600)
|
|
out := filepath.Join(dir, "out")
|
|
client := &veraeapi.Client{
|
|
Channel: auth.ChannelVerae,
|
|
VeraeBase: "https://api.test",
|
|
VeraeToken: "jwt",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
body := `{"existing":true,"certificate":"cidLocal1","receipts":[{"certificate":"cidLocal1","completedAt":"2026-10-03T12:00:00Z","blockIndex":"99"}]}`
|
|
if strings.HasPrefix(req.URL.Path, "/api/hash/") {
|
|
body = `{"receipts":[{"certificate":"cidLocal1","completedAt":"2026-10-03T12:00:00Z","blockIndex":"99"}]}`
|
|
}
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
},
|
|
}
|
|
res, err := Run(Options{SourcePDF: src, OutDir: out, Channel: auth.ChannelVerae, Client: client})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(res.CertificatePDF, "memo-verae-timestamp-cidLocal1.pdf") {
|
|
t.Fatal(res.CertificatePDF)
|
|
}
|
|
b, _ := os.ReadFile(res.CertificatePDF)
|
|
if !strings.HasPrefix(string(b), "%PDF") {
|
|
t.Fatal("not a pdf")
|
|
}
|
|
}
|
|
|
|
func TestRunNeverOverwritesSameName(t *testing.T) {
|
|
dir := t.TempDir()
|
|
out := filepath.Join(dir, "out")
|
|
n := 0
|
|
client := &veraeapi.Client{
|
|
Channel: auth.ChannelZapier,
|
|
ZapierBase: "https://zap.test",
|
|
XVeraeToken: "xVerae-x",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
n++
|
|
// same receipt id every time, to also exercise cert-name collisions
|
|
body := `{"receipt_id":"bafySame"}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
},
|
|
}
|
|
var results []Result
|
|
for i, content := range []string{"%PDF-1.4 first", "%PDF-1.4 second", "%PDF-1.4 third"} {
|
|
src := filepath.Join(dir, "src"+string(rune('a'+i))+".pdf")
|
|
_ = os.WriteFile(src, []byte(content), 0o600)
|
|
res, err := Run(Options{SourcePDF: src, OutDir: out, DocumentName: "Printed 2026-10-03 192901.pdf", Channel: auth.ChannelZapier, Client: client})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
results = append(results, res)
|
|
}
|
|
wantOrig := []string{"Printed 2026-10-03 192901.pdf", "Printed 2026-10-03 192901-2.pdf", "Printed 2026-10-03 192901-3.pdf"}
|
|
wantJSON := []string{"Printed-2026-10-03-192901.verae.json", "Printed-2026-10-03-192901-2.verae.json", "Printed-2026-10-03-192901-3.verae.json"}
|
|
wantCert := []string{"Printed-2026-10-03-192901-verae-timestamp-bafySame.pdf", "Printed-2026-10-03-192901-2-verae-timestamp-bafySame.pdf", "Printed-2026-10-03-192901-3-verae-timestamp-bafySame.pdf"}
|
|
for i, r := range results {
|
|
if filepath.Base(r.OriginalPDF) != wantOrig[i] || filepath.Base(r.ReceiptJSON) != wantJSON[i] || filepath.Base(r.CertificatePDF) != wantCert[i] {
|
|
t.Fatalf("job %d: %s | %s | %s", i, filepath.Base(r.OriginalPDF), filepath.Base(r.ReceiptJSON), filepath.Base(r.CertificatePDF))
|
|
}
|
|
}
|
|
if b, _ := os.ReadFile(results[0].OriginalPDF); string(b) != "%PDF-1.4 first" {
|
|
t.Fatalf("first original overwritten: %q", b)
|
|
}
|
|
// a pre-existing cert with the target name is never overwritten
|
|
p, err := writeExclusive(out, "Printed-2026-10-03-192901-verae-timestamp-bafySame", ".pdf", []byte("x"))
|
|
if err != nil || filepath.Base(p) != "Printed-2026-10-03-192901-verae-timestamp-bafySame-2.pdf" {
|
|
t.Fatal(p, err)
|
|
}
|
|
}
|
|
|
|
func TestRunWithChosenPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
src := filepath.Join(dir, "spool.pdf")
|
|
_ = os.WriteFile(src, []byte("%PDF-1.4 new"), 0o600)
|
|
pick := filepath.Join(dir, "My Contract v2.pdf")
|
|
_ = os.WriteFile(pick, []byte("old"), 0o600) // user confirmed "Replace"
|
|
client := &veraeapi.Client{Channel: auth.ChannelZapier, ZapierBase: "https://zap.test", XVeraeToken: "xVerae-x",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"receipt_id":"bafyR1"}`)), Header: make(http.Header)}, nil
|
|
}}
|
|
for i := 0; i < 2; i++ {
|
|
res, err := Run(Options{SourcePDF: src, OutPath: pick, Channel: auth.ChannelZapier, Client: client})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.OriginalPDF != pick {
|
|
t.Fatal(res.OriginalPDF)
|
|
}
|
|
wantCert := "My Contract v2-verae-timestamp-bafyR1.pdf"
|
|
wantJSON := "My Contract v2.verae.json"
|
|
if i == 1 {
|
|
wantCert = "My Contract v2-verae-timestamp-bafyR1-2.pdf"
|
|
wantJSON = "My Contract v2-2.verae.json"
|
|
}
|
|
if filepath.Base(res.CertificatePDF) != wantCert || filepath.Base(res.ReceiptJSON) != wantJSON {
|
|
t.Fatalf("run %d: %s %s", i, filepath.Base(res.CertificatePDF), filepath.Base(res.ReceiptJSON))
|
|
}
|
|
}
|
|
if b, _ := os.ReadFile(pick); string(b) != "%PDF-1.4 new" {
|
|
t.Fatal("original not written to chosen path")
|
|
}
|
|
}
|