TS 104-175 (the API) and TS 104-176 (the HTTP binding) are now published, so there is a fixed target to measure the suite against. The full suite was run against a broker, and two things were measured: which parts of the API the suite's requests use, and how much of the broker's code those requests reach.
Every request a Test Case makes goes through RequestsLibrary, which writes the method, the URL, the headers and
the response status into output.xml. A script maps each of these requests to its operation in
TS 104-176 and counts, for each operation:
The operations and the parameters depend only on the Test Cases. The status codes also depend on the broker.
| Clause | Resources | Operations | URL parameters | Status codes |
|---|---|---|---|---|
| §7 | Context information | 27/40 | 36/142 | 76/139 |
| §8 | Temporal evolution | 9/11 | 32/84 | 24/35 |
| §9 | Context sources | 10/14 | 12/39 | 25/40 |
| §10 | @context management | 4/6 | 4/9 | 10/18 |
| §11 | EntityMaps | 0/10 | - | 0/27 |
| §12 | Context source identity | 0/2 | - | 0/4 |
| §13 | Snapshots | 0/7 | 0/1 | 0/23 |
| All | 50/90 | 84/275 | 135/286 |
Not one Test Case exists for these 40 operations:
/info/sourceIdentity).Among the operations that are tested, the parameters are the largest gap.
collation,
containedBy, csf, entityMap, entityMapLifetime,
geometryProperty, lang, orderFrom, orderGeometry or
splitEntities.type is the one parameter of most write operations, and no write is ever sent it.Status codes:
Highlighted rows are operations with no request. In the last column, the parameters never given come first, and the status codes never received follow in bold.
| § | Method | Resource / operation | Req | TCs | Params | Codes | Never exercised |
|---|---|---|---|---|---|---|---|
| 7.2.3.1 | GET | /entities/Query Entities | 231 | 231 | 18/28 | 2/5 | collation containedBy csf entityMap entityMapLifetime geometryProperty lang orderFrom orderGeometry splitEntities 201 203 501 |
| 7.2.3.1 | HEAD | /entities/Query Entities | 0 | 0 | 0/28 | 0/3 | attrs collation containedBy coordinates csf datasetId entityMap entityMapLifetime expandValues geometry geometryProperty geoproperty georel id idPattern join joinLevel jsonKeys lang omit orderBy orderFrom orderGeometry pick q scopeQ splitEntities type 204 400 501 |
| 7.2.3.2 | POST | /entities/Create Entity | 678 | 466 | 0/0 | 4/5 | 422 |
| 7.2.3.3 | DELETE | /entities/Purge Entities | 13 | 13 | 5/14 | 2/3 | coordinates csf geometry geometryProperty geoproperty georel idPattern scopeQ splitEntities 207 |
| 7.3.3.1 | GET | /entities/{entityId}Retrieve Entity | 247 | 233 | 7/12 | 3/6 | containedBy datasetId entityMap entityMapLifetime type 201 203 501 |
| 7.3.3.1 | HEAD | /entities/{entityId}Retrieve Entity | 0 | 0 | 0/12 | 0/4 | attrs containedBy datasetId entityMap entityMapLifetime geometryProperty join joinLevel lang omit pick type 204 400 404 501 |
| 7.3.3.2 | PATCH | /entities/{entityId}Merge Entity | 33 | 33 | 0/5 | 3/4 | format lang observedAt options type 207 |
| 7.3.3.3 | PUT | /entities/{entityId}Replace Entity | 9 | 9 | 0/1 | 3/4 | type 207 |
| 7.3.3.4 | DELETE | /entities/{entityId}Delete Entity | 609 | 436 | 0/1 | 4/4 | type |
| 7.4.3.1 | POST | /entities/{entityId}/attrs/Append Attributes | 21 | 21 | 1/2 | 4/4 | type |
| 7.4.3.2 | PATCH | /entities/{entityId}/attrs/Update Attributes | 83 | 82 | 0/1 | 4/4 | type |
| 7.5.3.1 | GET | /entities/{entityId}/attrs/{attrId}Retrieve Attribute Value | 0 | 0 | 0/7 | 0/5 | datasetId entityMap entityMapLifetime join joinLevel lang type 200 203 400 404 501 |
| 7.5.3.1 | HEAD | /entities/{entityId}/attrs/{attrId}Retrieve Attribute Value | 0 | 0 | 0/7 | 0/4 | datasetId entityMap entityMapLifetime join joinLevel lang type 204 400 404 501 |
| 7.5.3.2 | PATCH | /entities/{entityId}/attrs/{attrId}Partial Attribute Update | 37 | 37 | 0/1 | 3/4 | type 207 |
| 7.5.3.3 | PUT | /entities/{entityId}/attrs/{attrId}Replace Attribute | 12 | 12 | 0/1 | 3/4 | type 207 |
| 7.5.3.4 | DELETE | /entities/{entityId}/attrs/{attrId}Delete Attribute | 39 | 39 | 2/3 | 3/4 | type 207 |
| 7.6.3.1 | GET | /entities/{entityId}/attrs/{attrId}/valueRetrieve Attribute Value | 0 | 0 | 0/5 | 0/5 | datasetId entityMap entityMapLifetime lang type 200 203 400 404 501 |
| 7.6.3.1 | HEAD | /entities/{entityId}/attrs/{attrId}/valueRetrieve Attribute Value | 0 | 0 | 0/5 | 0/4 | datasetId entityMap entityMapLifetime lang type 204 400 404 501 |
| 7.6.3.2 | PUT | /entities/{entityId}/attrs/{attrId}/valueSet Attribute Value | 0 | 0 | 0/3 | 0/4 | datasetId observedAt type 204 207 400 404 |
| 7.7.3.1 | POST | /entityOperations/createBatch Entity Creation | 22 | 22 | 0/0 | 3/3 | |
| 7.8.3.1 | POST | /entityOperations/upsertBatch Entity Creation or Update (Upsert) | 25 | 25 | 0/0 | 4/4 | |
| 7.9.3.1 | POST | /entityOperations/updateBatch Entity Update | 16 | 16 | 0/0 | 3/3 | |
| 7.10.3.1 | POST | /entityOperations/mergeBatch Entity Merge | 10 | 10 | 0/0 | 3/3 | |
| 7.11.3.1 | POST | /entityOperations/deleteBatch Entity Delete | 69 | 66 | 0/0 | 3/3 | |
| 7.12.3.1 | POST | /entityOperations/queryQuery Entities | 30 | 30 | 0/0 | 1/4 | 201 203 400 |
| 7.13.3.1 | GET | /subscriptions/Query Subscriptions | 5 | 5 | 1/1 | 2/2 | |
| 7.13.3.1 | HEAD | /subscriptions/Query Subscriptions | 0 | 0 | 0/1 | 0/2 | limit 204 400 |
| 7.13.3.2 | POST | /subscriptions/Create Subscription | 140 | 129 | 0/0 | 3/3 | |
| 7.14.3.1 | GET | /subscriptions/{subscriptionId}Retrieve Subscription | 30 | 30 | 0/0 | 3/3 | |
| 7.14.3.1 | HEAD | /subscriptions/{subscriptionId}Retrieve Subscription | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 7.14.3.2 | PATCH | /subscriptions/{subscriptionId}Update Subscription | 15 | 14 | 0/0 | 3/3 | |
| 7.14.3.3 | DELETE | /subscriptions/{subscriptionId}Delete Subscription | 131 | 123 | 0/0 | 3/3 | |
| 7.15.3.1 | GET | /types/Retrieve Available Entity Types / Retrieve Details of Available Entity Types | 5 | 5 | 1/1 | 2/2 | |
| 7.15.3.1 | HEAD | /types/Retrieve Available Entity Types / Retrieve Details of Available Entity Types | 0 | 0 | 0/1 | 0/2 | details 204 400 |
| 7.16.3.1 | GET | /types/{type}Retrieve Available Entity Type Information | 3 | 3 | 0/0 | 2/3 | 400 |
| 7.16.3.1 | HEAD | /types/{type}Retrieve Available Entity Type Information | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 7.17.3.1 | GET | /attributes/Retrieve Available Attributes / Retrieve Details of Available Attributes | 4 | 4 | 1/1 | 1/2 | 400 |
| 7.17.3.1 | HEAD | /attributes/Retrieve Available Attributes / Retrieve Details of Available Attributes | 0 | 0 | 0/1 | 0/2 | details 204 400 |
| 7.18.3.1 | GET | /attributes/{attrId}Retrieve Available Attribute Information | 3 | 3 | 0/0 | 2/3 | 400 |
| 7.18.3.1 | HEAD | /attributes/{attrId}Retrieve Available Attribute Information | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 8.2.3.1 | GET | /temporal/entities/Query Temporal Evolution of Entities | 69 | 69 | 20/29 | 2/4 | collation csf entityMap entityMapLifetime expandValues jsonKeys lang scopeQ splitEntities 201 203 |
| 8.2.3.1 | HEAD | /temporal/entities/Query Temporal Evolution of Entities | 0 | 0 | 0/29 | 0/2 | aggrMethods aggrPeriodDuration attrs collation coordinates csf datasetId endTimeAt entityMap entityMapLifetime expandValues geometry geoproperty georel id idPattern jsonKeys lang lastN omit orderBy pick q scopeQ splitEntities timeAt timeproperty timerel type 204 400 |
| 8.2.3.2 | POST | /temporal/entities/Create or Update Temporal Evolution of an Entity | 176 | 113 | 0/0 | 3/4 | 422 |
| 8.3.3.1 | GET | /temporal/entities/{entityId}Retrieve Temporal Evolution of an Entity | 86 | 84 | 10/12 | 3/4 | datasetId lang 203 |
| 8.3.3.1 | HEAD | /temporal/entities/{entityId}Retrieve Temporal Evolution of an Entity | 0 | 0 | 0/12 | 0/3 | aggrMethods aggrPeriodDuration attrs datasetId endTimeAt lang lastN omit pick timeAt timeproperty timerel 204 400 404 |
| 8.3.3.2 | DELETE | /temporal/entities/{entityId}Delete Temporal Evolution of an Entity | 782 | 546 | 0/0 | 3/3 | |
| 8.4.3.1 | POST | /temporal/entities/{entityId}/attrs/Add Attributes to Temporal Evolution of an Entity | 5 | 5 | 0/0 | 3/3 | |
| 8.5.3.1 | DELETE | /temporal/entities/{entityId}/attrs/{attrId}Delete Attribute from Temporal Evolution of an Entity | 7 | 7 | 2/2 | 3/3 | |
| 8.6.3.1 | PATCH | /temporal/entities/{entityId}/attrs/{attrId}/{instanceId}Modify attribute instance from Temporal Evolution of an Entity | 7 | 7 | 0/0 | 3/3 | |
| 8.6.3.2 | DELETE | /temporal/entities/{entityId}/attrs/{attrId}/{instanceId}Delete Attribute instance from Temporal Evolution of an Entity | 7 | 7 | 0/0 | 3/3 | |
| 8.7.3.1 | POST | /temporal/entityOperations/queryQuery Temporal Evolution of Entities | 5 | 5 | 0/0 | 1/3 | 201 400 |
| 9.2.3.1 | GET | /csourceRegistrations/Query Context Source Registrations | 27 | 27 | 12/19 | 2/2 | csf endTimeAt geometryProperty lang omit pick scopeQ |
| 9.2.3.1 | HEAD | /csourceRegistrations/Query Context Source Registrations | 0 | 0 | 0/19 | 0/2 | attrs coordinates csf endTimeAt geometry geometryProperty geoproperty georel id idPattern lang omit pick q scopeQ timeAt timeproperty timerel type 204 400 |
| 9.2.3.2 | POST | /csourceRegistrations/Register Context Source | 211 | 171 | 0/0 | 3/4 | 422 |
| 9.3.3.1 | GET | /csourceRegistrations/{registrationId}Retrieve Context Source Registration | 20 | 20 | 0/0 | 3/3 | |
| 9.3.3.1 | HEAD | /csourceRegistrations/{registrationId}Retrieve Context Source Registration | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 9.3.3.2 | PATCH | /csourceRegistrations/{registrationId}Update Context Source Registration | 10 | 10 | 0/0 | 3/3 | |
| 9.3.3.3 | DELETE | /csourceRegistrations/{registrationId}Delete Context Source Registration | 206 | 169 | 0/0 | 3/3 | |
| 9.4.3.1 | GET | /csourceSubscriptions/Create Context Source Registration Subscription | 11 | 11 | 0/0 | 1/3 | 201 409 |
| 9.4.3.1 | HEAD | /csourceSubscriptions/Create Context Source Registration Subscription | 0 | 0 | 0/0 | 0/3 | 204 400 409 |
| 9.4.3.2 | POST | /csourceSubscriptions/Query Context Source Registration Subscriptions | 59 | 44 | 0/1 | 1/2 | limit 200 |
| 9.5.3.1 | GET | /csourceSubscriptions/{subscriptionId}Retrieve Context Source Registration Subscription | 13 | 13 | 0/0 | 3/3 | |
| 9.5.3.1 | HEAD | /csourceSubscriptions/{subscriptionId}Retrieve Context Source Registration Subscription | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 9.5.3.2 | PATCH | /csourceSubscriptions/{subscriptionId}Update Context Source Registration Subscription | 11 | 11 | 0/0 | 3/3 | |
| 9.5.3.3 | DELETE | /csourceSubscriptions/{subscriptionId}Delete Context Source Registration Subscription | 55 | 42 | 0/0 | 3/3 | |
| 10.2.3.1 | GET | /jsonldContexts/List @contexts | 43 | 42 | 2/2 | 2/2 | |
| 10.2.3.1 | HEAD | /jsonldContexts/List @contexts | 0 | 0 | 0/2 | 0/2 | details kind 204 400 |
| 10.2.3.2 | POST | /jsonldContexts/Add @context | 39 | 27 | 0/0 | 2/2 | |
| 10.3.3.1 | GET | /jsonldContexts/{contextId}Serve @context | 30 | 23 | 1/2 | 2/4 | core 400 422 |
| 10.3.3.1 | HEAD | /jsonldContexts/{contextId}Serve @context | 0 | 0 | 0/2 | 0/4 | core details 204 400 404 422 |
| 10.3.3.2 | DELETE | /jsonldContexts/{contextId}Delete and Reload @context | 112 | 46 | 1/1 | 4/4 | |
| 11.2.3.1 | GET | /entityMaps/Create EntityMap for Query Entities | 0 | 0 | 0/0 | 0/4 | 201 203 400 501 |
| 11.2.3.1 | HEAD | /entityMaps/Create EntityMap for Query Entities | 0 | 0 | 0/0 | 0/3 | 204 400 501 |
| 11.2.3.2 | POST | /entityMaps/Create EntityMap for Query Entities | 0 | 0 | 0/0 | 0/2 | 201 400 |
| 11.3.3.1 | GET | /temporal/entityMapsCreate EntityMap for Query Temporal Evolution of Entities | 0 | 0 | 0/0 | 0/2 | 201 400 |
| 11.3.3.1 | HEAD | /temporal/entityMapsCreate EntityMap for Query Temporal Evolution of Entities | 0 | 0 | 0/0 | 0/2 | 204 400 |
| 11.3.3.2 | POST | /temporal/entityMapsCreate EntityMap for Query Temporal Evolution of Entities | 0 | 0 | 0/0 | 0/2 | 201 400 |
| 11.4.3.1 | GET | /entityMaps/{entityMapId}Retrieve EntityMap | 0 | 0 | 0/0 | 0/3 | 200 400 404 |
| 11.4.3.1 | HEAD | /entityMaps/{entityMapId}Retrieve EntityMap | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 11.4.3.2 | PATCH | /entityMaps/{entityMapId}Update EntityMap | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 11.4.3.3 | DELETE | /entityMaps/{entityMapId}Delete EntityMap | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 12.2.3.1 | GET | /info/sourceIdentityRetrieve Context Source Identity Information | 0 | 0 | 0/0 | 0/2 | 200 501 |
| 12.2.3.1 | HEAD | /info/sourceIdentityRetrieve Context Source Identity Information | 0 | 0 | 0/0 | 0/2 | 204 501 |
| 13.2.3.1 | POST | /snapshots/Create Snapshot | 0 | 0 | 0/0 | 0/3 | 202 400 409 |
| 13.2.3.2 | DELETE | /snapshots/Purge Snapshots | 0 | 0 | 0/1 | 0/4 | q 204 207 400 404 |
| 13.3.3.1 | GET | /snapshots/{snapshotId}Retrieve Snapshot Status | 0 | 0 | 0/0 | 0/3 | 200 400 404 |
| 13.3.3.1 | HEAD | /snapshots/{snapshotId}Retrieve Snapshot Status | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 13.3.3.2 | PATCH | /snapshots/{snapshotId}Update Snapshot Status | 0 | 0 | 0/0 | 0/3 | 200 400 404 |
| 13.3.3.3 | DELETE | /snapshots/{snapshotId}Delete Snapshot | 0 | 0 | 0/0 | 0/3 | 204 400 404 |
| 13.4.3.1 | POST | /snapshots/{snapshotId}/cloneClone Snapshot | 0 | 0 | 0/0 | 0/4 | 202 400 404 409 |
The broker was built with coverage instrumentation. For comparison, the same build also ran its own functional tests (952 cases). That build had a few files fewer, so the percentages of the last column have slightly smaller denominators.
| Code | Lines | Test suite | Broker's own tests |
|---|---|---|---|
| NGSI-LD: the API, the HTTP binding, the database layer | 38 639 | 58.0 % | 83.9 % |
| Broker-specific features, which no NGSI-LD test can reach | 11 986 | 7.9 % | 70.1 % |
| All | 50 625 | 46.1 % | 81.4 % |
The broker's own tests know its code, and they stop at 84 % of the NGSI-LD part. Most of the remaining 16 % is error handling that a test from outside cannot trigger: a database that fails, memory running out, internal errors. A suite that tests only through the API can realistically reach about 80 %, up from 58 %. API coverage is the better measure of the suite: it does not depend on any broker, and its target really is 100 %.
The functional areas where the broker's own tests reach far more code than the suite does:
The measurement is now a script in the suite, on the branch
feat/api-coverage.
It reads only the spec and a run's output.xml, so any broker gives the same operation and
parameter counts. It takes about 3 seconds on a full run.
python3 scripts/api_coverage/api_coverage.py results/output.xml # the table
python3 scripts/api_coverage/api_coverage.py results/output.xml --json develop.json # keep it
python3 scripts/api_coverage/api_coverage.py branch/output.xml --baseline develop.json # what a branch adds
The inventory comes from the Markdown of TS 104-176 and records the spec commit it was built from. When a new
version of the spec comes out, build_inventory.py builds it again.
TS 104-176 §5: a HEAD request functions as the GET request would, omits the body, and answers
204 No Content on success. The branch
feat/head-test-cases
adds 37 Test Cases in CommonBehaviours/CommonResponses/VerifyHEAD:
061_01: 204 and no body, on each of the 19 resources that have GET/HEAD and already have Test
Cases.061_02: 18 errors the GET answers, the same 400 or 404 and no body.Measured with --baseline: operations 50 → 69, URL parameters 84 → 89,
status codes 135 → 172. HEAD on /entityMaps/, /temporal/entityMaps,
/entityMaps/{entityMapId} and /snapshots/{snapshotId} comes with step 3, together
with the first tests of those resources.
Retrieve Attribute, the Attribute value (§7.5, §7.6), the context source identity (§12), EntityMaps (§11) and Snapshots (§13): the happy path of each, plus the 400, 404 and 409 cases. That is about 80–100 Test Cases, and operations reach 90 of 90.
Go through the 191 parameters and the 151 status codes that are never exercised, one operation at a time, starting with Query Entities and the temporal queries. That is about 250 Test Cases. This step moves the code coverage most, from 58 % towards 80 %.
A second inventory from TS 104-175 and TS 104-243 for what the resource tables do not show:
timerel values.The method is the same, but these are read from the request and response bodies, not from the URL.
All in all: about 350–400 new Test Cases on top of today's 1 112, with each batch measured as it comes in.
The schedule and the list of MRs are in the proposal to the TTF.
develop plus the open fixes, 1 112 Test Cases, all passing.forge.etsi.org/rep/data/specifications/ts-104-176, commit
1446ed11 (2026-06-30).--coverage -O0; the report was produced
with gcovr.GET/HEAD section of the spec counts as two operations, as the two methods are tested
separately. A HEAD's status codes are 204 and the GET's errors.