Certificate Management — Developer Guide
This guide covers running the Certificate Management TCK against a provider: configuring the run, reading the results, and fixing what fails. For how the suite is built, see the Architecture Guide. For what each check asserts, see CCM Conformity Testing.
Prerequisites
- Python 3.12+ and the CLI:
pip install --pre tractusx-testlab. In a checkout of this repository, runpoetry installand putpoetry runin front of everytestlabcommand below. - An engine-side connector that TestLab drives through its management API. TestLab uses it to negotiate with the provider.
- The provider under test: its connector's DSP endpoint and participant id, a CCMAPI offer under the CX-0135 usage policy, and the CCMAPI behind it. The Business Guide has the full list.
- Inbound reach. During a run, TestLab serves mock endpoints on port
8100(the engine settingserver_port). Send Feedback Notification waits for the provider to call one of them, so the provider must be able to reach this host on that port.
1. Check the suite
validate takes the manifest (index.yaml), never a single test file. It checks the manifest and all four tests against the v1-alpha syntax and the registered steps.
To see what a run will ask for, compile the suite and inspect the package:
testlab compile docs/examples/certificate-management-v2/raw/index.yaml --output ccm.tck
testlab inspect ccm.tck --variables --infrastructure
VARIABLES
ID Source Scope Type
sut_counter_party_id input sut string
sut_counter_party_address input sut string
ccm_usage_policy value — object
INFRASTRUCTURE
Capability Required Standard
engine.connector True CX-0018
sut.connector True CX-0018
Add --manifest for the package identity and checksum, or --json for machine-readable output.
2. Supply what the run needs
A run needs three groups of values. You can put all of them in one run config:
| Group | Keys | Where the need comes from |
|---|---|---|
| Infrastructure bindings | infrastructure.engine.connector.management_url, infrastructure.engine.connector.participant_id (plus api_key if the management API needs one), infrastructure.sut.connector.dsp_url, infrastructure.sut.connector.participant_id |
infrastructure: in the manifest: both connectors are required |
| Declared inputs | sut_counter_party_id, sut_counter_party_address |
env.variables with source: input |
| Test data values | consumer_bpn, provider_bpn, certificate_type, location_bpns, testlab_dsp_url, request_id, document_id |
${{ env.… }} references inside the three JSON files in raw/testdata/ |
# run-config.yaml
variables:
# Engine side: the connector TestLab operates (management_url includes the management path)
infrastructure.engine.connector.management_url: https://testlab-edc.example.com/management
infrastructure.engine.connector.api_key: <engine-api-key>
infrastructure.engine.connector.participant_id: BPNL000000000TLB
# SUT side: the provider's connector
infrastructure.sut.connector.dsp_url: https://provider-edc.example.com/api/v1/dsp
infrastructure.sut.connector.participant_id: BPNL000000000001
# Declared inputs: give the same connector as the SUT binding
sut_counter_party_id: BPNL000000000001
sut_counter_party_address: https://provider-edc.example.com/api/v1/dsp
# Values the request and notification bodies read
consumer_bpn: BPNL000000000TLB
provider_bpn: BPNL000000000001
certificate_type: iso9001
location_bpns: BPNS000000000001
testlab_dsp_url: https://testlab-edc.example.com/api/v1/dsp
request_id: 0b133a08-b03a-4f4f-b0f4-55bbbcf088f9
document_id: <documentId of an existing certificate at the provider>
The bindings do not have to live in the run config. testlab.config.yaml and TESTLAB_* environment variables (for example TESTLAB_SUT_CONNECTOR_DSP_URL) are also read; see Infrastructure Bindings. A --var KEY=VALUE flag overrides the file for one value.
Two things the run does not check up front
sut_counter_party_*repeats the SUT binding. The connector steps would fall back toinfrastructure.sut.connector.*without it, but the suite passes it explicitly. Keep the two in agreement.- The test data keys are not declared in the manifest. The run does not ask for them before it starts. If one is missing, the step that reads the body fails with
'env.<key>' resolves to nothing.
3. Run it
run compiles the manifest into a temporary package and validates it. Before the first step, it refuses to start if any declared input or required binding is missing, and it lists everything that is missing at once. It then starts the mock server and runs the four tests in manifest order. No test is skippable, so every test runs.
4. Read the results
| Output | Where | What it holds |
|---|---|---|
| Console | stdout | One box per test with each step's PASS/FAIL and duration, failed checks with expected against actual, then a TCK run summary |
| Exit code | shell | 0 when every test passed, 1 otherwise, including a run refused before it started |
| Transcript | ./logs/<date>/<time>_<run-id>.log (--logs-dir) |
The console output of the run, kept verbatim |
| Execution trace | ./data/<date>/<time>_<run-id>.jsonl (--data-dir) |
CloudEvents, one per line: every step's outputs, checks, and the HTTP requests and responses the run made. See Execution Logs |
Failing lines name the test id, the phase and step id, and the step: send-feedback-notification[setup:mock_receive_ack]:mock/api. A test whose checks never ran is reported as WARNING: N declared assertion(s) were never evaluated. It is not a pass.
5. Fix what fails
| Message | Cause | Fix |
|---|---|---|
This TCK needs 2 input variable(s) that were not supplied |
sut_counter_party_id / sut_counter_party_address missing |
Add them under variables: or pass --var |
This TCK requires infrastructure that is not fully bound: engine.connector, sut.connector |
A binding key is missing; the message lists each key and its TESTLAB_* name |
Set the listed keys |
'env.consumer_bpn' resolves to nothing |
A test data value is missing | Add every key from the test data row in step 2 |
It was not possible to get the catalog from the EDC provider! Response code: [404] |
The engine connector's management_url is wrong. It must include the management path. |
Correct infrastructure.engine.connector.management_url |
no offer from <dsp_url> is made under a policy this step accepts |
The provider has no CCMAPI offer matching the filters, or the offer's policy is not exactly the CX-0135 usage policy. The message lists the constraints that differ. | Fix the provider's asset or policy. A subset or superset of the policy does not match. |
Expected 200, got … on request_certificate, send_status_notification or send_unknown_cert_type |
The data-plane call reached the provider's CCMAPI and was refused | Check the provider logs. The request body is in the execution trace. |
Expected 'REJECTED', got … on send_unknown_cert_type |
The provider accepted an unknown certificate type | Validate certificateType and answer with requestStatus: REJECTED |
Timed out after 60.0s waiting for POST /companycertificate/notification/receive |
The provider never sent the acknowledgement, or cannot reach this host on port 8100 | Check the provider's outbound call and the network path to the engine |
Check wording comes from the validation operators. For problems that are not specific to this suite (unknown step ids, empty returns:, mocks never hit), see Debugging.
The local SUT stub
stubs/ccm-sut/ holds a FastAPI stub that imitates a connector and a CCMAPI service. It does not satisfy the current suite. Its catalog answers when infrastructure.engine.connector.management_url is http://localhost:8090/api/v1/dsp/management. However, it offers no CCMAPI asset under the CX-0135 usage policy, so all four tests stop at their first step. Its own run-config.yaml also uses variable names that the suite no longer declares. Use the stub to check your wiring, not as a conformant provider.