TEMPLATES · DOCUMENT TESTS

Break the layout before production

A template is designed once against one friendly payload. Then real data hits it forever — the 94-character legal name, the 60-row order, the six-line address, the emoji in a product name. The layout breaks, the API still returns 200, and the broken PDF goes straight to your customer's customer. Document tests generate those payloads automatically from each template's typed contract, render them all, and report what broke.

Start a run

POST /v1/templates/:id/tests returns 202 with a run_id. Poll GET /v1/templates/:id/tests/:run_id until status is passed, failed, or error.

curl -X POST https://doclinth.com/v1/templates/tmpl_…/tests \
  -H "Authorization: Bearer dl_live_YOUR_KEY"
{
  "id": "run_…",
  "status": "failed",
  "variants_total": 11,
  "variants_failed": 1,
  "results": [
    {
      "name": "long_strings",
      "kind": "auto",
      "status": "fail",
      "duration_ms": 1840,
      "diagnostics": {
        "messages": ["Text clipped in .customer-name: The International Consortium for…"]
      },
      "artifact_url": "https://…/tests/…/auto-long_strings.pdf"
    }
  ]
}

A failing variant stores its rendered (TEST-watermarked) PDF for 24 hours; each result carries a short, signed artifact_url so you can see exactly what broke. Passing variants store diagnostics only.

Automatic variants

Every variant is complete — all required fields present — so strict mode is never what fails; only the layout is under test. Each variant mutates one dimension, so a failure points to a specific pressure. If baseline fails, the run aborts: the template itself is broken and everything else would be noise.

VariantPressure
baselineThe template's own sample data, untouched (control).
empty_arraysEvery list becomes empty.
single_rowEvery list is one row.
many_rowsTop-level lists grow to 60 rows — page breaks, repeating headers.
long_stringsText fields grow ~5× — the long company name, the wrapping address.
unbroken_tokenA 120-character unbroken URL/ID that can force horizontal overflow.
unicodeCJK, Arabic (RTL), and emoji mixed into text fields.
missing_optionalsEvery {{#if}}-guarded optional field omitted.
extreme_numbersVery large and negative numbers in numeric fields.
broken_imagesImage URLs point nowhere — does the layout survive a missing logo?

Structured values a helper interprets — a currency code, a barcode payload, a date — are kept valid across every variant (they are contract fields, not free text), so a run tests the layout, not the helper's input validation.

What counts as a failure

Only high-confidence checks — a stress-tester that cries wolf is worse than none:

  • The document is wider than the printable page (horizontal overflow).
  • Text is clipped inside an element that hides its own overflow.
  • An image failed to load.
  • An unresolved {{ template marker survived into the PDF.
  • A page rendered blank.
  • A fixture expectation missed: wrong page count, required text absent, forbidden text present, or the render was too slow.

Fixtures add your own edge cases

Alongside the automatic variants, every named fixture runs with its expectations checked — your real 60-line order, your longest customer name, the payload that broke once in production. Publishing a template auto-seeds a sample fixture, so tests have a baseline from day one.

From the SDK and agents

// Node — @doclinth/sdk
const run = await doclinth.runTemplateTests("tmpl_…");
const done = await doclinth.getTemplateTestRun("tmpl_…", run.id);

Over MCP, the run_template_teststool queues a run and returns a summary with failure diagnostics — so an agent can create a template, stress-test it, and iterate until it's green. get_template includes the latest run summary.

Limits

Test renders do not consume your monthly generation quota — never pay to test safely. Runs are capped per template per day (free 4, paid 20) and rate-limited per account. Failing artifacts expire on the 24-hour purge.

No account? Try one first

Paste a template into the free PDF stress tester — no signup for the first run. It runs the same engine on a capped variant set and returns a shareable report.