Some checks failed
windows-msi / full-msi-unsigned (push) Has been cancelled
On HTTP 402/429 (and credit/billing error bodies), hold the print job and open https://zap.verae.ai/portal/?tab=billing so the user can reload prepaid balance. Throttle browser opens to once per 10 minutes. Auth failures still use the sign-in flow unchanged.
101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
package veraeapi
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/auth"
|
|
)
|
|
|
|
const digest64 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
|
|
func TestStampVeraeImmediateCertificate(t *testing.T) {
|
|
c := &Client{
|
|
Channel: auth.ChannelVerae,
|
|
VeraeBase: "https://api.example.test",
|
|
VeraeToken: "tok",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
switch {
|
|
case req.URL.Path == "/api/timestamp":
|
|
body := `{"existing":true,"certificate":"QmTestReceipt","receipts":[{"certificate":"QmTestReceipt","digest":"` + digest64 + `","completedAt":"2026-10-03T07:03:17.669231143Z","blockIndex":"113198"}]}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
case strings.HasPrefix(req.URL.Path, "/api/hash/"):
|
|
body := `{"receipts":[{"certificate":"QmTestReceipt","completedAt":"2026-10-03T07:03:17.669231143Z"}]}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
case req.URL.Path == "/api/verify":
|
|
body := `{"valid":true,"timestamp":"2026-10-03T07:03:17.669231143Z","blockIndex":113198}`
|
|
return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil
|
|
default:
|
|
t.Fatalf("path %s", req.URL.Path)
|
|
return nil, nil
|
|
}
|
|
},
|
|
}
|
|
r, err := c.Stamp(digest64, "Doc.pdf")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r.ReceiptID != "QmTestReceipt" {
|
|
t.Fatalf("receipt %q", r.ReceiptID)
|
|
}
|
|
if r.CreatedAt != "2026-10-03T07:03:17.669231143Z" {
|
|
t.Fatalf("created_at %q", r.CreatedAt)
|
|
}
|
|
}
|
|
|
|
func TestStampZapierAttachPdf(t *testing.T) {
|
|
pdf := []byte("%PDF-1.4 cert")
|
|
b64 := base64.StdEncoding.EncodeToString(pdf)
|
|
c := &Client{
|
|
Channel: auth.ChannelZapier,
|
|
ZapierBase: "https://zap.example.test",
|
|
XVeraeToken: "xVerae-test",
|
|
HTTPDo: func(req *http.Request) (*http.Response, error) {
|
|
if req.URL.Path != "/v1/timestamp" {
|
|
t.Fatalf("path %s", req.URL.Path)
|
|
}
|
|
if req.Header.Get("x-api-key") != "xVerae-test" {
|
|
t.Fatal("missing x-api-key")
|
|
}
|
|
body := `{"receipt_id":"cid123","pdfBase64":"` + b64 + `","pdfFilename":"reg.pdf"}`
|
|
return &http.Response{
|
|
StatusCode: 200,
|
|
Body: io.NopCloser(strings.NewReader(body)),
|
|
Header: make(http.Header),
|
|
}, nil
|
|
},
|
|
}
|
|
r, err := c.Stamp(digest64, "Contract.pdf")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r.ReceiptID != "cid123" || string(r.PDF) != string(pdf) {
|
|
t.Fatalf("%+v", r)
|
|
}
|
|
}
|
|
|
|
func TestIsBillingError(t *testing.T) {
|
|
cases := []struct {
|
|
err error
|
|
want bool
|
|
}{
|
|
{nil, false},
|
|
{&HTTPError{Service: "zapier stamp", Status: 402, Body: `{"error":"payment_required"}`}, true},
|
|
{&HTTPError{Service: "zapier stamp", Status: 429, Body: "rate limit"}, true},
|
|
{&HTTPError{Service: "zapier stamp", Status: 403, Body: "billing_failed: no credits"}, true},
|
|
{&HTTPError{Service: "zapier stamp", Status: 500, Body: "out of credit"}, true},
|
|
{&HTTPError{Service: "zapier stamp", Status: 401, Body: "unauthorized"}, false},
|
|
{&HTTPError{Service: "zapier stamp", Status: 500, Body: "upstream timeout"}, false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := IsBillingError(c.err); got != c.want {
|
|
t.Errorf("IsBillingError(%v)=%v want %v", c.err, got, c.want)
|
|
}
|
|
}
|
|
if IsAuthError(&HTTPError{Status: 402, Body: "x"}) {
|
|
t.Fatal("402 must not be auth")
|
|
}
|
|
}
|