Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-11
88 changes: 88 additions & 0 deletions openspec/changes/migrate-pdok-to-openconnector/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Design: migrate-pdok-to-openconnector

> Cross-repo architecture, the canonical PostalAddress schema shape, the full
> caching and write-through flow, and the three-layer architecture all live in
> the umbrella spec:
> `hydra/openspec/changes/shared-pdok-via-openconnector/design.md`
>
> This design document covers only procest-specific implementation details.

## Shim File Structure

The shim replaces `src/services/pdokService.js` in-place. The file structure:

```js
// SPDX-License-Identifier: EUPL-1.2
// SPDX-FileCopyrightText: 2024 Conduction B.V.
// ...

const BASE_URL = '/index.php/apps/openconnector/api/pdok'

// Network-calling functions (delegate to openconnector)
export async function suggest(q) { ... }
export async function lookup(id) { ... }
export async function free(q, rows = 10, start = 0) { ... }
export async function reverse(lat, lng) { ... }

// Pure utility functions (no network calls)
export function extractCoordinates(wkt) { ... }
export function formatAddress(obj) { ... }
```

The first four functions use the same `axios` or `fetch` pattern already used elsewhere
in procest (check existing service files to confirm the pattern). All four catch HTTP
errors: 503 responses resolve with `null` and surface the `message_key` to the caller;
404 responses (openconnector not installed) surface an inline warning and allow the form
to continue.

## extractCoordinates Placement

`extractCoordinates(wkt)` stays in the shim. OR-sourced addresses already have parsed
GeoJSON `location` objects; callers receiving OR objects don't need it. But the function
must remain available for any procest caller passing raw WKT from a non-OR source.
Removing it would be a breaking change.

Contract: `extractCoordinates("POINT(4.88525 52.37025)")` returns
`{lat: 52.37025, lng: 4.88525}` — note that PDOK WKT has longitude first, and this
function swaps the order to return `{lat, lng}` for caller convenience.

## Test Approach

### Unit Tests (`npm run test`)

Existing unit tests that mock `fetch()` on `https://api.pdok.nl` are updated to mock
`fetch()` on `/index.php/apps/openconnector/api/pdok/*` instead. No new test framework
is introduced. Tests cover:
- `suggest`, `lookup`, `free`, `reverse` call the correct openconnector URL
- 503 response resolves with `null` and surfaces `message_key`
- 404 response (openconnector absent) surfaces inline warning, no exception
- `extractCoordinates("POINT(4.88525 52.37025)")` returns `{lat: 52.37025, lng: 4.88525}`
- `formatAddress` remains a pure function (no network calls)

### E2E Smoke Test

A Playwright or manual smoke test verifies that address autocomplete still works
end-to-end via the openconnector shim: typing a partial address returns suggestions,
selecting one populates all address fields. Run in the dev environment (localhost:3000)
with openconnector installed.

### openconnector-absent Test

Verify that when openconnector is not installed (404 on the endpoint), the shim
surfaces an inline warning on the address field without breaking form submission.

## Seed Data

No new OR schemas or registers are introduced by this change — those live in the
`openregister/openspec/changes/add-addresses-register/` sibling spec.

For the E2E and integration test environment, the two valid address fixtures from
`openregister/tests/fixtures/addresses/` (Conduction HQ, Tilburg Stadhuis) are loaded
into the test environment OR instance so E2E tests can assert against OR-stored
addresses without requiring a live PDOK or openconnector connection. The woonplaats
fixture is intentionally excluded from E2E seed (it lacks the fields required for
address form population).

The Playwright test environment bootstrap script loads both fixtures via OR's API before
running tests. This is a test-environment concern only — no seed data is added to the
procest app itself.
69 changes: 69 additions & 0 deletions openspec/changes/migrate-pdok-to-openconnector/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Migrate PDOK to openconnector

## Why

This change implements the `[procest]` subset of the Hydra-level umbrella change
`shared-pdok-via-openconnector`. Today, `src/services/pdokService.js` calls
`https://api.pdok.nl` directly from the browser, violating ADR-022. Centralizing
PDOK access in openconnector brings server-side caching, rate-limit handling, circuit
breaker, observability, and write-through to the OR `addresses` register — all at zero
cost to procest callers because the shim preserves identical exported function signatures.
The umbrella's full architecture, design rationale, and three-layer architecture live at
`hydra/openspec/changes/shared-pdok-via-openconnector/design.md`.

## What

- Replace `src/services/pdokService.js` with a thin shim that exports the same six
functions (`suggest`, `lookup`, `free`, `reverse`, `extractCoordinates`,
`formatAddress`) — no procest caller requires modification.
- The first four functions delegate to
`GET /index.php/apps/openconnector/api/pdok/{suggest|lookup|free|reverse}`.
- `extractCoordinates` and `formatAddress` remain pure utility functions with no
network calls.
- 503 handling: shim resolves with `null` and surfaces the `message_key` to the caller.
- When openconnector is not installed (404), the shim surfaces an inline warning without
breaking form submission.
- Updated unit tests: mocks changed from `api.pdok.nl` to `/index.php/apps/openconnector/api/pdok/*`.
- E2E smoke test confirming address autocomplete works end-to-end via the shim.
- Seed data bootstrap confirming OR fixtures are loadable in the test environment.

