— Engineering · testing

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.

Tests
15
across 5 adapters
First run
13 / 2
passed / failed
Bugs caught
2
silent-fail-worthy
Runtime
325 ms
node --test, no framework

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
Why this matters

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 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:

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

Get a free API key →