from __future__ import annotations from collections.abc import Iterator, Mapping from typing import Any from wf_contract_manifest import generate_manifest from wf_contract_manifest.model import ManifestOperation UNION_RESULTS = { "InspectCapabilityResult", "PatchDraftResult", "ValidateDraftResult", "CompileDraftWorkspaceResult", "CreateArtifactFromWorkspaceResult", } AUTH_SECURITY_COMPONENTS = { "AuthRecordSummaryPayload", "ListAuthRecordsResult", "DeleteAuthRecordResult", "SourceAuthDiagnosisPayload", "SourceDiagnosisResult", } AUTH_METHODS = { "workflow.admin.auth.delete", "workflow.admin.auth.inspect", "workflow.admin.auth.list", "workflow.admin.auth.save", } def _schema_references(value: Any) -> Iterator[str]: if isinstance(value, Mapping): reference = value.get("$ref") if isinstance(reference, str): yield reference for child in value.values(): yield from _schema_references(child) elif isinstance(value, list): for child in value: yield from _schema_references(child) def _reachable_schema_names(schemas: Mapping[str, Any], roots: set[str]) -> set[str]: """Return schema components reachable through local schema references.""" reachable: set[str] = set() pending = list(roots) while pending: name = pending.pop() if name in reachable: continue reachable.add(name) prefix = "#/components/schemas/" for reference in _schema_references(schemas[name]): if reference.startswith(prefix): pending.append(reference.removeprefix(prefix)) return reachable def _structured_strings(value: Any) -> Iterator[str]: if isinstance(value, str): yield value elif isinstance(value, Mapping): for key, child in value.items(): yield str(key) yield from _structured_strings(child) elif isinstance(value, list): for child in value: yield from _structured_strings(child) def _schema_objects(value: Any) -> Iterator[Mapping[str, Any]]: if isinstance(value, Mapping): yield value for child in value.values(): yield from _schema_objects(child) elif isinstance(value, list): for child in value: yield from _schema_objects(child) def _result_component_name(operation: Any) -> str: reference = operation["result"]["schema"]["$ref"] assert isinstance(reference, str) return reference.removeprefix("#/components/schemas/") def test_generates_the_complete_real_workflow_contract() -> None: manifest = generate_manifest() schemas = manifest["components"]["schemas"] assert len(manifest["operations"]) == 72 assert len({operation["method"] for operation in manifest["operations"]}) == 72 assert len(schemas) == 142 assert len(manifest["components"]["errors"]) == 1 assert all( set(operation["result"]["schema"]) == {"$ref"} or operation["method"] == "workflow.draft_workspaces.inspect_authoring_contract" for operation in manifest["operations"] ) inspection = next( operation for operation in manifest["operations"] if operation["method"] == "workflow.draft_workspaces.inspect_authoring_contract" ) assert inspection["result"]["schema"]["anyOf"] == [ {"$ref": "#/components/schemas/AuthoringContractInventoryPayload"}, {"$ref": "#/components/schemas/DraftWorkspaceResult"}, ] assert {name for name in UNION_RESULTS if "anyOf" in schemas[name]} == UNION_RESULTS def test_manifest_preserves_recursive_json_value_binding_contract() -> None: schemas: dict[str, Any] = generate_manifest()["components"]["schemas"] value_schema = schemas["InputValueBinding"]["properties"]["value"] assert value_schema["$ref"] == "#/components/schemas/JsonValue" json_value_schema = schemas["JsonValue"] assert {branch["type"] for branch in json_value_schema["anyOf"]} == { "boolean", "integer", "number", "string", "array", "object", "null", } assert json_value_schema["anyOf"][4]["items"] == { "$ref": "#/components/schemas/JsonValue" } assert json_value_schema["anyOf"][5]["additionalProperties"] == { "$ref": "#/components/schemas/JsonValue" } def test_manifest_separates_recursive_step_inputs_from_workflow_outputs() -> None: manifest = generate_manifest() schemas = manifest["components"]["schemas"] assert { "ArrayExpression", "InputExpression", "InputExpressionBinding", "LiteralExpression", "ObjectExpression", "PathExpression", "StepInputBinding", } <= schemas.keys() input_binding_schema = schemas["InputExpressionBinding"] properties = input_binding_schema.get("properties") assert isinstance(properties, dict) assert properties["expression"] == { "$ref": "#/components/schemas/InputExpression" } expression_schema = schemas["InputExpression"] assert expression_schema["discriminator"] == { "mapping": { "array": "#/components/schemas/ArrayExpression", "literal": "#/components/schemas/LiteralExpression", "object": "#/components/schemas/ObjectExpression", "path": "#/components/schemas/PathExpression", }, "propertyName": "kind", } def operation(method: str) -> ManifestOperation: return next(item for item in manifest["operations"] if item["method"] == method) step_input = operation("workflow.draft_workspaces.set_step_input_bindings") step_output = operation("workflow.draft_workspaces.set_step_output_bindings") bindings_param = next( param for param in step_input["params"] if param["name"] == "bindings" ) assert bindings_param["schema"]["items"] == { "$ref": "#/components/schemas/StepInputBinding" } assert "InputExpressionBinding" not in str(step_output["params"]) def test_manifest_contains_the_two_focused_step_binding_operations() -> None: methods = {operation["method"] for operation in generate_manifest()["operations"]} assert { "workflow.draft_workspaces.set_step_input_bindings", "workflow.draft_workspaces.set_step_output_bindings", } <= methods def test_manifest_contains_the_four_browser_authoring_operations() -> None: methods = {operation["method"] for operation in generate_manifest()["operations"]} assert { "workflow.draft_workspaces.inspect_authoring_contract", "workflow.draft_workspaces.set_contract", "workflow.draft_workspaces.set_start", "workflow.draft_workspaces.set_workflow_output_bindings", } <= methods def test_generated_contract_preserves_security_and_extension_boundaries() -> None: manifest = generate_manifest() schemas = manifest["components"]["schemas"] assert AUTH_SECURITY_COMPONENTS <= schemas.keys() for name in AUTH_SECURITY_COMPONENTS: properties = schemas[name].get("properties", {}) assert isinstance(properties, dict) assert "payload" not in properties result_components = { _result_component_name(operation) for operation in manifest["operations"] if operation["method"] in AUTH_METHODS } assert { operation["method"] for operation in manifest["operations"] } & AUTH_METHODS == AUTH_METHODS reachable = _reachable_schema_names(schemas, result_components) for name in reachable: for schema in _schema_objects(schemas[name]): properties = schema.get("properties", {}) if isinstance(properties, Mapping): assert "payload" not in properties, name assert schemas["SourceDiagnosisResult"]["additionalProperties"] is True assert schemas["RegistryEntryPayload"]["additionalProperties"] is True def test_generated_contract_contains_no_temporary_or_transport_state() -> None: strings = set(_structured_strings(generate_manifest())) assert not any("TemporaryDirectory" in value for value in strings) assert not any("\\Temp\\" in value for value in strings) assert not any("127.0.0.1" in value for value in strings) assert not any("/rpc" in value for value in strings) def test_generated_required_properties_are_declared() -> None: schemas = generate_manifest()["components"]["schemas"] for component_name, component in schemas.items(): for schema in _schema_objects(component): required = schema.get("required") properties = schema.get("properties") if isinstance(required, list) and isinstance(properties, Mapping): assert set(required) <= properties.keys(), component_name