kit - six rules, in your own test suite

truecopy/kit

An interface is dodged with a return null. An assertion is not. Drop the conformance kit into your gate with a corpus of your own documents.

selfCheck() { return null } compiles, passes review and ships, and that is exactly what one of the two readers this library came from had, until a check like this one measured it from the outside.

import { checkContract, contractReport, failures, pdfWithText } from 'truecopy/kit';

it('holds the reading contract', async () => {
  const results = await checkContract(
    myReader,
    [
      { name: 'balances', document: aSoundDocument, expected: 'read' },
      { name: 'does not balance', document: aWrongDocument, expected: 'needs-review' }
    ],
    {
      referencePdf: pdfWithText([{ word: '2018', x: 50, y: 700 }]),
      open: openLikeTheApp,
      foreign: [{ name: 'a payslip', document: aPayslip }]
    }
  );

  expect(failures(results)).toEqual([]);
  writeFileSync('contract.txt', contractReport(results));
});

The six rules

  1. The verdict the corpus announces is the verdict returned.
  2. A reading that contradicts its document never comes back as sound.
  3. Everything read is reviewable by the person.
  4. The chain from bytes to records really runs, on a real PDF.
  5. A document without substance is refused, not silently returned empty.
  6. A document of another kind is refused.

Not one of them knows your domain.

Your door, not the kit’s

open is required, and it is your function. Both readers that gave rise to this already had an extractor of their own, and a kit that imposes one stops measuring the project’s reader and starts measuring its own, the one thing a conformance suite must never do.

Rule 6 needs a corpus only you have

A foreign document often carries the shape of what you seek (years, numbers, amounts) without being it. A payslip carries the vocabulary of retirement. An invoice carries dates and totals. Only you know what your reader has to be told apart from, so foreign comes from you.

This is the rule nobody writes and every reader lacks.

A real PDF, not a stub

pdfWithText(words) builds one: a real content stream, a real cross-reference table. Longer than a stub, and that is the point: what a reader must be able to do is open the file the person downloaded, and a test that fakes the PDF engine proves none of it.

pdfWithPages(pages) does the same over several pages, which is the only way to exercise what appears across them: a blank page in the middle, or two pages cut into a different number of columns.

A report you commit

writeFileSync('contract.txt', contractReport(results));

Pact’s idea, and the reason it outlived the assertion helpers that did the same work: the contract lives outside both sides, as an artifact you can version, review and diff. A green suite says the rules held today; a committed report says what held, and a pull request that moves a line makes the change argue for itself.

The counts are in it on purpose. A reading that quietly falls from twenty-six records to twenty-four still passes every rule, and no assertion anywhere is going to notice. The diff is.

Nothing here touches the filesystem: this library runs in a browser, and one that imports fs stops doing that. You write the string where you want it.