From a585469c6232df2b0c8f0cdf75307871ca059385 Mon Sep 17 00:00:00 2001 From: kzangeli Date: Mon, 1 Jun 2026 12:13:05 +0200 Subject: [PATCH 1/3] doubts: mark RESOLVED status + add #83 (HttpCtrl query-string non-GET) + #84 (post-MR cluster) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring testsuite-doubts.md fully up-to-date with the recent MRs and the remaining unclear cluster surfaced by the post-MR ETSI run. Status added to existing entries: #2 PARTIALLY RESOLVED via !284 (trailing-/ slice) #22 PARTIALLY RESOLVED via !284 (trailing-/ slice; query-params slice still open, see #83) #23 RESOLVED via !285 (25 distop tests: Should Be Equal -> Should Be Equal As Integers) #40 Slice #1 RESOLVED via !284; slices #2-3 still open, see #83 #79 documented (no test-side fix yet — attrs= -> pick=) #80 RESOLVED via !283 (deleteAll= sweep) #80b documented (no fix yet — Create Subscription And Entity keyword default-arg bug) #82 documented (broker side done; test side still pending per item) New entries: #83 HttpCtrl matcher's non-GET branch ignores the request's query string, so canonical ?options=… / ?deleteAll=… / ?sysAttrs=… forwards never match. Affects D003_02_red, D006_02_inc, D013_01_inc, D013_02_{exc,inc}. #84 Post-MR remaining ETSI cluster: 84a D013_01_exc / D014_02_exc — § 9.3.3 fixture (same pattern as #82b on new tests) 84b D013_01_red / D005_01_exc — § 5.9.2 CSR-vs-local-entity conflict (201 != 409) 84c D014_02_inc — HttpCtrl 'NoneType' .get_url (downstream of #83's query-string mismatch) 84d D005_01_inc / D005_01_red / D003_02_red — context-handling (#70 pattern on new tests) 84e D018_02_02 — not triaged yet Co-Authored-By: Claude Opus 4.7 --- testsuite-doubts.md | 211 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/testsuite-doubts.md b/testsuite-doubts.md index 50417d0d..e06c2793 100644 --- a/testsuite-doubts.md +++ b/testsuite-doubts.md @@ -93,6 +93,11 @@ when no stub matches, returning a default 404 immediately would let broker-side error handling exercise (and would be obvious in test logs). +**Status:** PARTIALLY RESOLVED in MR !284 — the specific trailing-`/` +slice of `__is_satisfy` (stub registered with trailing `/` never +matching a request with trailing `/`) is fixed there. The broader +substring-matching / odd-control-flow concerns remain. + --- ## 3. Mock server started AFTER CSR registered @@ -659,6 +664,15 @@ Robotframework-httpctrl supports `Set Stub Reply` with regex match in newer versions — adopting that across the DistOps suite would flip ~30 tests without touching the broker. +**Status:** PARTIALLY RESOLVED. The trailing-`/` slice — stub URL +ending in `/` not matching a request also ending in `/` — is fixed +in MR !284 (`__is_satisfy` now `rstrip("/")`-s both sides). +Broker-side `?pick=type[,scope],` is emitted per § 6.3.4 + § 6.3.6 +(swBroker commit 700d07f); HttpCtrl's query-string matching needs +further work to interpret it (substring matching mishandles the +canonical order). The wider regex/prefix proposal still stands for +the URL-params slice. + ## 23. DistOps — `Get Stub Count 1 (integer) != 1 (string)` (~12 tests) @@ -674,6 +688,9 @@ strict and fails. **Fix wanted:** use `Should Be Equal As Numbers` / `Should Be Equal As Integers`, or `Should Be True ${stub_count} == 1`. +**Status:** RESOLVED — fixed in MR !285 (25 test files; `Should Be +Equal` → `Should Be Equal As Integers`). + ## 24. DistOps — `No keyword with name 'Get Request Url Params' found` @@ -1110,6 +1127,12 @@ request to `/a/b/attrs` (missing trailing slash) or (c) switch tests that need precise URL matching to `Wait For Request` + manual reply instead of the stub mechanism. +**Status:** Slice #1 (trailing-`/` mismatch) RESOLVED in MR !284 — +`__is_satisfy` now strips trailing `/` from both sides of every +comparison. Slices #2 (`?sysAttrs=true` etc.) and #3 (`?options=…`, +`?deleteAll=…`) remain — those need HttpCtrl URL-params handling or +test-side stub registration to accept the canonical broker form. + ## 41. `020_14_01/02` — default temporal page size is not in the spec @@ -2505,6 +2528,9 @@ The spec could be clearer that distributed-operation forwards must translate any legacy `attrs=` from a 1.6-era CSR into `pick=` so that mock test stubs written against the modern spec match the forwarded URL. +**Status:** documented (no fix yet) — the test still needs to flip +`attrs=speed` → `pick=speed` in `D011_02_exc.robot`. + ## 80. `046_22_08` — `deleteAll=${true}` serializes as `deleteAll=True` (capital T) **Files:** @@ -2591,6 +2617,14 @@ delete cycle is consistent. But: The second form matches the convention already used elsewhere in the suite (`deleteAll=${EMPTY}` etc.). +**Status — 80:** RESOLVED — `deleteAll=${true}` part fixed in MR !283 +(same MR that swept all `local=${True}` URL params); the broker now +gets a lowercase `true` and the test progresses past the parseBool 400. + +**Status — 80b:** documented (no fix yet) — the `${entity_id_suffix}` +default-arg bug in `Create Subscription And Entity` is still on the +queue. Low priority since the tests "work" by accident. + ## 82. `D010_01_exc` / `D002_02_exc` / `D003_02_exc` / `D004_01_exc` — distop tests surfaced as test-side after § 9.3.3 enforcement The triage of four distop "later error" failures (from the `lowercase @@ -2667,3 +2701,180 @@ doesn't match the URL id. `$.id` with the actual `${entity_id}` before PATCHing. Or omit the `id` from the fragment entirely (PATCH attrs doesn't need it). +**Status — 82a/82b/82c/82d:** documented (no test-side fix yet). +The broker-side enforcement that surfaced each item is upstream: +swBroker commits a0384f3 (§ 9.3.3 exclusive structural validation) ++ 700d07f (forward `pick=type[,scope]` with id-reassemble) + +ldHooks Link-header @context propagation. Each item still needs its +own test-suite-side fix per the recipe in the entry above. + +## 83. HttpCtrl matcher accidentally couples `?options=…` / `?sysAttrs=true` etc. to GET-only sub-branches + +**Library:** `libraries/robotframework-httpctrl/src/HttpCtrl/http_stub.py`, +`HttpStubContainer.__is_satisfy`. + +**Hit:** Even after the trailing-`/` fix (MR !284), a forward whose +URL carries the canonical NGSI-LD URL params still misses its stub +on **non-GET** methods. The matcher has two branches inside +`if '?' in criteria.url:`: + +- GET branch (lines ~84–126): walks both the stub and request + query strings, validates the stub's parameters appear in the + request, and accepts the match. +- Non-GET branch (lines ~128–144): only verifies + `stub.criteria.url == criteria_url_components[0]` — the stub URL + WITHOUT a `?` against the request URL minus its `?…` tail. If the + stub registers any query at all, the non-GET branch won't take it. + +This affects all write-op forwards that the broker (correctly per +§ 10.2.4 / § 10.2.7 / § 10.3.4) emits with a query string: +`?options=noOverwrite`, `?options=update`, `?deleteAll=true`, +`?sysAttrs=true`, … + +**Concrete hits in the swBroker ETSI run:** + +- `D003_02_red` — `POST /attrs/?options=noOverwrite` +- `D006_02_inc` — `DELETE /attrs/speed?deleteAll=true` +- `D013_01_inc` — `POST /entityOperations/upsert?options=replace` +- `D013_02_exc` / `D013_02_inc` — `?options=update` + +In each case the broker forwards a spec-correct URL and HttpCtrl's +non-GET branch falls through without matching. Mock either times +out or returns its default, broker reports `207` instead of `204` +(or `502` instead of `200`). + +**Spec:** TS 104-175 § 10.2.4.4, § 10.2.7.4, § 10.3.4.4 require these +params to be propagated end-to-end on distop forwards. + +**Our call:** broker uninvolved — emits canonical URL. + +**Fix wanted:** mirror the GET-branch parameter-walk in the non-GET +branch, OR adopt the wider regex/glob proposal already noted in #22 +/ #40 (slice 2-3). + +## 84. New ETSI failures cluster — invalid-fixture / context / HttpCtrl AttributeError + +**Background.** After MRs !283 (lowercase URL bool params), !284 +(HttpCtrl trailing-`/` matcher), !285 (`Should Be Equal` → +`As Integers`) all landed and the swBroker side already enforces +§ 9.3.3 + forward-pick `type[,scope]` (commits a0384f3 + 700d07f), +the remaining "Unclear / needs triage" cluster from the swBroker +ETSI run reduced to six tests with three distinct test-side root +causes (broker is correct in all six): + +**84a) `D013_01_exc` / `D014_02_exc` — fixture invalid for exclusive (§ 9.3.3 surface, new tests)** + +Same pattern as #82b but on different tests. Setup builds an +exclusive CSR from `csourceRegistrations/context-source-registration-vehicle-redirection-ops.jsonld` +— a type-only `{"entities":[{"type":"Vehicle"}]}` selector with no +attribute names. Per § 9.3.3 an exclusive registration shall define +BOTH a specific entity id AND propertyNames / relationshipNames; a +spec-strict broker rejects it with `400 BadRequestData`. Test +expects `201` and the setup `Check Response Status Code 201` fails +(`201 != 400`). + +**Fix:** give the test its own fixture with id + propertyNames +matching what the test exercises (same recipe as #82b). + +**84b) `D013_01_red` / `D005_01_exc` — CSR creation conflict with local entity (`201 != 409`)** + +Setup creates the entity locally with `local=true`, then registers +a redirect / exclusive CSR for the same entity id. Per § 5.9.2 the +broker checks for overlap between the new exclusive/redirect CSR +and any locally-held data: if the local entity already carries the +attrs the CSR claims, the broker returns `409 AlreadyExists`. Test +expects `201`. + +The original test design relied on the pre-§ 9.3.3 broker +behaviour where CSRs that only specified `type: Vehicle` and no +attribute set didn't trigger the conflict check (nothing to +compare). After § 9.3.3 the CSR must carry propertyNames, and as +soon as those propertyNames overlap the local entity's attrs the +conflict check fires. + +**Fix:** seed the local entity with attrs that do NOT overlap the +CSR's claim, OR create the CSR before the entity, OR drop the +`local=true` on entity creation so the broker's distop dispatcher +takes ownership. + +**84c) `D014_02_inc` — HttpCtrl `'NoneType' object has no attribute 'get_url'`** + +When the broker's forward to the CSR's mock takes a path HttpCtrl's +`__is_satisfy` doesn't match, the handler falls through to +`ResponseStorage().pop()` — but for this test the storage is empty +and pop returns `None`. The next `response.get_url()` raises +`AttributeError`. This is the "blocking fallback" side-issue +already called out in #2 surfacing as a None-deref rather than a +hang. Likely cause: same query-string mismatch as #83 (`?options=update` +on update-batch forwards). Once #83 is fixed this should clear. + +**Fix wanted:** test-side stubs aligned with what the broker emits +once #83 lands. + +**84d) `D005_01_inc` / `D005_01_red` / `D003_02_red` — expanded IRI in response (#70 pattern on different tests)** + +Body assertion `Dictionary does not contain key 'speed'` because +the response returns `https://ngsi-ld-test-suite/context#speed` +(the test-suite-context expansion). Same root cause as #70: the +test sends `Content-Type: application/json` to the broker without +a Link header, so the broker uses core context; CSR was registered +with test-suite-compound context; the two namespaces don't reconcile +in the response. + +**Fix:** add the test-suite-context Link header on the test's main +call (or move to `application/ld+json` with a root `@context`). + +**84e) `D018_02_02` — `204 != 207`** + +Not triaged in detail yet — left for a follow-up run. + +**84f) `D016_01_red` — test forgets to stub the GET for the post-merge query** + +The test merges two entities via a redirect CSR (stubs for +`POST /broker{1,2}/.../entityOperations/merge` are registered). Both +merge forwards return 204 successfully. The verification step then +calls `Query Entities entity_types=Vehicle ...` — but the test never +stubs `GET /broker{1,2}/.../entities?type=Vehicle&id=…`. With redirect +mode the broker holds no local data, so it forwards the query to both +CSes; neither stub matches; both forwards time out; the broker merges +zero results and returns `[]`. The assertion +`Should Contain ${response.json()} speed` fails with +`'[]' does not contain 'speed'`. + +**Fix:** add stubs for the `GET /broker{1,2}/ngsi-ld/v1/entities?…` +that return a merged-entity payload containing `speed`. Mirror what +`D016_01_inc` does (which stubs both legs). + +**84g) `D001_01_inc` — joins #72 (leftover Vehicle entities in current state)** + +Full-suite failure mode `Lengths are different: 1 != 5`. The +`Query Entities entity_types=Vehicle local=true` returns 5 entities — +one created by this test plus 4 leftovers from earlier +DistOps tests in the full run (mirrors the cluster already documented +in #72: `D001_02_inc / D001_03_03_inc / D011_02_inc`). Passes in +isolation. Same fix as #72. + +**84h) `D007_01_exc` — Robot keyword choice doesn't tolerate the spec-mandated absence of speed** + +After PUT-Replace + an exclusive CSR claiming `speed`, the local +entity correctly drops the claimed attr per § 4.3.6.3 — the local +copy carries `{id, type, isParked2}` and no `speed`. The test then +does + +```robot +${body}= Get From Dictionary ${response.json()} speed +Should Not Contain ${body} speed +``` + +`Get From Dictionary` raises when the key is absent, so the test +fails on line 40 with `Dictionary does not contain key 'speed'` — +even though that's exactly the broker behaviour the test is meant +to verify. + +**Fix:** use `Dictionary Should Not Contain Key ${response.json()} +speed` instead of fetching the value and substring-checking it. The +extracted-then-substring-checked pattern only makes sense if you +expect the key to be present and want to verify it equals +something — not the negative-presence assertion this test is +trying to do. + -- GitLab From 63751476cbf5967fd8ab5c96438786e2f67f1594 Mon Sep 17 00:00:00 2001 From: kzangeli Date: Mon, 1 Jun 2026 22:35:15 +0200 Subject: [PATCH 2/3] =?UTF-8?q?doubts:=20add=20#69=20(HttpCtrl=20Set=20Stu?= =?UTF-8?q?b=20Reply=20double-encodes=20string=20body)=20=E2=80=94=20RESOL?= =?UTF-8?q?VED?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HttpCtrl `set_stub_reply` double-encode doubt had no entry in the file. Add it as #69 (adjacent to #68, the other HttpCtrl distop stub-body doubt), marked RESOLVED: the conditional auto-serialise landed on develop via the housekeeping bundle !278 and, redundantly, the standalone !282. Doc-only; no behaviour change. --- testsuite-doubts.md | 46 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/testsuite-doubts.md b/testsuite-doubts.md index e06c2793..cdba2d4a 100644 --- a/testsuite-doubts.md +++ b/testsuite-doubts.md @@ -2110,6 +2110,52 @@ str/bytes" failure rather than a mysterious half-response and broker-side "transport failure". +## 69. HttpCtrl vendored 0.3.1 — `Set Stub Reply` double-encodes a string body + +**Hit:** DistOps tests that call `Set Stub Reply ` +with a body produced by `Convert JSON To String` (a Python `str` already +holding serialised JSON) — including `D011_02_exc_01` and structurally +similar tests across the suite. The broker forwards correctly and the stub +matches, but the wire body it receives is `'"[{\"id\":\"X\"}]"'` — a JSON +string wrapping the intended array. The broker's response parser sees a +`String` where it expects an `Array`, the merge can't proceed, and the +entity-level assertion (`$.speed`, etc.) fails. + +**Why:** in `libraries/robotframework-httpctrl/src/HttpCtrl/__init__.py`, +`set_stub_reply` used to wrap every body in `json.dumps()`: + +```python +response = Response(int(status), None, json.dumps(body), None, None) +``` + +That was presumably added to fix the **opposite** problem (`Set Stub Reply` +receiving a Python `dict` and sending it raw — the `D010_01_aux` / MR !273 +case; see #68's "Independent HttpCtrl bug" note). The fork over-corrected: +dict callers were served correctly, but callers passing an already-serialised +JSON string got it double-encoded. + +**Impact / broker:** none — the broker is correct; the wire body was mangled +upstream of it. + +**Status:** RESOLVED — auto-serialise only non-`str`/`bytes` bodies: + +```python +if not isinstance(body, (str, bytes)) and body is not None: + body = json.dumps(body) +response = Response(int(status), None, body, None, None) +``` + +This handles both callers: dicts/lists get auto-serialised (the original +intent of the fork's patch), str/bytes pass through unchanged. Landed on +develop via the housekeeping bundle **!278** and, redundantly, the standalone +**!282** (the fix had been duplicated across both branches — !278 merged +first, so !282 reduced to the explanatory comment + docstring). The +`D010_01_aux` test-side work-around (`Evaluate json.dumps($entity_body)`, +landed via !273) stays harmless — a string `json.dumps`'d to itself is +unchanged — so !273 needs no follow-up. Verified: `D011_02_exc_01` passes and +the DistOps subset PASS count rose ~9 vs the pre-fix baseline. + + ## 70. `D003_01_exc` — Append-Attrs fragment sent as `application/json` with no Link header, so attribute names don't expand to the CSR's **Hit:** `D003_01_exc Append Entity Attribute` fails the -- GitLab From 15c402ce686a1a3e0f8ed9e8245326a7bb06fd26 Mon Sep 17 00:00:00 2001 From: kzangeli Date: Tue, 2 Jun 2026 15:47:41 +0200 Subject: [PATCH 3/3] doubts: relocate #85-#88 from code/doubts MRs (273/277/279/281) into the doubts MR Per the "doubts.md changes live in one dedicated MR" policy, fold the doubt entries that were riding inside other MRs into this one: - #85 D010_01_aux dict-body stub (RESOLVED via !273; dict-body counterpart of #69) - #86 D011_02_exc_01 CSR-without-Link-header context mismatch (OPEN; !277 workaround; continues #79) - #87 020_17..020_20 temporal-setup pairing (PARTIALLY RESOLVED via !279; broker tombstone-kind bug still open) - #88 DistOps write-op status-code triage, 30 tests / 7 patterns (folded from !281) !273/!277/!279 were rebased to drop their doubts.md hunks; !281 (doubts-only) is superseded by #88 here and can be closed. Doc-only. --- testsuite-doubts.md | 191 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) diff --git a/testsuite-doubts.md b/testsuite-doubts.md index cdba2d4a..8a5e8958 100644 --- a/testsuite-doubts.md +++ b/testsuite-doubts.md @@ -2924,3 +2924,194 @@ expect the key to be present and want to verify it equals something — not the negative-presence assertion this test is trying to do. + +## 85. `D010_01_aux` — stub body passed as a Python dict; HttpCtrl doesn't auto-serialise + +**Hit:** `D010_01_aux` (auxiliary RetrieveEntity gap-fill merge) fails with +`No value found for path $.isParked` — the broker's response contains only +the local entity, missing the gap-fill attribute the auxiliary CSR should +provide. + +**Why:** the test does + +```robotframework +${entity_body}= Load Entity ${entity_payload_filename} ${entity_id} +Set Stub Reply GET /ngsi-ld/v1/entities/${entity_id} 200 ${entity_body} +``` + +`Load Entity` returns a Python **dict**, and HttpCtrl's response writer +(`http_handler.py::__send_response`) only encodes the body when it is already +a `str`. A dict falls through: `isinstance(body, str)` is false → no encoding; +`len(dict)` returns the number of KEYS → wrong `Content-Length`; +`wfile.write(dict)` raises (silently swallowed) → empty body on the wire. The +mock sends headers only, the broker has no auxiliary body to merge, the test +fails on the missing gap-fill attribute. + +**Impact / broker:** none. A correct aux-merge produces the spec-correct +§ 9.3.2 result (local wins for shared attrs, CSR fills gaps) when verified +end-to-end against a real upstream returning valid JSON. + +**Status:** RESOLVED via !273 — the test now serialises the body first +(`Evaluate json.dumps($entity_body) json`) so the wire payload is +non-empty. This is the dict-body counterpart of [[#69]]: once the vendored +fork's conditional auto-serialise landed (via !278/!282, see #69), this +test-side `json.dumps` became belt-and-suspenders — a string re-`json.dumps`'d +to itself is unchanged — so it stays harmless. The same dict-body pattern may +affect other `D010_*` retrieve tests; the framework-side guard in #69 is the +cleaner long-term home. + + +## 86. `D011_02_exc_01` — CSR response without a Link header → broker can't reconcile attribute IRIs + +**Hit:** Even with the upgraded HttpCtrl matcher (!277) and the spec-canonical +`pick=` stub URL (see #79), `D011_02_exc_01` still fails with `No value found +for path $.speed`. The mock is matched, the broker forwards correctly and +receives the speed entity body — but the merged response contains only the +local-broker attributes (`brandName`, `isParked`); the CSR-side `speed` is +silently dropped. + +**Root cause (likely):** the HttpCtrl mock can set Content-Type + body via +`Set Stub Reply` but cannot set a Link header. So the mock's response carries +no `Link: <…>; rel="http://www.w3.org/ns/json-ld#context"`. The broker, +receiving a JSON-only response with no Link, falls back to the **default core +context** when expanding the CSR's short attribute names. The local entity was +stored with the test-suite context, so its `speed` expanded to one IRI; the +CSR-side `speed` expands to `…/default-context/speed`. Different IRIs → the +merge keeps them as different attributes; the response `@context` only knows +the local one, so the CSR-side speed is lost on compaction. + +**Two-sided issue:** + +- **Test-side:** `Set Stub Reply` cannot add response headers, so it cannot + simulate a real CS that echoes back the request's `Link`. The right + HttpCtrl-fork fix is `Set Stub Reply` with optional headers. +- **Broker-side:** when a CSR responds with no Link header, the broker should + fall back to the registered CSR's `@context` (§ 5.2.12) or the original + request's `@context` — not the default core. § 9.3 is permissive here; + arguably a spec gap too. + +**Status:** OPEN. !277 switched the stub URL to spec-canonical `pick=speed` so +the mock is at least reached when paired with the matcher fix; the merge bug +itself needs broker-side review. Continuation of #79 (same test, next layer); +related to the header-less-stub limitation in [[#83]]. + + +## 87. `020_17 / 020_18 / 020_19 / 020_20` setup — pair the temporal POST with a current-state POST + +**Status:** PARTIALLY RESOLVED via !279 (setup side, fixing [[#9]]); a +broker-side deletedAt-tombstone bug remains, see below. + +**Hit + spec recap:** see [[#9]] for the full analysis. Briefly: each test's +setup did only `POST /temporal/entities`, then the body's `DELETE +/entities/{id}/attrs/{name}` (a current-state operation) returned 404 because +the entity had no current-state representation; no `deletedAt` tombstone was +written, so the temporal-evolution assertion failed with `Item root[] +removed from dictionary`. + +**Fix (via !279):** each `Create Temporal Entity` setup now also POSTs a +matching current-state entity via `Create Entity`, using two new minimal +fixtures: `vehicle-different-attribute-types.jsonld` (fuelLevel/isParkedIn/name, +020_17/18) and `vehicle-with-scope.jsonld` (scope + fuelLevel, 020_19/20). The +following `POST /temporal/entities` may return 201 *or* 204 per § 11.2.2.5 +(append to existing), so the status check is relaxed accordingly. + +**Result:** 020_17_01 (Property) and 020_18_01 (Property + temporalValues) now +pass. The four non-Property cases (020_17_02/03, 020_18_02/03, 020_19_01, +020_20_01) still fail on a *different*, broker-side root cause: + +**Broker bug surfaced (separate work):** the `deletedAt` tombstone collapses +all attribute kinds to `Property` with `urn:ngsi-ld:null`. For a deleted +**Relationship**, the temporal-store tombstone should preserve +`type: Relationship` and substitute `object: urn:ngsi-ld:null` — not switch +the kind to Property. Same for **LanguageProperty** (`languageMap`) and +**Scope**. The broker emits a uniform `{ "type": "Property", "value": +"urn:ngsi-ld:null", "deletedAt": … }` for every kind, so the deepdiff fails +with `… removed` + `type changed from "" to "Property"`. The broker fix +belongs in its own MR — out of scope for the test-side change. + + +## 88. DistOps write-op status-code mismatches — 30 tests across 7 patterns, mostly test-side design issues + +A categorisation pass on the 30 DistOps tests whose `HTTP status code +comparison failed`. Triaged into seven patterns; for most the broker is +correct and the tests need a redesign by the original author. None of this is +implementation-blocking: the broker behaviour is the spec-compliant or +spec-defensible reading in every case. Overlaps the clusters in #82 / [[#84]]. + +### 88a) 201 ≠ 409 (8 tests) — setup creates a CSR that conflicts with locally-stored data + +Affected: `D001_02_exc`, `D002_02_exc`, `D005_01_exc`, `D009_01_exc`, +`D013_01_red`, `D006_01_red`, `D006_02_red`, `D009_01_red`. Each setup does +`Create Entity … local=true` then `Create Context Source Registration …` +(exclusive or redirect) for the same entity. Both CSR shapes conflict with a +locally-stored copy and the broker correctly returns **409**: + +- **Exclusive** (4): an exclusive CSR must also specify `propertyNames` / + `relationshipNames`; claiming a whole entity exclusively is not permitted. + The redirection-ops fixture has neither, so it is malformed for exclusive + use anyway. +- **Redirect** (4): a redirect CSR cannot coexist with a local copy of the + same entity — writes would go to the CS, reads to local. 409 by design. + +**Impact / broker:** none — conflict detection at CSR-creation time is +implemented and tested. **Fix wanted:** the tests need a redesign by their +author — the docstrings don't match the setup as written. Likely the intent +was to register the CSR first and exercise the distop path in the test body, +not to pre-create a conflicting local entity. The four `exc` cases also need +`propertyNames` in the CSR fixture. + +### 88b) 204 ≠ 404 (5 tests) — redirect-mode forwards return CS's 404 + +Affected: `D003_01_red`, `D004_01_red`, `D014_01_red`, `D014_02_red`, +`D015_01_red`. Test expects 204; broker returns 404. Likely the stub matches a +different URL shape than the broker forwards (see the matcher work in !277), so +the mock returns its default 404 — or the same setup-conflict pattern as 88a. +**Status:** needs per-test triage. + +### 88c) 207 ≠ 204 (3 tests) and 204 ≠ 207 (2 tests) — inclusive-mode forward failure status + +Affected: `D002_02_01_inc`, `D002_02_02_inc`, `D004_01_inc`, `D018_02_02`, +`D003_01_inc`. Open spec ambiguity (already logged in spec-doubts-2 #90): when +a write forwarded to an *inclusive* CSR fails, should the broker return 207 +(partial success) or a clean 204/201? § 10.2.1.4 and § 9.3.2 imply different +answers; current behaviour varies per op. **Resolution:** wait for spec +clarification. + +### 88d) 200 ≠ 400 (3 tests) — query rejected as invalid + +Affected: `D011_02_red_02`, `D011_03_inc_02`, `D011_04_inc_02`. Broker +considers the query malformed (400); test expects 200. **Status:** needs +per-test triage of whether the strictness is justified. + +### 88e) 201 ≠ 400 (3 tests) — batch op rejected as invalid + +Affected: `D012_01_red`, `D013_02_red`, `D015_01_inc`. Same as 88d for batch +ops; probably related to 88a (CSR-side conflict) since these are +redirect/inclusive. **Status:** needs per-test triage. + +### 88f) 200 ≠ 404 (2 tests) — retrieve says not-found + +Affected: `D010_03_inc_02`, `D010_03_inc_03`. Inclusive-mode retrieve where +the broker reports 404 but the test expects the entity (presumably via the +mocked CSR). Likely a stub-URL mismatch or the 88a conflict pattern. +**Status:** needs per-test triage. + +### 88g) Singletons (4 tests) + +- `D010_02_red` (404 ≠ 200) — retrieve returns 200 where test expects 404. +- `D018_01` (508 ≠ 204) — loop-detection test expects 508; broker returns 204. +- `D003_02_red` (207 ≠ 404) — append-attr test expects 207; broker returns 404. +- `D004_01_exc` (204 ≠ 400) — exclusive create-entity-with-CSR; broker rejects. + +Each likely maps to one of the patterns above; needs individual inspection. + +### Summary + +Of the 30 mismatches: **8 (88a)** confirmed test bug (setup violates +CSR-creation conflict rules; tests need redesign); **5 (88c)** spec ambiguity +already in spec-doubts-2 #90, not actionable until the spec resolves; +**17 (88b/d/e/f/g)** need per-test triage, structurally suggestive of the same +setup-shape issues plus stub-URL gaps but unconfirmed. For all 30 the broker's +behaviour is at minimum spec-defensible; no broker-side change is recommended +without a spec/conformance discussion. + -- GitLab