## Capabilities

### Modified Capabilities

- `pdok-consumer-via-openconnector`: procest's `src/services/pdokService.js` now routes
address lookup/suggest/free/reverse calls through
`/index.php/apps/openconnector/api/pdok/*` instead of calling `api.pdok.nl` directly,
while preserving the existing caller surface.

## Affected Repos

procest only.

## References

- Umbrella spec:
`hydra/openspec/changes/shared-pdok-via-openconnector/`
- Umbrella design (canonical architecture, shim contract):
`hydra/openspec/changes/shared-pdok-via-openconnector/design.md`
- openconnector PDOK adapter (the endpoint this shim targets):
`openconnector/openspec/changes/add-pdok-adapter/`
— openconnector MUST ship before procest's shim calls are fully functional;
each task in this change describes only procest's contracts so the shim can be
built and tests written without waiting for openconnector to deploy.
- OR addresses register:
`openregister/openspec/changes/add-addresses-register/`
— referenced for E2E fixture loading (OR fixtures used in integration test
environment to avoid live PDOK dependency).

## Out of Scope

- The openconnector PDOK adapter — covered by sibling spec
`openconnector/openspec/changes/add-pdok-adapter/`.
- The OR `addresses` register definition — covered by sibling spec
`openregister/openspec/changes/add-addresses-register/`.
- procest's existing `pdok-integration` spec body — NOT modified by this change;
a separate follow-up per-app spec will update it to reference the new consumer
contract (see umbrella design, Phase 2).
- decidesk / zaakafhandelapp / pipelinq migration — separate per-app specs that
reference the openconnector adapter once it ships.
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
status: draft
---
# procest PDOK Consumer via openconnector

## Purpose

This spec defines the procest-side requirements for routing all PDOK Locatieserver
access through the openconnector PDOK adapter instead of calling `api.pdok.nl`
directly from the browser. The consumer contract is intentionally minimal: procest
replaces one service file with a thin shim and updates its tests — all other address
logic remains unchanged.

**Upstream dependency:** The openconnector PDOK adapter
(`openconnector/openspec/changes/add-pdok-adapter/`) provides the endpoints this shim
calls. The shim can be built and tested independently against the documented endpoint
contract; full end-to-end functionality requires openconnector to be installed and
the OR `addresses` register to exist.

**Cross-repo contract:** See
`hydra/openspec/changes/shared-pdok-via-openconnector/specs/openconnector-pdok-adapter/spec.md`
(Requirement: Frontend Shim Contract) for the umbrella-level requirement. This spec
scopes requirements to the procest repo only.

## ADDED Requirements

### Requirement: Frontend Shim Routes All PDOK Calls Through openconnector

`src/services/pdokService.js` MUST NOT contain any direct calls to `https://api.pdok.nl`.
The shim MUST export six functions — `suggest(q)`, `lookup(id)`, `free(q)`, `reverse(lat,
lng)`, `extractCoordinates(wkt)`, `formatAddress(obj)` — with identical signatures to
the current implementation. The first four MUST delegate to
`GET /index.php/apps/openconnector/api/pdok/{suggest|lookup|free|reverse}`. The last two
MUST remain pure utility functions with no network calls.

#### Scenario: suggest call reaches openconnector instead of api.pdok.nl

- GIVEN the procest frontend is loaded and openconnector is installed
- WHEN a procest component calls `suggest("Lauriergracht")`
- THEN the shim SHALL send
`GET /index.php/apps/openconnector/api/pdok/suggest?q=Lauriergracht`
- AND SHALL return the normalized suggestion array to the caller
- AND SHALL NOT call `https://api.pdok.nl` directly

#### Scenario: extractCoordinates remains a pure utility function

- GIVEN the shim is loaded
- WHEN a procest component calls `extractCoordinates("POINT(4.88525 52.37025)")`
- THEN the shim SHALL return `{lat: 52.37025, lng: 4.88525}`
- AND this function SHALL NOT make any network request

#### Scenario: No direct api.pdok.nl reference remains in the file

- GIVEN the shim file has been replaced
- WHEN the file content is inspected
- THEN no reference to `api.pdok.nl` SHALL be present anywhere in
`src/services/pdokService.js`

### Requirement: Caller Signatures Are Preserved

All six exported function signatures MUST be identical to those of the replaced
`pdokService.js` — `suggest(q)`, `lookup(id)`, `free(q)`, `reverse(lat, lng)`,
`extractCoordinates(wkt)`, `formatAddress(obj)`. No procest component, view, or test
that currently imports from `pdokService.js` SHALL require modification as a result of
this change.

#### Scenario: All six functions are exported with unchanged signatures

- GIVEN the shim file is loaded in a test environment
- WHEN each of the six exports is inspected
- THEN all six SHALL be present and callable with their original signatures
- AND no existing procest caller SHALL need modification

