refactor: name draft diagnostic codes
This commit is contained in:
@@ -14,6 +14,9 @@ from wf_core.models.schemas import NodeDef
|
|||||||
from .models import WorkflowDraftWorkspace, summarize_draft_workspace
|
from .models import WorkflowDraftWorkspace, summarize_draft_workspace
|
||||||
from .store import DraftWorkspaceConflictError, DraftWorkspaceStore
|
from .store import DraftWorkspaceConflictError, DraftWorkspaceStore
|
||||||
|
|
||||||
|
WORKSPACE_EXISTS_CODE = "workspace_exists"
|
||||||
|
REVISION_CONFLICT_CODE = "revision_conflict"
|
||||||
|
|
||||||
JsonObject = dict[str, Any]
|
JsonObject = dict[str, Any]
|
||||||
JsonPatch = list[dict[str, Any]]
|
JsonPatch = list[dict[str, Any]]
|
||||||
NodeDefsForDraft = Callable[[JsonObject], Sequence[NodeDef]]
|
NodeDefsForDraft = Callable[[JsonObject], Sequence[NodeDef]]
|
||||||
@@ -47,7 +50,7 @@ def create_draft_workspace(
|
|||||||
except DraftWorkspaceConflictError as exc:
|
except DraftWorkspaceConflictError as exc:
|
||||||
return _conflict_payload(
|
return _conflict_payload(
|
||||||
exc.workspace,
|
exc.workspace,
|
||||||
code="workspace_exists",
|
code=WORKSPACE_EXISTS_CODE,
|
||||||
message=f"draft workspace {workspace_id!r} already exists",
|
message=f"draft workspace {workspace_id!r} already exists",
|
||||||
)
|
)
|
||||||
return summarize_draft_workspace(workspace)
|
return summarize_draft_workspace(workspace)
|
||||||
@@ -139,7 +142,7 @@ def _revision_conflict_payload(
|
|||||||
) -> JsonObject:
|
) -> JsonObject:
|
||||||
return _conflict_payload(
|
return _conflict_payload(
|
||||||
workspace,
|
workspace,
|
||||||
code="revision_conflict",
|
code=REVISION_CONFLICT_CODE,
|
||||||
message=(
|
message=(
|
||||||
f"workspace {workspace.id!r} is at revision "
|
f"workspace {workspace.id!r} is at revision "
|
||||||
f"{workspace.revision}, not {expected_revision}"
|
f"{workspace.revision}, not {expected_revision}"
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ from wf_core.validation.issues import ValidationIssue, ValidationIssueCode
|
|||||||
from .adapter import build_workflow_from_draft
|
from .adapter import build_workflow_from_draft
|
||||||
from .models import WorkflowDraft
|
from .models import WorkflowDraft
|
||||||
|
|
||||||
|
DRAFT_INVALID_CODE = "draft_invalid"
|
||||||
|
PATCH_INVALID_CODE = "patch_invalid"
|
||||||
|
DRAFT_NOT_OBJECT_CODE = "draft_not_object"
|
||||||
|
UNKNOWN_OUTCOME_CODE = "unknown_outcome"
|
||||||
|
|
||||||
JsonObject = dict[str, Any]
|
JsonObject = dict[str, Any]
|
||||||
JsonPatch = list[dict[str, Any]]
|
JsonPatch = list[dict[str, Any]]
|
||||||
OutcomeLookup = Callable[[str], tuple[str, ...] | None]
|
OutcomeLookup = Callable[[str], tuple[str, ...] | None]
|
||||||
@@ -89,7 +94,7 @@ def patch_workflow_draft(
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
return _invalid_result(
|
return _invalid_result(
|
||||||
DraftDiagnostic(
|
DraftDiagnostic(
|
||||||
code="patch_invalid",
|
code=PATCH_INVALID_CODE,
|
||||||
path="patch",
|
path="patch",
|
||||||
message=str(exc),
|
message=str(exc),
|
||||||
)
|
)
|
||||||
@@ -97,7 +102,7 @@ def patch_workflow_draft(
|
|||||||
if not isinstance(patched, dict):
|
if not isinstance(patched, dict):
|
||||||
return _invalid_result(
|
return _invalid_result(
|
||||||
DraftDiagnostic(
|
DraftDiagnostic(
|
||||||
code="draft_not_object",
|
code=DRAFT_NOT_OBJECT_CODE,
|
||||||
path="",
|
path="",
|
||||||
message="patched draft must be a JSON object",
|
message="patched draft must be a JSON object",
|
||||||
)
|
)
|
||||||
@@ -124,12 +129,12 @@ def _diagnostic_from_exception(exc: Exception) -> DraftDiagnostic:
|
|||||||
if isinstance(exc, ValidationError):
|
if isinstance(exc, ValidationError):
|
||||||
error = exc.errors()[0]
|
error = exc.errors()[0]
|
||||||
return DraftDiagnostic(
|
return DraftDiagnostic(
|
||||||
code="draft_invalid",
|
code=DRAFT_INVALID_CODE,
|
||||||
path=_format_location(error["loc"]),
|
path=_format_location(error["loc"]),
|
||||||
message=error["msg"],
|
message=error["msg"],
|
||||||
)
|
)
|
||||||
return DraftDiagnostic(
|
return DraftDiagnostic(
|
||||||
code="draft_invalid",
|
code=DRAFT_INVALID_CODE,
|
||||||
path="",
|
path="",
|
||||||
message=str(exc),
|
message=str(exc),
|
||||||
)
|
)
|
||||||
@@ -237,7 +242,7 @@ def _validate_known_outcomes(
|
|||||||
for outcome in route_map:
|
for outcome in route_map:
|
||||||
if outcome not in known_outcomes:
|
if outcome not in known_outcomes:
|
||||||
return DraftDiagnostic(
|
return DraftDiagnostic(
|
||||||
code="unknown_outcome",
|
code=UNKNOWN_OUTCOME_CODE,
|
||||||
path=f"routes.{step_id}.{outcome}",
|
path=f"routes.{step_id}.{outcome}",
|
||||||
step_id=step_id,
|
step_id=step_id,
|
||||||
message=(
|
message=(
|
||||||
|
|||||||
@@ -165,6 +165,51 @@ def test_explain_related_docs_do_not_point_to_planning_artifacts() -> None:
|
|||||||
assert "docs/superpowers/" not in related_doc
|
assert "docs/superpowers/" not in related_doc
|
||||||
|
|
||||||
|
|
||||||
|
def test_explain_registry_covers_core_validation_codes() -> None:
|
||||||
|
from wf_core.validation.issues import ValidationIssueCode
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ValidationIssueCode.INVALID_SOURCE_PATH.value,
|
||||||
|
ValidationIssueCode.INVALID_DESTINATION_PATH.value,
|
||||||
|
ValidationIssueCode.UNKNOWN_EDGE_DESTINATION.value,
|
||||||
|
ValidationIssueCode.UNDECLARED_EDGE_OUTCOME.value,
|
||||||
|
ValidationIssueCode.MISSING_OUTCOME_EDGE.value,
|
||||||
|
}
|
||||||
|
registry_codes = {
|
||||||
|
entry.code for entry in DEFAULT_EXPLAIN_REGISTRY.list_full_entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert expected <= registry_codes
|
||||||
|
|
||||||
|
|
||||||
|
def test_explain_unknown_edge_destination_mentions_forward_route_repair() -> None:
|
||||||
|
card = DEFAULT_EXPLAIN_REGISTRY.get("unknown_edge_destination")
|
||||||
|
|
||||||
|
text = "\n".join(card.how_to_fix)
|
||||||
|
|
||||||
|
assert "wf draft handle" in text
|
||||||
|
assert "wf draft branch" in text
|
||||||
|
assert "target step first" in text.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_explain_registry_uses_exported_draft_codes() -> None:
|
||||||
|
from wf_artifacts.draft_workspaces.api import REVISION_CONFLICT_CODE
|
||||||
|
from wf_artifacts.drafts.api import (
|
||||||
|
DRAFT_INVALID_CODE,
|
||||||
|
PATCH_INVALID_CODE,
|
||||||
|
UNKNOWN_OUTCOME_CODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
registry_codes = {
|
||||||
|
entry.code for entry in DEFAULT_EXPLAIN_REGISTRY.list_full_entries()
|
||||||
|
}
|
||||||
|
|
||||||
|
assert DRAFT_INVALID_CODE in registry_codes
|
||||||
|
assert PATCH_INVALID_CODE in registry_codes
|
||||||
|
assert UNKNOWN_OUTCOME_CODE in registry_codes
|
||||||
|
assert REVISION_CONFLICT_CODE in registry_codes
|
||||||
|
|
||||||
|
|
||||||
def test_explain_related_doc_files_exist() -> None:
|
def test_explain_related_doc_files_exist() -> None:
|
||||||
repo_root = Path(__file__).resolve().parents[2]
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
for entry in DEFAULT_EXPLAIN_REGISTRY.list_full_entries():
|
for entry in DEFAULT_EXPLAIN_REGISTRY.list_full_entries():
|
||||||
|
|||||||
Reference in New Issue
Block a user