Two bugs the test harness caught in its first run
We just shipped a Chrome extension that auto-fills customer records in five different Norwegian accounting platforms. Five platforms means five adapters, each mapping Nordic Data's normalised company shape onto the platform's specific API. Five chances for a field name to drift.
So we wrote a test harness. 15 tests, ~60 lines per platform, runs in 325 ms. The tests are not theoretical: each assertion exists because a regression would silently break a customer write. The first run was 13 passes and 2 fails. Both fails were real bugs. Both would have shipped broken customer records to live accounting systems.
This post is what the two bugs were, how the harness caught them, and why writing tests before the first OAuth integration is non-negotiable.
The harness
One file. node:test built-in runner. Zero framework dependencies. Each adapter has 2-3 tests verifying the mapped record matches the platform's expected JSON shape. Plus 5 cross-adapter invariants (writable fields declared, doesn't throw on minimal fixture, stable displayName, etc.). The fixture is real data from the live API for Equinor (org 923609016).
// test/adapters.test.ts — extract
import { test } from "node:test";
import assert from "node:assert/strict";
test("visma: countryId is ISO 2-letter, not country name", () => {
const rec = vismaAdapter.mapToRecord(equinorFixture, "customer");
assert.equal(rec.mainAddress.countryId, "NO");
assert.equal(rec.mainAddress.country, undefined);
});
The fixture is what catches the bugs. We hardcoded the actual API response shape — not a stylised "Norway" placeholder — because the API returns Norwegian names by default and we needed to know whether our code handled that.
Bug 1: Visma country mapping
The expected output: mainAddress.countryId === "NO". Visma uses ISO 3166-1 alpha-2.
The first run:
not ok 4 - visma: countryId is ISO 2-letter, not country name
expected: 'NO'
actual: 'Norge'
The bug was here in src/adapters/visma.ts:
// Before
const countryId = (address.country || "Norway") === "Norway"
? "NO"
: address.country;
It maps the English string "Norway" to "NO". The live API returns the Norwegian word "Norge". Our condition never matched, so we fell through to address.country and wrote "Norge" into Visma's countryId field. Visma would have rejected the write with a 400 error citing "unknown country".
The fix uses the country_code field that the Nordic Data API already provides (always ISO 2-letter when known), with a name-based fallback for the common Nordic variants:
// After
const countryId = address.country_code
|| ({ "Norge": "NO", "Norway": "NO",
"Sverige": "SE", "Sweden": "SE",
"Danmark": "DK", "Denmark": "DK" })[address.country || ""]
|| address.country;
Bug 2: PowerOffice country mapping
Same shape. Same wrong code. Caught by the same test pattern.
not ok 8 - poweroffice: maps Equinor with vatNumber as the orgnr carrier
expected: 'NO'
actual: 'Norge'
The PowerOffice adapter had a copy-pasted version of the same mapping logic. Both got fixed in one commit. After re-running:
$ npx tsx --test test/adapters.test.ts
ok 1 - tripletex: maps Equinor to a valid Customer record
ok 2 - tripletex: bankruptcy surfaces in description
ok 3 - tripletex: does NOT use the deprecated `notes` field
ok 4 - visma: maps Equinor to a CustomerUpdateDto-compatible record
ok 5 - visma: bankruptcy surfaces in note field
ok 6 - fiken: maps to Contact (single entity for customer/supplier)
ok 7 - fiken: supplier record sets supplier=true, customer=false
ok 8 - poweroffice: maps Equinor with vatNumber as the orgnr carrier
ok 9 - 24sevenoffice: uses PascalCase field names
ok 10 - 24sevenoffice: Supplier vs Customer is in Type field
...
# pass 15
# fail 0
# duration_ms 325.5844
If we'd shipped without these tests, the first customer to install the extension and look up a Norwegian company on Visma or PowerOffice would have seen the lookup appear to succeed (the UI fields fill in), then later see the actual write to Visma fail with an opaque server error, or worse — succeed with a broken country value that downstream systems reject. Bugs like this are the exact reason a "looks-like-it-works" extension can ruin trust permanently in a small market like Norway.
What the harness deliberately doesn't test
The tests cover what we can verify from the OpenAPI spec: field names exist, value types are correct, sentinel transforms work. They do NOT cover:
- The OAuth flow. No mock server. The OAuth handshake either works against the platform's auth endpoint or it doesn't; mocks of that have a low truth-to-effort ratio.
- The DOM selectors. The browser extension's selectors target real platform UIs that we don't have. Selector verification requires a real account on each platform.
- Integration round-trips. No test fires a real API call against Tripletex/Visma. Those need credentials and would slow down the test loop. We'll add contract tests against test-environment endpoints later.
The harness is the cheapest test that catches the highest-frequency bug class: field-name drift. Adding integration tests against real APIs is on the roadmap once we have OAuth client credentials.
The test that almost didn't get written
The reflex on a side project is to skip tests and ship fast. The reasoning is "I'll know if it breaks." For your-own-frontend code, that reasoning is fine. For an integration adapter that writes to a customer's accounting system, it is not. The customer can't tell whether the write succeeded with bad data or failed silently — they see what the UI shows them, and the UI shows what we filled. If we wrote "Norge" into Visma's countryId field, Visma's UI would show "Norge" until the customer tried to actually save the record, at which point the API would 400 and the customer would lose 15 minutes figuring out what we did wrong.
The harness exists because the cost of one undetected field-mapping drift is permanently losing one customer in a market where every customer matters. Spending 4 hours writing the harness up front is the cheapest insurance available.
What's next
Three follow-ups already on the list:
- Contract tests against test-environment APIs. Once we have OAuth credentials for Tripletex and Visma developer tenants, fire real writes against test instances and assert response codes.
- Selector smoke tests. When we have a real account on each platform, take a screenshot of every customer-creation form's DOM and assert our selectors match. Add to CI once the extension is in active use.
- Property-based tests for the orgnr checksum. The mod-11 check digit is implemented in three places (CLI, server, extension). They should agree on every valid org number and disagree on every invalid one.
fast-checkcan verify this with a few lines.
The full test file is in the open-source repo at github.com/nordic-data/connect. The two bug fixes are visible in the same commit as the tests. If you build integration adapters for any platform that exposes an API, please write the tests first. The next bug it catches will save your reputation, not just your time.
Build something on Nordic Data?
The API is free up to 5,000 requests/month. CLI: npx nordic-data lookup 923609016