#### Scenario: Existing procest tests pass unchanged against the shim

- GIVEN the shim is in place and `npm run test` is executed
- WHEN the test runner completes
- THEN all tests SHALL pass with zero failures
- AND no test file SHALL reference `api.pdok.nl`

### Requirement: Graceful Handling When openconnector Returns 503 or Is Absent

The shim MUST handle two degraded conditions without throwing uncaught exceptions or
breaking form submission:

1. **openconnector returns HTTP 503** (PDOK unavailable, circuit open): the affected
function SHALL resolve with `null` and SHALL surface the `message_key` from the
response body to the caller for display.
2. **openconnector not installed (HTTP 404)**: the shim SHALL surface an inline warning
to the address field component and SHALL allow form submission to continue.

#### Scenario: 503 response resolves with null and surfaces message_key

- GIVEN openconnector returns HTTP 503 with
`{"error": "pdok_unavailable", "message_key": "pdok.unavailable"}`
- WHEN the shim receives the 503 response on any network-calling function
- THEN the function SHALL resolve with `null`
- AND the `message_key` value `"pdok.unavailable"` SHALL be available to the caller
for display
- AND no uncaught exception SHALL reach the form component

#### Scenario: openconnector absent surfaces warning without blocking form

- GIVEN openconnector is not installed and the endpoint returns HTTP 404
- WHEN a procest component calls `suggest("Tilburg")`
- THEN the shim SHALL surface an inline warning on the address field
- AND form submission SHALL remain possible (address field is non-blocking)
65 changes: 65 additions & 0 deletions openspec/changes/migrate-pdok-to-openconnector/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Tasks: migrate-pdok-to-openconnector

> This change implements the `[procest]` subset of the Hydra-level umbrella
> `shared-pdok-via-openconnector`. The full architecture, design rationale,
> normalized response schema, and migration story live in the umbrella.
> See `hydra/openspec/changes/shared-pdok-via-openconnector/design.md`.

## Tasks

### PR-1. Replace pdokService.js with shim (S)

- [x] PR-1.1 Replace `src/services/pdokService.js` with a thin shim that exports
`suggest(q)`, `lookup(id)`, `free(q)`, `reverse(lat, lng)`, `extractCoordinates(wkt)`,
and `formatAddress(obj)`. The first four delegate to
`GET /index.php/apps/openconnector/api/pdok/{endpoint}` using the existing `axios` or
`fetch` pattern already in use in procest (check other service files). `extractCoordinates`
and `formatAddress` remain pure utility functions with no network calls.
- **Acceptance:** No call to `https://api.pdok.nl` remains in the file; all six
functions exported; `extractCoordinates("POINT(4.88525 52.37025)")` returns
`{lat: 52.37025, lng: 4.88525}` in unit test.

- [x] PR-1.2 Add 503 handling: when openconnector returns HTTP 503, the shim resolves
with `null` and makes the `message_key` from the response body available to the caller
for display. No uncaught exceptions should reach the form component.
- **Acceptance:** Unit test with mocked 503 response confirms the function resolves
with `null` and the `message_key` is accessible to the caller.

- [x] PR-1.3 Add 404 handling: when openconnector is not installed and returns HTTP 404,
the shim surfaces an inline warning on the address field without throwing or breaking
form submission.
- **Acceptance:** Unit test with mocked 404 response confirms inline warning is
surfaced and form submission is unaffected.

### PR-2. Update procest unit tests (S)

- [x] PR-2.1 Update any existing procest unit tests that mock `fetch()` or `axios` on
`api.pdok.nl` to mock on `/index.php/apps/openconnector/api/pdok/*` instead.
Confirm all tests pass.
- **Acceptance:** `npm run test` passes with zero failures; no test file references
`api.pdok.nl`.

### PR-3. End-to-end verification (S)

- [ ] PR-3.1 Run the procest frontend in the dev environment (localhost:3000); verify
address autocomplete still resolves suggestions when typing a partial address and that
a full lookup populates all address fields.
- **Acceptance:** Manual or Playwright smoke test confirms the address field functions
correctly end-to-end via the openconnector shim.

- [ ] PR-3.2 Confirm that when openconnector is not installed (404 on the endpoint),
the shim surfaces an inline warning on the address field without breaking form
submission.
- **Acceptance:** Test scenario with openconnector absent confirms form submits
successfully with warning displayed.

### PR-4. Seed data for procest test environment (S)

- [ ] PR-4.1 Ensure the two valid address fixtures (Conduction HQ, Tilburg Stadhuis)
from `openregister/tests/fixtures/addresses/` are loadable in the procest dev/test
environment so E2E tests can assert against OR-stored addresses without requiring a
live PDOK or openconnector connection. Add a test environment bootstrap step that
calls OR's API to load both fixtures before Playwright tests run.
- **Acceptance:** Test environment bootstrap loads both fixtures; Playwright test can
call OR addresses listing and receive the fixtures without openconnector or PDOK
being available.
Loading
Loading