158 lines
4.5 KiB
Python
158 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, cast
|
|
|
|
from wf_client.capabilities import CapabilityResult, RemoteCapability
|
|
from wf_client.deployments import Deployment, DeploymentValidation
|
|
from wf_client.protocols import WorkflowClientPort
|
|
from wf_client.runs import Run, TracePage
|
|
from wf_client.workflows import (
|
|
ArtifactRef,
|
|
WorkflowDiagnostic,
|
|
WorkflowValidation,
|
|
)
|
|
from wf_core import ValidationReport
|
|
from wf_platform import CapabilityRef
|
|
|
|
|
|
class _Port:
|
|
"""A port that records accidental representation-time remote operations."""
|
|
|
|
def __init__(self) -> None:
|
|
self.calls: list[tuple[str, dict[str, Any]]] = []
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
async def operation(**params: Any) -> object:
|
|
self.calls.append((name, params))
|
|
return {}
|
|
|
|
return operation
|
|
|
|
|
|
def _port() -> _Port:
|
|
return _Port()
|
|
|
|
|
|
def test_capability_html_repr_is_bounded_and_does_not_call_port() -> None:
|
|
port = _port()
|
|
remote = RemoteCapability(
|
|
_port=cast(WorkflowClientPort, port),
|
|
ref=CapabilityRef.parse("app.default.search"),
|
|
qualified_name="app.default.search",
|
|
description="Search things <carefully>",
|
|
input_schema={"type": "object", "properties": {"query": {"type": "string"}}},
|
|
output_schema={"type": "object", "properties": {"items": {"type": "array"}}},
|
|
outcomes=("ok",),
|
|
is_async=False,
|
|
)
|
|
|
|
rendered = remote._repr_html_()
|
|
|
|
assert "app.default.search" in rendered
|
|
assert "input schema" in rendered.lower()
|
|
assert "<carefully>" in rendered
|
|
assert port.calls == []
|
|
|
|
|
|
def test_rich_representations_bound_large_values_and_redact_secret_like_fields() -> (
|
|
None
|
|
):
|
|
port = _port()
|
|
result = CapabilityResult(
|
|
outcome="ok",
|
|
output={"token": "do-not-show", "items": ["x" * 400] * 20},
|
|
diagnostics=(),
|
|
)
|
|
run = Run(
|
|
_port=cast(WorkflowClientPort, port),
|
|
run_id="run-1",
|
|
deployment_id="deployment-1",
|
|
status="completed",
|
|
outcome="ok",
|
|
output=result.output,
|
|
interrupt=None,
|
|
diagnostics=(),
|
|
trace_count=1000,
|
|
)
|
|
|
|
rendered = repr(run)
|
|
html = run._repr_html_()
|
|
|
|
assert len(rendered) <= 1_201
|
|
assert len(html) <= 2_500
|
|
assert "do-not-show" not in rendered
|
|
assert "do-not-show" not in html
|
|
assert "1000 frames" in rendered
|
|
assert port.calls == []
|
|
|
|
|
|
def test_repr_redacts_only_exact_sensitive_keys_in_snake_and_camel_case() -> None:
|
|
result = CapabilityResult(
|
|
outcome="ok",
|
|
output={
|
|
"apiKey": "hide-me",
|
|
"accessToken": "hide-me-too",
|
|
"setCookie": "hide-me-three",
|
|
"tokenCount": 3,
|
|
"authorizationStatus": "ok",
|
|
"secretary": "safe",
|
|
},
|
|
diagnostics=(),
|
|
)
|
|
|
|
rendered = repr(result)
|
|
|
|
assert "hide-me" not in rendered
|
|
assert "hide-me-too" not in rendered
|
|
assert "hide-me-three" not in rendered
|
|
assert '"tokenCount": 3' in rendered
|
|
assert '"authorizationStatus": "ok"' in rendered
|
|
assert '"secretary": "safe"' in rendered
|
|
|
|
|
|
def test_repr_does_not_materialize_an_unbounded_iterable() -> None:
|
|
class ExplodingIterable:
|
|
def __iter__(self):
|
|
for index in range(10_000):
|
|
if index > 8:
|
|
raise AssertionError("repr consumed too many values")
|
|
yield index
|
|
|
|
result = CapabilityResult("ok", {"values": ExplodingIterable()}, ())
|
|
|
|
rendered = repr(result)
|
|
|
|
assert "more items" in rendered
|
|
|
|
|
|
def test_all_rich_objects_render_without_port_access() -> None:
|
|
raw_port = _port()
|
|
port = cast(WorkflowClientPort, raw_port)
|
|
diagnostic = WorkflowDiagnostic("error", "bad", "state.x", "broken")
|
|
local = ValidationReport()
|
|
objects = [
|
|
ArtifactRef("artifact", 1),
|
|
diagnostic,
|
|
WorkflowValidation(local, "valid", (diagnostic,)),
|
|
CapabilityResult("ok", {"value": 1}, ()),
|
|
DeploymentValidation("deployment", "artifact", 1, "runnable", ()),
|
|
TracePage(0, 25, (), False, 0),
|
|
]
|
|
|
|
for value in objects:
|
|
assert repr(value)
|
|
assert value._repr_html_()
|
|
assert repr(
|
|
Deployment.from_payload(
|
|
port,
|
|
{
|
|
"id": "deployment",
|
|
"artifact_id": "artifact",
|
|
"artifact_version": 1,
|
|
"bindings": [],
|
|
"drift_policy": "block",
|
|
},
|
|
)
|
|
)
|
|
assert raw_port.calls == []
|