Skip to content

fix(controllers): accept string UUIDs on /sources/test, /jobs/run|test, /synchronizations/{id}/run|test (closes #839)#840

Closed
rubenvdlinde wants to merge 3 commits into
feature/i18n-complete-translationsfrom
fix/action-controllers-uuid-param-type
Closed

fix(controllers): accept string UUIDs on /sources/test, /jobs/run|test, /synchronizations/{id}/run|test (closes #839)#840
rubenvdlinde wants to merge 3 commits into
feature/i18n-complete-translationsfrom
fix/action-controllers-uuid-param-type

Conversation

@rubenvdlinde
Copy link
Copy Markdown
Contributor

Summary

Closes #839. The chain-B/C OR cutover swapped auto-increment integer ids for UUIDs, but five action-controller methods kept int $id parameters:

Method File:line
SourcesController::test lib/Controller/SourcesController.php:225
JobsController::run lib/Controller/JobsController.php:181
JobsController::test lib/Controller/JobsController.php:225
SynchronizationsController::test lib/Controller/SynchronizationsController.php:228
SynchronizationsController::run lib/Controller/SynchronizationsController.php:277

PHP coerces the inbound UUID string ("77637bae-01ec-4909-9f47-f2236fe7c3b1") to int by taking the leading digits → 77. ObjectService::find(id: "77", schema: '<schema>') then raises DoesNotExistException → the controller's try/catch returns 404 Not Found.

Why this matters

This is the root cause of the empty openconnector dashboard reported on #838. The Newman/Playwright journey tests "pass" because they only assert response shape (Newman scripts check 200/4xx, not semantic success), but the underlying CallService::call(), JobService::executeJob(), SynchronizationService::synchronize() never actually runs — so no call_log / job_log / synchronization_log row gets written, and the dashboard charts have nothing to plot even after thousands of test invocations.

Every Run-now / Test-connection button click from the action-row-handlers we just added in #830 was also silently 404'ing for the same reason.

Fix

Five int $idstring $id parameter changes + drop the now-redundant (string) $id cast at the find() call site + update the @param int $id docblocks to @param string $id ...UUID.... No behavioural changes elsewhere.

Related upstream gaps

This patch makes the controllers reach saveObject again, but two OR-side issues will still block log persistence end-to-end:

  • openregister#1615 — the oc_openregister_table_65_223 (call_log) magic table doesn't exist on this instance because OR only lazy-creates magic tables for schemas with seed data. CallService writes will throw relation does not exist until that lands.
  • openregister#1614 — even after the table exists, the x-openregister-archival retention annotation declared in the openconnector descriptor is silently stripped (vocabulary gap). Logs aren't immutable and don't get retention-swept.

Filed as separate issues; this PR only addresses the openconnector-internal typing bug.

Test plan

  • Static: PHP type signatures + docblocks updated consistently
  • Local: create a source pointing at https://httpbin.org, POST /api/sources/test/{uuid} → returns the actual HTTP test result (not 404)
  • After OR#1615 lands: a call_log row appears in oc_openregister_table_65_223 and is visible via GET /api/objects/openregister/api/objects/openconnector/call_log
  • The openconnector dashboard chart picks it up (after nc-vue#323 + new beta release)
  • CI: build, lint, phpcs, phpmd, psalm, phpstan

…t, /synchronizations/{id}/run|test

The chain-B/C OR cutover replaced auto-increment integer ids with UUIDs,
but five action-controller methods kept `int $id` parameters:

  SourcesController::test           lib/Controller/SourcesController.php:225
  JobsController::run               lib/Controller/JobsController.php:181
  JobsController::test              lib/Controller/JobsController.php:225
  SynchronizationsController::test  lib/Controller/SynchronizationsController.php:228
  SynchronizationsController::run   lib/Controller/SynchronizationsController.php:277

PHP coerces the inbound UUID string ("77637bae-01ec-4909-9f47-f2236fe7c3b1")
to int by taking the leading digits → 77. ObjectService::find(id: "77",
schema: '<schema>') then raises DoesNotExistException → the catch returns
404 Not Found. Every action-button click in the UI + every Newman
fixture POST to /sources/test/{uuid} silently 404s with no log written.

This is the root cause of the empty openconnector dashboard reported in
#838 — the journey tests "pass" because they
only assert response status, not semantic success, but the underlying
CallService / JobService / SynchronizationService never actually runs.

Switch `int $id` → `string $id` on all 5 methods. Drop the now-redundant
`(string) $id` cast at the `find()` call site (the param is already
a string). Update docblocks (`@param int $id` → `@param string $id ...UUID...`).

Discovered while investigating an empty openconnector dashboard.
Companion bugs filed at OR side:
  - ConductionNL/openregister#1614 — honor x-openregister-archival annotation
  - ConductionNL/openregister#1615 — eager magic-table creation on schema import
Closes #839.
…`sourceId` integer

The openconnector_register.json descriptor declares `call_log.sourceId`
as a legacy integer FK kept "during transition per chain-A REQ-A-008
(cleanup tracked at #821)", and a parallel `source` property typed
`string format=uuid` ($ref to source schema) as the post-chain-B
canonical reference.

`CallService::call()` (and its 3 short-circuit paths for disabled
source / missing location / rate-limited) write `'sourceId' =>
$source->getUuid()` — a UUID string being shoved into the legacy
integer column. Postgres rejects:

    SQLSTATE[22P02]: Invalid text representation: 7
    ERROR: invalid input syntax for type integer:
           "77637bae-01ec-4909-9f47-f2236fe7c3b1"

Switch all four `'sourceId'` keys to `'source'` so the UUID lands in
the correct column. The legacy integer column stays empty (NULL) —
#821 tracks its removal once nothing reads
it anymore.

## Verified end-to-end on the local container

(After ConductionNL/openregister PR #1616 lands the eager magic-table
creation that materialises `oc_openregister_table_65_223`:)

  TRUNCATE oc_openregister_table_65_223;
  // PHP CLI: CallService->call(source: $src, endpoint: '/get');
  → row inserted with `source = '77637bae-...'`, status_code = 200
  GET /api/objects/openregister/api/objects/openconnector/call_log
  → returns the row (after a manual UPDATE _owner='admin' workaround
    pending ConductionNL/openregister#1617 — system-context attribution)

Same fix needed for the JobService / SynchronizationService write paths
(jobId / synchronizationId → job / synchronization) — filed separately
as #841 since those services have additional
complexity (event_message also has the integer-FK problem on
eventId / consumerId / subscriptionId).

Companion changes shipping together in this PR:
- 5 action-controllers `int $id` → `string $id` (the original commit)
- This CallService field-name fix

Both are "chain-B/C cutover type drift" bugs.
…ns' into fix/action-controllers-uuid-param-type
rubenvdlinde added a commit to ConductionNL/openregister that referenced this pull request May 22, 2026
… (#1616)

`ImportHandler::importFromApp` previously called
`MagicMapper::ensureTableForRegisterSchema` only from inside the
seed-objects loop (line 3187, formerly 3174). Schemas with an empty
seed-objects array therefore never got their per-schema magic table
provisioned during register import.

The fallback was lazy: the first write against the schema would trigger
`ensureTableForRegisterSchema` inside `MagicMapper::saveObject`. For
entity-style schemas (source, job, mapping, …) users write to them
directly so the table appears on first use. For **log-style schemas**
(call_log, job_log, synchronization_log, synchronization_contract_log
in ConductionNL/openconnector — and similar in other apps) writes only
happen from internal services. If anything earlier in the chain
swallowed an error (controller validation, find lookup, etc.), the
service never reached saveObject and the table stayed missing forever.

In practice: openconnector dashboards rendered empty even after
thousands of Newman + Playwright invocations because every
`POST /sources/test/{id}` 404'd at the controller (separate bug,
ConductionNL/openconnector#840), AND if it ever reached CallService
the saveObject would have thrown `relation oc_openregister_table_65_223
does not exist`.

## Fix

Provision the magic table for **every** Schema bound to the Register
inside `autoCreateRegisterIfApplication()`, right after the Register
is created / reconciled and its Configuration is synced. The loop
iterates the schemas already in scope, is idempotent (the underlying
helper short-circuits when the table already exists per
MagicTableHandler line 109-112), and non-fatal (any per-schema
failure logs a warning instead of aborting the whole import).

The original seed-objects-loop pre-create at line 3225 stays in place
as a defensive no-op for back-compat.

## Verified end-to-end on a chain-E openconnector instance

  occ maintenance:repair --include-expensive
  → OpenConnector Repair step fires importFromApp
  → autoCreateRegisterIfApplication runs the new loop
  → All 15 openconnector schemas now have magic tables:
       source / job / mapping / synchronization (entity)
       call_log / job_log / synchronization_log /
       synchronization_contract_log (log — previously missing)
       … plus 7 others.
  CallService->call() then succeeds and writes to call_log.
@rubenvdlinde rubenvdlinde deleted the branch feature/i18n-complete-translations May 22, 2026 05:10
@rubenvdlinde rubenvdlinde deleted the fix/action-controllers-uuid-param-type branch May 22, 2026 05:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant