Repository navigation
Verify the open API surface with live contract tests that need no credentials #13
Description
Activity
- addedNoReleaseDocumentation, maintenance or CI/CD - no version bumpDocumentation, maintenance or CI/CD - no version bumpfeatureNew featureNew feature
on Aug 2, 2026 MariusStorhaug commented
on Aug 2, 2026 MemberAuthorMore actionsPartly unblocked. The endpoints in the first release need no credential, so a live suite covering
/ping,/version, and/v1/publicData/listcan run in CI unconditionally, with no secret to provision and nothing to skip. That is worth doing on its own, and it gives the repository a real-network contract check from the first release.The gated part — a key in a repository secret, skipped when absent — applies only to the authenticated surface and belongs with #15. Worth splitting this issue along that line when it is picked up.
MariusStorhaug commented
on Aug 2, 2026 MemberAuthorMore actionsConcrete evidence for this, surfaced during PR #16 (the key-free 0.1.0 surface).
A
LastModifiedtimezone fix passed the unit suite the whole time yet was worse against the live API. Cause:ConvertFrom-Jsonin PowerShell 7 already deserializes an ISO-8601Ztimestamp into a[datetime]withKind=Utc, so by the time the command sees the field it is aDateTime, not a string. The mock returned a raw wire string the transport never actually produces, so the test encoded an assumption about the boundary instead of the boundary's real behaviour. Only a live call caught it (fixed in0f4d628onbuild-lovdata-module).Generalised lesson worth baking into the live suite's rationale: when a mock stands in for a deserialization boundary, it must return what the deserializer actually returns, not what the wire format looks like.
Two things that lower the bar for landing at least part of this now:
- The entire 0.1.0 surface is key-free.
Test-LovdataConnection(/ping),Get-LovdataApiVersion(/version), andGet-/Save-LovdataPublicDataset(/v1/publicData/*) all work with no credential, so a live contract suite over them can run in CI unconditionally, with no secret, not just as the skipped-when-absent path described above. The key-gated portion can stay deferred to the authenticated surface (Add the authenticated Lovdata API surface in a later release #15). - End-to-end already verified by hand against the live service during the 🚀 [Feature]: Norwegian legal data, no account needed #16 review:
/ping-> true;/version->lovdata-api 2026-07-31-1613; all four packages listed withKind=Utc; a real download oflovtidend-avd1-2026.tar.bz2produced 1,315,035 bytes matching the reportedSizeBytes; overwrite guard refused the second attempt. That is the contract a key-free live suite would lock in.
- The entire 0.1.0 surface is key-free.
MariusStorhaug commented
on Aug 2, 2026 MemberAuthorMore actionsWhy this matters, with evidence rather than principle
The case for this issue was originally "the module has no live coverage, which is a gap". Building
0.1.0produced something stronger: three defects shipped past a green test suite, and all three failed the same way. In each case a mock encoded what the boundary was expected to produce rather than what it actually produces, so the test passed while the code was wrong.1.
LastModifiedlost its UTC offset.ConvertFrom-Jsonin PowerShell 7 already deserializes an ISO-8601Ztimestamp into a[datetime]withKind=Utc. The command cast it to a string first, which rendered it through the current culture and dropped the offset, so the reparse producedKind=Unspecified. The same package then reported a different day depending on the runner's timezone.The mock returned a raw JSON string — a wire format the transport never emits, because deserialization happens before the command sees the value. The test asserted
Should -BeOfType [datetime], which passed either way. Only a live call exposed it, and the value was worse after the "fix" than before it.2. Change references carried the wrong law's date. The parser computed one date for the whole
lastChangedByfield and stamped it on every reference in it. A law amended by two others recorded the first amendment's date against the second, and dropped the per-referencefra <date>entirely. That is wrong date information about legal amendments, in published data, in a corpus consulted precisely because Lovdata is authoritative.The fixture had a single-reference field. The corpus has 33 multi-reference ones in the acts alone. The defect was invisible to the test because with one reference the field-wide date coincides with the correct answer.
3. The same defect silently affected a second field.
changesToDocumentsshares that parser. It never showed the symptom, because those fields are bare RefID lists with no free-text date for the old code to latch onto — so the computed field-wide date usually happened to be right. Same latent bug, masked by the data shape, and it would have surfaced the first time Lovdata changed that field's format.The generalisable rule
A mock standing in for a deserialization boundary must return what the deserializer returns, not what the wire format looks like. Every one of the three above is an instance of violating that.
Unit tests cannot enforce it, because the mock is the assumption. Only something that touches the real boundary can. That is what this issue provides.
And it costs nothing to run
Every endpoint in
0.1.0is key-free:/ping,/version,/v1/publicData/list,/v1/publicData/get/{filename}. A live contract suite over those needs no secret, no repository configuration, and nothing to skip — it can run unconditionally in CI, on forks, and on contributor pull requests. The gated portion applies only to the authenticated surface in #15 and should be split along that line.What such a suite should assert
Contract, not data values, so it does not break when Lovdata publishes an ordinary update:
/v1/publicData/listreturns entries carryingfilename,description,sizeBytesandlastModified, andlastModifieddeserializes to a UTC[datetime]— the exact property defect 1 violated.sizeBytesarrives as a string and converts to a whole number.- A downloaded package's byte length equals the
sizeBytesthe listing reported. This was verified by hand during0.1.0—lovtidend-avd1-2026.tar.bz2returned exactly 1 315 035 bytes — and it is the single strongest end-to-end assertion available without a key. /pinganswers, and/versionreturnsname,timestampandrevision.- Rate-limit headers are present on an unauthenticated response, since the module surfaces them.
Verified during
0.1.0by hand; the point of this issue is that none of it is verified by anything that runs on its own.- changed the title
[-]Add live API integration tests gated behind a repository secret[/-][+]Verify the open API surface with live contract tests that need no credentials[/+]on Aug 2, 2026 MariusStorhaug commented
on Aug 2, 2026 MemberAuthorMore actionsRetitled from "Add live API integration tests gated behind a repository secret".
The original title contradicted what this issue turned into. It described a suite blocked on provisioning a secret, which would invite deferring the work until someone arranges credentials. That blocker does not exist: every endpoint in
0.1.0is key-free, so the suite that would have caught all three defects recorded above runs unconditionally — no secret, no repository configuration, nothing to skip, and green on forks and contributor pull requests.Scope after the retitle:
- This issue covers live contract tests over the open surface:
/ping,/version,/v1/publicData/list,/v1/publicData/get/{filename}. No credentials, so nothing gates it. - The credential-gated portion moves to Add the authenticated Lovdata API surface in a later release #15, alongside the authenticated surface it would test. There is nothing to gate until that surface exists.
The title is what gets scanned in a backlog, and the old one implied a dependency that was never real.
- This issue covers live contract tests over the open surface:
Every test in the module runs against a mocked transport, because no Lovdata API key is available to the repository yet. That keeps CI green and fast, but it also means nothing in the pipeline proves the module can actually talk to Lovdata. The shape of several responses is documented as free-form in the OpenAPI document, so the field names the module maps are informed guesses until a real response confirms them. A user is the first one to find out if a guess was wrong.
Request
User impact
A change that breaks the module against the live API — a renamed field, a changed status code, a different error shape — currently ships green. Contract coverage against the real service turns that into a failed build instead of a bug report.
Acceptance criteria
References
apirole; contact api@lovdata.notests/BeforeAll.ps1andtests/AfterAll.ps1phasesTechnical decisions
Separation: Integration tests live in their own discovered test entry under
tests/, so a failure names the live suite rather than being mixed into unit results. The unit suites keep mocking the transport and stay the default local experience.Gating: Skip on the absence of the key rather than failing. A
-Skipcondition on the container is preferred over an earlyreturn, so skipped tests are reported as skipped and the gap is visible in the run summary.Secret plumbing: The key reaches the workflow through the
TestDatasecret input on the Process-PSModule workflow, the same mechanismPSModule/GitHubuses for its test credentials.Scope: Read-only endpoints only. Nothing in the live suite may create, change, or delete anything in Lovdata.
Contract assertions: Assert the fields the module maps — that a legal source carries an identifier and a description, that the error body for a rejected key carries a message — rather than asserting on data values that Lovdata may legitimately change.
Unauthenticated coverage:
/pingand/versionneed no key, so the live checks that use them can run unconditionally once #8 lands. That gives the pipeline some real-network coverage even with no secret configured.Depends on: the load-bearing core in #2, and an API key being provisioned as a repository secret. The pull request targets
build-lovdata-module.Implementation plan
Core changes
TestDatainput in.github/workflows/Process-PSModule.ymltests/that skips when the key is absentVerification