package main import ( "errors" "os" "path/filepath" "testing" "github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/job" "github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/veraeapi" "github.com/VeraeLLC/PrintToVeraeTimeStamp/internal/vlog" ) func pendingEnv(t *testing.T) string { d := t.TempDir() t.Setenv("VERAE_PENDING_DIR", filepath.Join(d, "pending")) t.Setenv("VERAE_NO_NOTIFY", "1") t.Setenv("VERAE_OPEN_ON_COMPLETE", "none") return d } func TestHoldAndRetryAfterSignIn(t *testing.T) { d := pendingEnv(t) src := filepath.Join(d, "job.pdf") _ = os.WriteFile(src, []byte("%PDF-1.4 x"), 0o600) authErr := &veraeapi.HTTPError{Service: "zapier stamp", Status: 401, Body: "unauthorized"} if !isAuthFailure(authErr) || !isAuthFailure(errNoKey) || isAuthFailure(errors.New("timeout")) { t.Fatal("isAuthFailure") } m, err := holdJob(src, stampTarget{Title: "Printed 2026-10-03 192901", OutPath: filepath.Join(d, "chosen.pdf")}, "watch", authErr, true) if err != nil { t.Fatal(err) } if _, err := os.Stat(src); !os.IsNotExist(err) { t.Fatal("source should have been moved") } if l := listPending(); len(l) != 1 || l[0].ID != m.ID { t.Fatalf("list %v", l) } keyOK := false stamp := func(pdf string, tg stampTarget, lj vlog.Job) (job.Result, error) { if tg.OutPath != filepath.Join(d, "chosen.pdf") { t.Errorf("chosen path lost on retry: %q", tg.OutPath) } if !keyOK { return job.Result{}, authErr } return job.Result{ReceiptID: "R1", CertificatePDF: pdf}, nil } // cancelled prompt → job stays pending prompts := 0 acquireKeyHook = func(string) bool { prompts++; return false } defer func() { acquireKeyHook = nil }() if handleAuthFailure("x", stamp) || len(listPending()) != 1 || prompts != 1 { t.Fatalf("cancel: prompts=%d pending=%d", prompts, len(listPending())) } // a second prompt while one is open is suppressed (single prompt) release, ok := promptLock() if !ok { t.Fatal("lock") } if handleAuthFailure("x", stamp) || prompts != 1 { t.Fatalf("second prompt shown while locked (prompts=%d)", prompts) } release() // successful sign-in → retried and removed acquireKeyHook = func(string) bool { prompts++; keyOK = true; return true } if !handleAuthFailure("x", stamp) || len(listPending()) != 0 { t.Fatalf("after sign-in pending=%d", len(listPending())) } } func TestRetryKeepsNonAuthFailures(t *testing.T) { d := pendingEnv(t) for _, n := range []string{"a.pdf", "b.pdf"} { p := filepath.Join(d, n) _ = os.WriteFile(p, []byte("%PDF"), 0o600) if _, err := holdJob(p, stampTarget{Title: n}, "ipp", errNoKey, false); err != nil { t.Fatal(err) } } calls := 0 done, remaining, authFail := retryPending(func(pdf string, tg stampTarget, lj vlog.Job) (job.Result, error) { calls++ if calls == 1 { return job.Result{}, errors.New("network down") } return job.Result{ReceiptID: "R"}, nil }) if done != 1 || remaining != 1 || authFail { t.Fatalf("done=%d remaining=%d authFail=%v", done, remaining, authFail) } } func TestIsBillingFailure(t *testing.T) { pendingEnv(t) bill := &veraeapi.HTTPError{Service: "zapier stamp", Status: 402, Body: `{"error":"payment_required"}`} if !isBillingFailure(bill) { t.Fatal("expected billing failure") } if isAuthFailure(bill) { t.Fatal("402 must not be auth failure") } if isBillingFailure(&veraeapi.HTTPError{Status: 401, Body: "no"}) { t.Fatal("401 is not billing") } }