From 92c19ff4d9312f3d9706830d7f442b3fc8c1407a Mon Sep 17 00:00:00 2001 From: kzangeli Date: Fri, 5 Jun 2026 15:03:35 +0200 Subject: [PATCH] fix: 038_02_01 Location parsing + 038_08_03 actually-invalid q MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 038_02_01 stored the raw Location header as the subscription id; Location is a URI reference (RFC 9110 § 10.2.2) carrying the resource path, so the follow-up retrieve and the teardown delete built doubled URLs and failed — leaking the created subscription into the 041_* count assertions. The id is now the last path segment, and the retrieve comparison ignores the clause-12.4.7 Additional Members (root level and inside 'notification') like its 038_01/038_03 siblings. 038_08_03's fixture used "q": "invalidQuery" — a bare attribute path, which the § 7.x ABNF (QueryTerm = Attribute) makes a VALID existence-predicate query that brokers must accept with 201. The fixture now sends "(invalidQuery" (unbalanced parenthesis), the same genuinely-invalid form 037_03_02 uses. The notification_result_regex_expr variable and the assertionUtils.py threshold_to_diff_deeper=0 fix are re-declared identically to their origin branch (unmerged MR) — git deduplicates on merge. Analysis recorded as testsuite-doubts.md #96. --- .../038_02.robot | 18 ++++++++++-- .../subscription-invalid-query.jsonld | 2 +- libraries/assertionUtils.py | 9 ++++++ resources/AssertionUtils.resource | 1 + testsuite-doubts.md | 29 +++++++++++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/TP/NGSI-LD/ContextSource/RegistrationSubscription/CreateContextSourceRegistrationSubscription/038_02.robot b/TP/NGSI-LD/ContextSource/RegistrationSubscription/CreateContextSourceRegistrationSubscription/038_02.robot index e50bf18f..3875fb9f 100644 --- a/TP/NGSI-LD/ContextSource/RegistrationSubscription/CreateContextSourceRegistrationSubscription/038_02.robot +++ b/TP/NGSI-LD/ContextSource/RegistrationSubscription/CreateContextSourceRegistrationSubscription/038_02.robot @@ -22,7 +22,10 @@ ${subscription_id}= ${EMPTY} ${response}= Create Context Source Registration Subscription ${subscription_payload} Dictionary Should Contain Key ${response.headers} Location msg=HTTP Headers do not contain key 'Location' - ${subscription_id}= Get From Dictionary ${response.headers} Location + ${location}= Get From Dictionary ${response.headers} Location + # Location is a URI reference (RFC 9110 § 10.2.2) — typically the resource + # path /ngsi-ld/v1/csourceSubscriptions/. Extract the generated id. + ${subscription_id}= Fetch From Right ${location} / Set Suite Variable ${subscription_id} Check Response Status Code 201 ${response.status_code} @@ -32,7 +35,18 @@ ${subscription_id}= ${EMPTY} ... subscription_id=${subscription_id} ... context=${ngsild_test_suite_context} ... accept=${CONTENT_TYPE_LD_JSON} - ${ignored_attributes}= Create List ${id_regex_expr} ${status_regex_expr} + # Ignore the Additional Members ('lastFailure', 'lastNotification', 'timesFailed', + # 'timesSent', 'status'), both at root level and inside 'notification' (clause + # 5.2.14), where they may have been set already by the initial notification sent + # upon subscription creation (clause 12.4.7). + ${ignored_attributes}= Create List + ... ${id_regex_expr} + ... ${status_regex_expr} + ... ${lastfailure_regex_expr} + ... ${lastNotification_regex_expr} + ... ${timesFailed_regex_expr} + ... ${timesSent_regex_expr} + ... ${notification_result_regex_expr} Check Created Resource Set To ${subscription_payload} ${response1.json()} ${ignored_attributes} diff --git a/data/csourceSubscriptions/subscription-invalid-query.jsonld b/data/csourceSubscriptions/subscription-invalid-query.jsonld index ff185d6c..ed33311f 100644 --- a/data/csourceSubscriptions/subscription-invalid-query.jsonld +++ b/data/csourceSubscriptions/subscription-invalid-query.jsonld @@ -6,7 +6,7 @@ "type":"Building" } ], - "q": "invalidQuery", + "q": "(invalidQuery", "notification":{ "format":"keyValues", "endpoint":{ diff --git a/libraries/assertionUtils.py b/libraries/assertionUtils.py index 14b35fba..3e186153 100644 --- a/libraries/assertionUtils.py +++ b/libraries/assertionUtils.py @@ -98,10 +98,16 @@ def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ig :param exclude_regex_paths: list of regex paths of keys to be ignored :param ignore_core_context_version: whether any core context version is allowed in the results :param group_by: a key to group the results, useful for lists of results + + threshold_to_diff_deeper=0: since deepdiff 8, when more than 1/3 of a dict's keys + differ, DeepDiff reports the whole dict as changed instead of descending into it, + which silently bypasses exclude_regex_paths targeting the nested keys (e.g. the + broker-computed Additional Members inside 'notification'). 0 restores per-key diffs. """ if group_by is not None and ignore_core_context_version: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, + threshold_to_diff_deeper=0, iterable_compare_func=compare_func, custom_operators=[ AnyCoreContextVersionOperator(), @@ -111,6 +117,7 @@ def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ig group_by=group_by) elif group_by is not None: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, + threshold_to_diff_deeper=0, iterable_compare_func=compare_func, custom_operators=[ StringOrSingleListContextOperator(), @@ -120,6 +127,7 @@ def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ig group_by=group_by) elif ignore_core_context_version: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, + threshold_to_diff_deeper=0, iterable_compare_func=compare_func, custom_operators=[ AnyCoreContextVersionOperator(), @@ -128,6 +136,7 @@ def compare_dictionaries_ignoring_keys(expected, actual, exclude_regex_paths, ig ]) else: res = DeepDiff(expected, actual, exclude_regex_paths=exclude_regex_paths, ignore_order=True, verbose_level=1, + threshold_to_diff_deeper=0, iterable_compare_func=compare_func, custom_operators=[ StringOrSingleListContextOperator(), diff --git a/resources/AssertionUtils.resource b/resources/AssertionUtils.resource index 6b75603d..c21eaa5c 100755 --- a/resources/AssertionUtils.resource +++ b/resources/AssertionUtils.resource @@ -20,6 +20,7 @@ ${lastNotification_regex_expr}= root\\['lastNotification'\\] ${timesFailed_regex_expr}= root\\['timesFailed'\\] ${timesSent_regex_expr}= root\\['timesSent'\\] ${is_active_expr}= root\\['isActive'\\] +${notification_result_regex_expr}= \\['notification'\\]\\['(lastNotification|lastSuccess|lastFailure|timesSent|timesFailed|status)'\\] ${context_source_ignored_regex_expr}= root\\['@context'\\] diff --git a/testsuite-doubts.md b/testsuite-doubts.md index 50417d0d..fe3e9429 100644 --- a/testsuite-doubts.md +++ b/testsuite-doubts.md @@ -2667,3 +2667,32 @@ 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). + + +## 96. `038_02_01` / `038_08_03` — Location parsed as an id, and a syntactically valid `q` expected to fail + +Two independent defects in the csourceSubscription-creation tests; both also +LEAK their subscription (the teardown's delete fails / never runs), and the +leaked subscriptions then poison the `041_*` count assertions +(`Query All Subscriptions: 5 != 3` etc.) in full-suite runs. + +**a) `038_02_01` (create without id):** the test stores the raw `Location` +response header as `${subscription_id}`. `Location` is a URI reference +(RFC 9110 § 10.2.2) — brokers return the resource path +`/ngsi-ld/v1/csourceSubscriptions/` — so the follow-up retrieve and the +teardown delete build doubled URLs and fail. Fixed by extracting the last +path segment. The retrieve comparison additionally needs the clause-12.4.7 +Additional-Members ignores (root level and inside `notification`) like its +siblings 038_01/038_03 — the initial notification to the dead fixture +endpoint sets `timesSent`/`timesFailed`/`lastNotification`/`lastFailure`/ +`status` immediately. + +**b) `038_08_03` (InvalidQuery):** the fixture's `"q": "invalidQuery"` is a +bare attribute path — per TS 104-175 § 7.x ABNF (`QueryTerm = Attribute`) +that is a syntactically VALID query (an existence predicate), so a broker +must accept it with 201. The fixture now uses `"(invalidQuery"` (unbalanced +parenthesis — genuinely invalid), mirroring `037_03_02`. + +(The `notification_result_regex_expr` variable and the assertionUtils.py +`threshold_to_diff_deeper=0` fix are re-declared identically to their origin +branch since that MR is unmerged — git deduplicates on merge.) -- GitLab