> ## Documentation Index
> Fetch the complete documentation index at: https://ihub.learnille.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Certificate acceptance checks

> Automated regressions, live Infisical checks and PDF layout validation

**Owner:** Backend Engineering

**Last reviewed:** 2026-09-24

**Next review:** 2026-12-24

Read the [certificate lifecycle](https://ihub.learnille.com/engineering/architecture/course-certificates.md) before
running live checks. Use a test Infisical environment with disposable learners and
courses, private storage, Redis and test notification delivery.

## Automated checks

From `server`, with Node 24 and pnpm 9.15.4:

```bash theme={null}
pnpm install --frozen-lockfile
pnpm exec vitest run --config test/vitest.unit.config.ts src/certificate src/course/progress-certificate.spec.ts
pnpm typecheck:ci
```

The controller suite starts a temporary HTTP server on loopback and substitutes
authentication/CASL guards; it checks route protection wiring, validation, envelopes
and binary responses. Service/worker tests mock repositories, storage and queues.
These tests do not establish live JWT, database locking, storage or email delivery.
The renderer tests use real fonts/template and validate reference positions,
wrapping, dates, embedding and explicit rejection of unrenderable text.

## Live API and persistence

1. Apply the certificate migration and build the backend. Confirm
   `dist/assets/certificates` contains the PDF and three fonts. Start the API,
   certificate worker and outbox worker with test configuration from Infisical.
2. Use two distinct learners. Complete a valid enrolled course as learner A.
   Verify automatic generation produces one certificate and one outbox event;
   the notification links to `/my-certificate?certificateId=...`.
3. Call `POST /certificate/course/:courseId/issue` as A. Expect 200 with metadata.
   Repeat it, including simultaneous HTTP requests and a queue job. Verify one
   database row, one issuance event, one ID/date and unchanged PDF SHA-256.
4. List with page/limit and course filters. Empty results must be valid. Get the
   certificate metadata; confirm no storage key or other learners' records leak.
5. Download with A's JWT. Expect raw `application/pdf`, attachment filename and
   `private, no-store`. Compare the downloaded SHA-256 with metadata. Repeat the
   download after changing A's name/course title; bytes must remain unchanged.
6. Attempt list/get/download/issue without JWT, with expired JWT and as B. No
   unauthenticated access; B cannot fetch A's metadata or PDF (404). Passing A's
   user ID/name in the issuance body must never issue as A.
7. Verify new issuance rejects incomplete/empty courses, pending/rejected/refunded
   enrollment, expired access and partial course access. Verify fully purchased
   sections and both supported completion paths can qualify.
8. Test stale/foreign video/lesson IDs and a nearly complete rounded percentage.
   Neither may grant a certificate. Progress writes must reject another learner,
   another course's content and unpurchased sections.
9. Simulate Redis unavailability during final progress: saved progress survives;
   the explicit issue endpoint recovers after infrastructure is healthy. Retry a
   failed generation job and confirm no duplicate issuance.
10. Simulate storage upload failure: no certificate/outbox record should commit.
    Restore storage and retry. Simulate unavailable/corrupted stored content:
    download returns 503 rather than a broken PDF or a silent regeneration.
11. Simulate email failure after issuance: the PDF stays downloadable and the outbox
    retries. Repeated successful issuance requests must not create new emails.
12. Verify migration up on the target schema and unique index enforcement using
    concurrent real database transactions. Back up before any rollback test;
    dropping the table does not remove objects from storage.

## Visual acceptance

Generate reproducible reference, long-text and accented-name examples locally:

```bash theme={null}
pnpm exec ts-node --transpile-only scripts/certificates/render-preview.ts
```

The preview script does not issue database records or send notifications; its
outputs go to `server/tmp/certificate-previews` by default. Render each generated
PDF with Poppler and open it in a browser PDF viewer:

```bash theme={null}
pdftoppm -png -r 150 generated-certificate.pdf certificate-check
pdffonts generated-certificate.pdf
pdftotext generated-certificate.pdf -
```

Compare the supplied filled reference with the same name, title, duration and date.
Check the exact one-page landscape size, retained artwork, name blue, typography,
left alignment and line breaks. Ensure fonts are embedded without renderer warnings
and extracted text contains only the actual date, name and course description.

Repeat with long names, accents, long course titles, unbroken words, missing duration
and a UTC date boundary. No clipping, overlap with the completion sentence, footer
or signature rules, or tiny unreadable text is acceptable. Empty names, unsupported
scripts/emoji and excessive text must fail explicitly with 422, not issue a blank
or corrupted certificate. Name/signature lines remain empty as in the supplied
reference. This feature does not add an electronic or handwritten signature.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.