|
17 | 17 | Page, |
18 | 18 | derive_service_origin, |
19 | 19 | is_local_resource_identifier, |
| 20 | + parse_agent_service_document, |
20 | 21 | parse_collection, |
| 22 | + parse_collection_page, |
21 | 23 | parse_collection_search_request, |
22 | 24 | parse_filter_definition, |
| 25 | + parse_filter_definition_page, |
23 | 26 | parse_offering, |
24 | 27 | parse_offering_page, |
25 | 28 | parse_offering_search_request, |
| 29 | + parse_problem_details, |
26 | 30 | parse_problem_response, |
27 | 31 | parse_resource_identity, |
28 | 32 | parse_service_document, |
29 | 33 | parse_sort_definition, |
| 34 | + parse_sort_definition_page, |
30 | 35 | resolve_continuation, |
31 | 36 | resolve_resource_reference, |
32 | 37 | validate_value, |
33 | 38 | ) |
| 39 | +from offering_protocol.core.validation import _normalize_agent_response |
34 | 40 | from offering_protocol.directory.transport import HttpRequest, HttpResponse |
35 | 41 | from offering_protocol.service import CatalogRequest, Request, ServiceBuilder |
36 | 42 |
|
@@ -265,6 +271,25 @@ async def evaluate_case(subject: str, case: dict[str, Any], role: str) -> bool | |
265 | 271 | succeeds(lambda: parse_collection_search_request(json.dumps(case["request"]))) |
266 | 272 | == case["valid"] |
267 | 273 | ) |
| 274 | + if subject == "composition-contract": |
| 275 | + if case.get("operation") == "normalize-agent-response" and role == "agent": |
| 276 | + normalized = _normalize_agent_response(case["document"], case["kind"]) |
| 277 | + _parse_agent_response(normalized, case["kind"]) |
| 278 | + return normalized == case["expected"] |
| 279 | + if case.get("operation") == "validate-advertisement": |
| 280 | + document = _service_document_with_protocols(case["protocols"]) |
| 281 | + return succeeds(lambda: parse_service_document(json.dumps(document))) == case["valid"] |
| 282 | + if case.get("operation") == "filter-advertisement" and role == "agent": |
| 283 | + document = parse_agent_service_document( |
| 284 | + json.dumps(_service_document_with_protocols(case["protocols"])) |
| 285 | + ) |
| 286 | + actual = ( |
| 287 | + document.protocols.model_dump(mode="json", exclude_defaults=True) |
| 288 | + if document.protocols is not None |
| 289 | + else {} |
| 290 | + ) |
| 291 | + return actual == case["expected"] |
| 292 | + return None |
268 | 293 | if subject == "offering-search-contract" and case.get("operation") == "validate-request": |
269 | 294 | return ( |
270 | 295 | succeeds(lambda: parse_offering_search_request(json.dumps(case["request"]))) |
@@ -308,6 +333,40 @@ async def evaluate_case(subject: str, case: dict[str, Any], role: str) -> bool | |
308 | 333 | return None |
309 | 334 |
|
310 | 335 |
|
| 336 | +def _parse_agent_response(document: dict[str, Any], kind: str) -> object: |
| 337 | + encoded = json.dumps(document, separators=(",", ":")) |
| 338 | + parsers: dict[str, Callable[[str], object]] = { |
| 339 | + "service-document": parse_agent_service_document, |
| 340 | + "collection": parse_collection, |
| 341 | + "offering": parse_offering, |
| 342 | + "collection-page": parse_collection_page, |
| 343 | + "offering-page": parse_offering_page, |
| 344 | + "filter-page": parse_filter_definition_page, |
| 345 | + "sort-page": parse_sort_definition_page, |
| 346 | + "problem": parse_problem_details, |
| 347 | + } |
| 348 | + parser = parsers.get(kind) |
| 349 | + if parser is None: |
| 350 | + raise ValueError("Unknown Agent response kind") |
| 351 | + return parser(encoded) |
| 352 | + |
| 353 | + |
| 354 | +def _service_document_with_protocols(protocols: object) -> dict[str, object]: |
| 355 | + return { |
| 356 | + "description": "ODP Python conformance adapter", |
| 357 | + "http": {"endpoint_base": "/odp"}, |
| 358 | + "language": "en", |
| 359 | + "localizations": ["en"], |
| 360 | + "name": "Conformance Service", |
| 361 | + "odp_version": "1.0", |
| 362 | + "operations": [ |
| 363 | + {"authentication": "not-required", "name": "get-offering"}, |
| 364 | + {"authentication": "not-required", "name": "list-offerings"}, |
| 365 | + ], |
| 366 | + "protocols": protocols, |
| 367 | + } |
| 368 | + |
| 369 | + |
311 | 370 | async def evaluate(request: dict[str, Any]) -> dict[str, object]: |
312 | 371 | try: |
313 | 372 | actual = await evaluate_case(request["vector"]["subject"], request["case"], request["role"]) |
|
0 commit comments