Files
lda-wf/tests/wf_cli/test_artifacts.py
T
2026-06-15 22:37:59 +07:00

245 lines
6.7 KiB
Python

from __future__ import annotations
import json
from dataclasses import dataclass
from typing import Any
from typer.testing import CliRunner
from wf_cli.app import app
class _ArtifactHandlers:
def __init__(self) -> None:
self.calls: list[tuple[str, int]] = []
async def inspect_artifact(
self,
*,
artifact_id: str,
version: int,
) -> dict[str, Any]:
self.calls.append((artifact_id, version))
return {"id": artifact_id, "version": version}
async def delete_artifact(
self,
*,
artifact_id: str,
version: int,
) -> dict[str, Any]:
self.calls.append((artifact_id, version))
return {
"artifact_id": artifact_id,
"version": version,
"deleted": True,
"blocked_by_deployments": [],
}
async def create_artifact_from_plan(
self,
*,
artifact_id: str,
version: int,
title: str,
plan: dict[str, Any],
outcomes: tuple[str, ...],
kind: str = "workflow",
description: str | None = None,
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
self.calls.append((artifact_id, version))
return {
"artifact_id": artifact_id,
"version": version,
"title": title,
"plan_name": plan["name"],
"outcomes": list(outcomes),
"kind": kind,
"source_bindings": source_bindings,
}
class _BlockedArtifactHandlers:
def __init__(self) -> None:
self.calls: list[tuple[str, int]] = []
async def delete_artifact(
self,
*,
artifact_id: str,
version: int,
) -> dict[str, Any]:
self.calls.append((artifact_id, version))
return {
"artifact_id": artifact_id,
"version": version,
"deleted": False,
"blocked_by_deployments": ["echo.default"],
}
@dataclass(frozen=True)
class _Context:
handlers: _ArtifactHandlers
@dataclass(frozen=True)
class _BlockedContext:
handlers: _BlockedArtifactHandlers
def test_artifact_inspect_accepts_version_option(monkeypatch) -> None:
handlers = _ArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _Context(handlers=handlers),
)
result = CliRunner().invoke(
app,
["artifact", "inspect", "demo_artifact", "--version", "2"],
)
assert result.exit_code == 0, result.output
assert json.loads(result.output) == {"id": "demo_artifact", "version": 2}
assert handlers.calls == [("demo_artifact", 2)]
def test_artifact_inspect_keeps_positional_version(monkeypatch) -> None:
handlers = _ArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _Context(handlers=handlers),
)
result = CliRunner().invoke(app, ["artifact", "inspect", "demo_artifact", "3"])
assert result.exit_code == 0, result.output
assert json.loads(result.output) == {"id": "demo_artifact", "version": 3}
assert handlers.calls == [("demo_artifact", 3)]
def test_artifact_delete_requires_confirm(monkeypatch) -> None:
handlers = _ArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _Context(handlers=handlers),
)
result = CliRunner().invoke(app, ["artifact", "delete", "echo", "1"])
assert result.exit_code != 0
assert "confirm" in result.output.lower()
def test_artifact_delete_confirmed_succeeds(monkeypatch) -> None:
handlers = _ArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _Context(handlers=handlers),
)
result = CliRunner().invoke(app, ["artifact", "delete", "echo", "1", "--confirm"])
assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert payload["artifact_id"] == "echo"
assert payload["version"] == 1
assert payload["deleted"] is True
assert handlers.calls == [("echo", 1)]
def test_artifact_delete_blocked_returns_blocker_ids(monkeypatch) -> None:
handlers = _BlockedArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _BlockedContext(handlers=handlers),
)
result = CliRunner().invoke(app, ["artifact", "delete", "echo", "1", "--confirm"])
assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert payload["deleted"] is False
assert payload["blocked_by_deployments"] == ["echo.default"]
def test_artifact_create_from_plan_reads_yaml(monkeypatch, tmp_path) -> None:
handlers = _ArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _Context(handlers=handlers),
)
plan_file = tmp_path / "plan.yaml"
plan_file.write_text(
"""
name: yaml_plan
input_schema: {type: object, properties: {}}
state_schema: {type: object, properties: {}}
output_schema: {type: object, properties: {}}
outcomes: [ok]
start: __end__
nodes: []
edges: []
""",
encoding="utf-8",
)
result = CliRunner().invoke(
app,
[
"artifact",
"create-from-plan",
str(plan_file),
"--artifact",
"yaml_artifact",
"--version",
"1",
"--title",
"YAML Artifact",
"--outcome",
"ok",
"--binding",
"local.ops=local.ops",
],
)
assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert payload["artifact_id"] == "yaml_artifact"
assert payload["plan_name"] == "yaml_plan"
assert payload["source_bindings"] == {"local.ops": "local.ops"}
def test_artifact_create_from_plan_rejects_non_object_yaml(
monkeypatch, tmp_path
) -> None:
handlers = _ArtifactHandlers()
monkeypatch.setattr(
"wf_cli.commands.artifacts.load_cli_context",
lambda _ctx: _Context(handlers=handlers),
)
plan_file = tmp_path / "plan.yaml"
plan_file.write_text("- not\n- object\n", encoding="utf-8")
result = CliRunner().invoke(
app,
[
"artifact",
"create-from-plan",
str(plan_file),
"--artifact",
"bad",
"--version",
"1",
"--title",
"Bad",
],
)
assert result.exit_code != 0
assert "structured file must contain an object" in result.output.lower()