feat: turntable + blend export smoke test coverage
- build_graph_turntable_config: accept render_params kwarg so smoke can use low settings (512x512, 24 frames) without touching golden case defaults - add smoke naming + template functions for turntable and blend - add ensure_workflow_turntable_smoke_resources() — provisions output type (mp4/turntable_video) + workflow graph, binds to Blender_Studio_Schadowcatcher_Anim template - add ensure_workflow_blend_smoke_resources() — provisions output type (blend/blend_asset) + workflow graph, binds to BlenderStudio template - add test_workflow_turntable_smoke() — preflight + dispatch + run tracking - add test_workflow_blend_smoke() — preflight + dispatch + blend node check - 6 new harness unit tests (17 total, all passing), using real asset: cad_file_id 8dd73335 (81113-l_cut.stp, has USD master + GLB) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -227,6 +227,63 @@ def test_workflow_smoke_template_name_maps_supported_modes_to_blenderstudio():
|
|||||||
assert module.workflow_smoke_template_name("turntable") is None
|
assert module.workflow_smoke_template_name("turntable") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_smoke_turntable_output_type_name_includes_variant_and_mode():
|
||||||
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
name = module.smoke_turntable_output_type_name("graph")
|
||||||
|
|
||||||
|
assert "Turntable" in name
|
||||||
|
assert "Graph" in name
|
||||||
|
assert name.startswith("[Workflow Smoke]")
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_smoke_turntable_template_name_returns_animation_template_for_graph():
|
||||||
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
assert module.workflow_smoke_turntable_template_name("graph") == "Blender_Studio_Schadowcatcher_Anim"
|
||||||
|
assert module.workflow_smoke_turntable_template_name("shadow") == "Blender_Studio_Schadowcatcher_Anim"
|
||||||
|
assert module.workflow_smoke_turntable_template_name("legacy") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_smoke_blend_template_name_returns_blenderstudio():
|
||||||
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
assert module.workflow_smoke_blend_template_name() == "BlenderStudio"
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_graph_turntable_config_accepts_render_params_override():
|
||||||
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
config = module.build_graph_turntable_config(
|
||||||
|
execution_mode="graph",
|
||||||
|
render_params={"width": 512, "height": 512, "fps": 12, "duration_s": 2, "samples": 16},
|
||||||
|
)
|
||||||
|
|
||||||
|
turntable_node = next(node for node in config["nodes"] if node["id"] == "turntable")
|
||||||
|
assert turntable_node["params"]["width"] == 512
|
||||||
|
assert turntable_node["params"]["height"] == 512
|
||||||
|
assert turntable_node["params"]["fps"] == 12
|
||||||
|
assert turntable_node["params"]["duration_s"] == 2
|
||||||
|
assert turntable_node["params"]["samples"] == 16
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_graph_turntable_config_defaults_unchanged_without_render_params():
|
||||||
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
config = module.build_graph_turntable_config(execution_mode="graph")
|
||||||
|
|
||||||
|
turntable_node = next(node for node in config["nodes"] if node["id"] == "turntable")
|
||||||
|
assert turntable_node["params"]["width"] == 768
|
||||||
|
assert turntable_node["params"]["height"] == 768
|
||||||
|
|
||||||
|
|
||||||
|
def test_smoke_blend_output_type_name_is_stable():
|
||||||
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
assert module.smoke_blend_output_type_name() == "[Workflow Smoke] Blend Export"
|
||||||
|
assert module.smoke_blend_workflow_name() == "[Workflow Smoke] Blend Export"
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_output_type_render_template_binding_moves_output_type_to_preferred_template():
|
def test_ensure_output_type_render_template_binding_moves_output_type_to_preferred_template():
|
||||||
module = _load_render_pipeline_script()
|
module = _load_render_pipeline_script()
|
||||||
|
|
||||||
|
|||||||
@@ -319,7 +319,19 @@ def build_graph_still_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_graph_turntable_config(*, execution_mode: str = "graph") -> dict:
|
def build_graph_turntable_config(*, execution_mode: str = "graph", render_params: dict | None = None) -> dict:
|
||||||
|
turntable_params: dict = {
|
||||||
|
"width": 768,
|
||||||
|
"height": 768,
|
||||||
|
"render_engine": "cycles",
|
||||||
|
"samples": 16,
|
||||||
|
"fps": 12,
|
||||||
|
"duration_s": 2,
|
||||||
|
}
|
||||||
|
if render_params:
|
||||||
|
normalized = _normalize_render_params(render_params)
|
||||||
|
turntable_params.update(normalized)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"ui": {"preset": "turntable", "execution_mode": execution_mode},
|
"ui": {"preset": "turntable", "execution_mode": execution_mode},
|
||||||
@@ -357,14 +369,7 @@ def build_graph_turntable_config(*, execution_mode: str = "graph") -> dict:
|
|||||||
{
|
{
|
||||||
"id": "turntable",
|
"id": "turntable",
|
||||||
"step": "blender_turntable",
|
"step": "blender_turntable",
|
||||||
"params": {
|
"params": turntable_params,
|
||||||
"width": 768,
|
|
||||||
"height": 768,
|
|
||||||
"render_engine": "cycles",
|
|
||||||
"samples": 16,
|
|
||||||
"fps": 12,
|
|
||||||
"duration_s": 2,
|
|
||||||
},
|
|
||||||
"ui": {"type": "renderFramesNode", "label": "Turntable Render", "position": {"x": 440, "y": 100}},
|
"ui": {"type": "renderFramesNode", "label": "Turntable Render", "position": {"x": 440, "y": 100}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -607,6 +612,33 @@ def workflow_smoke_template_name(execution_mode: str) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_turntable_output_type_name(execution_mode: str) -> str:
|
||||||
|
return f"[Workflow Smoke] Turntable {execution_mode.title()}"
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_turntable_workflow_name(execution_mode: str) -> str:
|
||||||
|
return f"[Workflow Smoke] Turntable {execution_mode.title()}"
|
||||||
|
|
||||||
|
|
||||||
|
def workflow_smoke_turntable_template_name(execution_mode: str) -> str | None:
|
||||||
|
normalized = str(execution_mode or "").strip().lower()
|
||||||
|
if normalized in {"graph", "shadow"}:
|
||||||
|
return "Blender_Studio_Schadowcatcher_Anim"
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_blend_output_type_name() -> str:
|
||||||
|
return "[Workflow Smoke] Blend Export"
|
||||||
|
|
||||||
|
|
||||||
|
def smoke_blend_workflow_name() -> str:
|
||||||
|
return "[Workflow Smoke] Blend Export"
|
||||||
|
|
||||||
|
|
||||||
|
def workflow_smoke_blend_template_name() -> str:
|
||||||
|
return "BlenderStudio"
|
||||||
|
|
||||||
|
|
||||||
def build_workflow_golden_cases() -> list[dict]:
|
def build_workflow_golden_cases() -> list[dict]:
|
||||||
still_invocation = {
|
still_invocation = {
|
||||||
"width": 1024,
|
"width": 1024,
|
||||||
@@ -856,6 +888,236 @@ def ensure_workflow_still_smoke_resources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_workflow_turntable_smoke_resources(
|
||||||
|
client: APIClient,
|
||||||
|
*,
|
||||||
|
execution_mode: str = "graph",
|
||||||
|
) -> dict:
|
||||||
|
output_type_name = smoke_turntable_output_type_name(execution_mode)
|
||||||
|
workflow_name = smoke_turntable_workflow_name(execution_mode)
|
||||||
|
output_types = get_output_types(client, include_inactive=True)
|
||||||
|
output_type = find_named(output_types, output_type_name)
|
||||||
|
|
||||||
|
smoke_render_params = {
|
||||||
|
"width": 512,
|
||||||
|
"height": 512,
|
||||||
|
"fps": 12,
|
||||||
|
"duration_s": 2,
|
||||||
|
"samples": 16,
|
||||||
|
"engine": "cycles",
|
||||||
|
}
|
||||||
|
output_type_payload = {
|
||||||
|
"name": output_type_name,
|
||||||
|
"description": f"Turntable workflow smoke profile ({execution_mode}) — 512x512, 24 frames",
|
||||||
|
"renderer": "blender",
|
||||||
|
"render_settings": smoke_render_params,
|
||||||
|
"output_format": "mp4",
|
||||||
|
"sort_order": 0,
|
||||||
|
"is_active": True,
|
||||||
|
"compatible_categories": [],
|
||||||
|
"render_backend": "celery",
|
||||||
|
"is_animation": True,
|
||||||
|
"transparent_bg": False,
|
||||||
|
"workflow_family": "order_line",
|
||||||
|
"artifact_kind": "turntable_video",
|
||||||
|
"invocation_overrides": smoke_render_params,
|
||||||
|
"workflow_definition_id": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
if output_type is None:
|
||||||
|
resp = client.post("/output-types", json=output_type_payload)
|
||||||
|
if resp.status_code not in (200, 201):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Turntable smoke output type create failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
output_type = resp.json()
|
||||||
|
ok(f"Provisioned turntable smoke output type: {output_type_name}")
|
||||||
|
else:
|
||||||
|
resp = client.patch(f"/output-types/{output_type['id']}", json=output_type_payload)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Turntable smoke output type update failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
output_type = resp.json()
|
||||||
|
info(f"Reusing turntable smoke output type: {output_type_name}")
|
||||||
|
|
||||||
|
preferred_template_name = workflow_smoke_turntable_template_name(execution_mode)
|
||||||
|
if preferred_template_name:
|
||||||
|
bound_templates = ensure_output_type_render_template_binding(
|
||||||
|
client,
|
||||||
|
output_type=output_type,
|
||||||
|
preferred_template_name=preferred_template_name,
|
||||||
|
)
|
||||||
|
info(
|
||||||
|
"Turntable smoke template candidates: "
|
||||||
|
+ ", ".join(template.get("name") or "<unnamed>" for template in bound_templates)
|
||||||
|
)
|
||||||
|
|
||||||
|
workflow = None
|
||||||
|
if execution_mode != "legacy":
|
||||||
|
workflows = get_workflows(client)
|
||||||
|
workflow = find_named(workflows, workflow_name)
|
||||||
|
workflow_config = build_graph_turntable_config(
|
||||||
|
execution_mode=execution_mode,
|
||||||
|
render_params=smoke_render_params,
|
||||||
|
)
|
||||||
|
workflow_payload = {
|
||||||
|
"name": workflow_name,
|
||||||
|
"output_type_id": output_type["id"],
|
||||||
|
"config": workflow_config,
|
||||||
|
"is_active": True,
|
||||||
|
}
|
||||||
|
if workflow is None:
|
||||||
|
resp = client.post("/workflows", json=workflow_payload)
|
||||||
|
if resp.status_code not in (200, 201):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Turntable smoke workflow create failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
workflow = resp.json()
|
||||||
|
ok(f"Provisioned turntable smoke workflow: {workflow_name}")
|
||||||
|
else:
|
||||||
|
resp = client.put(
|
||||||
|
f"/workflows/{workflow['id']}",
|
||||||
|
json={
|
||||||
|
"name": workflow_payload["name"],
|
||||||
|
"config": workflow_payload["config"],
|
||||||
|
"is_active": workflow_payload["is_active"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Turntable smoke workflow update failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
workflow = resp.json()
|
||||||
|
info(f"Reusing turntable smoke workflow: {workflow_name}")
|
||||||
|
|
||||||
|
resp = client.patch(
|
||||||
|
f"/output-types/{output_type['id']}",
|
||||||
|
json=build_output_type_workflow_link_payload(
|
||||||
|
workflow_definition_id=workflow["id"],
|
||||||
|
execution_mode=execution_mode,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Turntable smoke output type link failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
output_type = resp.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"output_type": output_type,
|
||||||
|
"workflow": workflow,
|
||||||
|
"execution_mode": execution_mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_workflow_blend_smoke_resources(
|
||||||
|
client: APIClient,
|
||||||
|
) -> dict:
|
||||||
|
output_type_name = smoke_blend_output_type_name()
|
||||||
|
workflow_name = smoke_blend_workflow_name()
|
||||||
|
output_types = get_output_types(client, include_inactive=True)
|
||||||
|
output_type = find_named(output_types, output_type_name)
|
||||||
|
|
||||||
|
output_type_payload = {
|
||||||
|
"name": output_type_name,
|
||||||
|
"description": "Blend export workflow smoke profile — graph-authoritative .blend file export",
|
||||||
|
"renderer": "blender",
|
||||||
|
"render_settings": {},
|
||||||
|
"output_format": "blend",
|
||||||
|
"sort_order": 0,
|
||||||
|
"is_active": True,
|
||||||
|
"compatible_categories": [],
|
||||||
|
"render_backend": "celery",
|
||||||
|
"is_animation": False,
|
||||||
|
"transparent_bg": False,
|
||||||
|
"workflow_family": "order_line",
|
||||||
|
"artifact_kind": "blend_asset",
|
||||||
|
"invocation_overrides": {},
|
||||||
|
"workflow_definition_id": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
if output_type is None:
|
||||||
|
resp = client.post("/output-types", json=output_type_payload)
|
||||||
|
if resp.status_code not in (200, 201):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Blend smoke output type create failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
output_type = resp.json()
|
||||||
|
ok(f"Provisioned blend smoke output type: {output_type_name}")
|
||||||
|
else:
|
||||||
|
resp = client.patch(f"/output-types/{output_type['id']}", json=output_type_payload)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Blend smoke output type update failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
output_type = resp.json()
|
||||||
|
info(f"Reusing blend smoke output type: {output_type_name}")
|
||||||
|
|
||||||
|
preferred_template_name = workflow_smoke_blend_template_name()
|
||||||
|
bound_templates = ensure_output_type_render_template_binding(
|
||||||
|
client,
|
||||||
|
output_type=output_type,
|
||||||
|
preferred_template_name=preferred_template_name,
|
||||||
|
)
|
||||||
|
info(
|
||||||
|
"Blend smoke template candidates: "
|
||||||
|
+ ", ".join(template.get("name") or "<unnamed>" for template in bound_templates)
|
||||||
|
)
|
||||||
|
|
||||||
|
workflows = get_workflows(client)
|
||||||
|
workflow = find_named(workflows, workflow_name)
|
||||||
|
workflow_config = build_graph_blend_export_config(execution_mode="graph")
|
||||||
|
workflow_payload = {
|
||||||
|
"name": workflow_name,
|
||||||
|
"output_type_id": output_type["id"],
|
||||||
|
"config": workflow_config,
|
||||||
|
"is_active": True,
|
||||||
|
}
|
||||||
|
if workflow is None:
|
||||||
|
resp = client.post("/workflows", json=workflow_payload)
|
||||||
|
if resp.status_code not in (200, 201):
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Blend smoke workflow create failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
workflow = resp.json()
|
||||||
|
ok(f"Provisioned blend smoke workflow: {workflow_name}")
|
||||||
|
else:
|
||||||
|
resp = client.put(
|
||||||
|
f"/workflows/{workflow['id']}",
|
||||||
|
json={
|
||||||
|
"name": workflow_payload["name"],
|
||||||
|
"config": workflow_payload["config"],
|
||||||
|
"is_active": workflow_payload["is_active"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Blend smoke workflow update failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
workflow = resp.json()
|
||||||
|
info(f"Reusing blend smoke workflow: {workflow_name}")
|
||||||
|
|
||||||
|
resp = client.patch(
|
||||||
|
f"/output-types/{output_type['id']}",
|
||||||
|
json=build_output_type_workflow_link_payload(
|
||||||
|
workflow_definition_id=workflow["id"],
|
||||||
|
execution_mode="graph",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Blend smoke output type link failed: {resp.status_code} {resp.text[:400]}"
|
||||||
|
)
|
||||||
|
output_type = resp.json()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"output_type": output_type,
|
||||||
|
"workflow": workflow,
|
||||||
|
"execution_mode": "graph",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def ensure_workflow_golden_resources(
|
def ensure_workflow_golden_resources(
|
||||||
client: APIClient,
|
client: APIClient,
|
||||||
*,
|
*,
|
||||||
@@ -1582,6 +1844,171 @@ def test_workflow_still_smoke(
|
|||||||
return success
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_turntable_smoke(
|
||||||
|
client: APIClient,
|
||||||
|
cad_file_id: str,
|
||||||
|
*,
|
||||||
|
execution_mode: str = "graph",
|
||||||
|
) -> bool:
|
||||||
|
section(f"3b. Workflow Turntable Smoke — {execution_mode}")
|
||||||
|
|
||||||
|
smoke_resources = ensure_workflow_turntable_smoke_resources(
|
||||||
|
client,
|
||||||
|
execution_mode=execution_mode,
|
||||||
|
)
|
||||||
|
output_type = smoke_resources["output_type"]
|
||||||
|
workflow = smoke_resources["workflow"]
|
||||||
|
|
||||||
|
info(
|
||||||
|
f"Turntable smoke contract: output_type={output_type['name']} "
|
||||||
|
f"workflow={workflow['name'] if workflow else 'legacy-only'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
product_id = get_or_create_test_product(client, cad_file_id)
|
||||||
|
if not product_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
order = create_test_order(
|
||||||
|
client,
|
||||||
|
product_id=product_id,
|
||||||
|
output_type_ids=[output_type["id"]],
|
||||||
|
test_label=f"Workflow Turntable Smoke [{execution_mode}]",
|
||||||
|
)
|
||||||
|
if order is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lines = order.get("lines", [])
|
||||||
|
if len(lines) != 1:
|
||||||
|
fail("Turntable smoke expects exactly one order line")
|
||||||
|
return False
|
||||||
|
line_id = lines[0]["id"]
|
||||||
|
|
||||||
|
if workflow is not None:
|
||||||
|
resp_preflight = client.get(
|
||||||
|
f"/workflows/{workflow['id']}/preflight",
|
||||||
|
params={"context_id": line_id},
|
||||||
|
)
|
||||||
|
if resp_preflight.status_code != 200:
|
||||||
|
fail(f"Turntable workflow preflight failed: {resp_preflight.status_code} {resp_preflight.text[:300]}")
|
||||||
|
return False
|
||||||
|
preflight = resp_preflight.json()
|
||||||
|
info(
|
||||||
|
"Preflight: "
|
||||||
|
f"execution_mode={preflight.get('execution_mode')} "
|
||||||
|
f"allowed={preflight.get('graph_dispatch_allowed')}"
|
||||||
|
)
|
||||||
|
if not preflight.get("graph_dispatch_allowed"):
|
||||||
|
fail(f"Turntable workflow preflight blocked dispatch: {preflight.get('summary')}")
|
||||||
|
for issue in preflight.get("issues", []):
|
||||||
|
info(f" {issue.get('code')}: {issue.get('message')}")
|
||||||
|
return False
|
||||||
|
ok(f"Turntable workflow preflight passed for {execution_mode} mode")
|
||||||
|
|
||||||
|
success = _submit_and_wait(
|
||||||
|
client,
|
||||||
|
order,
|
||||||
|
[output_type["id"]],
|
||||||
|
use_graph_dispatch=False,
|
||||||
|
timeout_seconds=ANIMATION_RENDER_TIMEOUT_SECONDS,
|
||||||
|
)
|
||||||
|
|
||||||
|
if workflow is not None:
|
||||||
|
workflow_run = wait_for_workflow_run(
|
||||||
|
client,
|
||||||
|
workflow_id=workflow["id"],
|
||||||
|
line_id=line_id,
|
||||||
|
)
|
||||||
|
if workflow_run is None:
|
||||||
|
warn("Turntable workflow run could not be resolved after dispatch")
|
||||||
|
else:
|
||||||
|
ok(
|
||||||
|
f"Turntable workflow run tracked: mode={workflow_run.get('execution_mode')} "
|
||||||
|
f"run={workflow_run.get('id')[:8]}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_blend_smoke(
|
||||||
|
client: APIClient,
|
||||||
|
cad_file_id: str,
|
||||||
|
) -> bool:
|
||||||
|
section("3c. Workflow Blend Export Smoke — graph")
|
||||||
|
|
||||||
|
smoke_resources = ensure_workflow_blend_smoke_resources(client)
|
||||||
|
output_type = smoke_resources["output_type"]
|
||||||
|
workflow = smoke_resources["workflow"]
|
||||||
|
|
||||||
|
info(
|
||||||
|
f"Blend smoke contract: output_type={output_type['name']} "
|
||||||
|
f"workflow={workflow['name'] if workflow else 'legacy-only'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
product_id = get_or_create_test_product(client, cad_file_id)
|
||||||
|
if not product_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
order = create_test_order(
|
||||||
|
client,
|
||||||
|
product_id=product_id,
|
||||||
|
output_type_ids=[output_type["id"]],
|
||||||
|
test_label="Workflow Blend Export Smoke [graph]",
|
||||||
|
)
|
||||||
|
if order is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
lines = order.get("lines", [])
|
||||||
|
if len(lines) != 1:
|
||||||
|
fail("Blend smoke expects exactly one order line")
|
||||||
|
return False
|
||||||
|
line_id = lines[0]["id"]
|
||||||
|
|
||||||
|
if workflow is not None:
|
||||||
|
resp_preflight = client.get(
|
||||||
|
f"/workflows/{workflow['id']}/preflight",
|
||||||
|
params={"context_id": line_id},
|
||||||
|
)
|
||||||
|
if resp_preflight.status_code != 200:
|
||||||
|
fail(f"Blend workflow preflight failed: {resp_preflight.status_code} {resp_preflight.text[:300]}")
|
||||||
|
return False
|
||||||
|
preflight = resp_preflight.json()
|
||||||
|
if not preflight.get("graph_dispatch_allowed"):
|
||||||
|
fail(f"Blend workflow preflight blocked dispatch: {preflight.get('summary')}")
|
||||||
|
for issue in preflight.get("issues", []):
|
||||||
|
info(f" {issue.get('code')}: {issue.get('message')}")
|
||||||
|
return False
|
||||||
|
ok("Blend workflow preflight passed")
|
||||||
|
|
||||||
|
success = _submit_and_wait(
|
||||||
|
client,
|
||||||
|
order,
|
||||||
|
[output_type["id"]],
|
||||||
|
use_graph_dispatch=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
if workflow is not None:
|
||||||
|
workflow_run = wait_for_workflow_run(
|
||||||
|
client,
|
||||||
|
workflow_id=workflow["id"],
|
||||||
|
line_id=line_id,
|
||||||
|
)
|
||||||
|
if workflow_run is None:
|
||||||
|
warn("Blend workflow run could not be resolved after dispatch")
|
||||||
|
else:
|
||||||
|
node_result = _node_result_by_name(workflow_run, "blend")
|
||||||
|
if node_result and node_result.get("status") == "completed":
|
||||||
|
ok("Blend export node completed")
|
||||||
|
elif node_result:
|
||||||
|
fail(f"Blend export node status: {node_result.get('status')}")
|
||||||
|
success = False
|
||||||
|
ok(
|
||||||
|
f"Blend workflow run tracked: mode={workflow_run.get('execution_mode')} "
|
||||||
|
f"run={workflow_run.get('id')[:8]}..."
|
||||||
|
)
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
def _assert_workflow_run_contract(case: dict, workflow_run: dict) -> bool:
|
def _assert_workflow_run_contract(case: dict, workflow_run: dict) -> bool:
|
||||||
success = True
|
success = True
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user