Files
MajoorWaldi-ComfyUI-Majoor-…/tests/features/test_workflows_routes.py
T
2026-06-09 10:28:25 +02:00

271 lines
9.6 KiB
Python

import json
import tempfile
import pytest
from aiohttp import web
from aiohttp.test_utils import make_mocked_request
from mjr_am_backend.features.workflows import service as workflows_service
from mjr_am_backend.routes.handlers import workflows as workflows_routes
from mjr_am_backend.shared import Result
def _build_app() -> web.Application:
app = web.Application()
routes = web.RouteTableDef()
workflows_routes.register_workflow_routes(routes)
app.add_routes(routes)
return app
@pytest.mark.asyncio
async def test_workflow_content_rejects_invalid_path(monkeypatch, tmp_path):
workflow_dir = tmp_path / "workflows"
workflow_dir.mkdir()
outside = tmp_path / "outside.json"
outside.write_text(json.dumps({"nodes": []}), encoding="utf-8")
monkeypatch.setenv("MJR_AM_WORKFLOW_DIRECTORY", str(workflow_dir))
app = _build_app()
req = make_mocked_request(
"GET",
f"/mjr/am/workflows/content?filepath={outside.as_posix()}",
app=app,
)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "FORBIDDEN"
@pytest.mark.asyncio
async def test_workflow_save_rejects_bad_json(monkeypatch):
monkeypatch.setattr(workflows_routes, "_csrf_error", lambda request: None)
monkeypatch.setattr(workflows_routes, "_require_write_access", lambda request: Result.Ok(True))
async def _bad_json(_request):
return Result.Err("INVALID_JSON", "Invalid JSON body")
monkeypatch.setattr(workflows_routes, "_read_json", _bad_json)
app = _build_app()
req = make_mocked_request("POST", "/mjr/am/workflows/save", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "INVALID_JSON"
@pytest.mark.asyncio
async def test_workflow_thumbnail_missing_returns_not_found(monkeypatch, tmp_path):
missing = tmp_path / "workflows" / "missing.png"
monkeypatch.setattr(workflows_routes, "is_workflow_thumbnail_path", lambda _path: True)
app = _build_app()
req = make_mocked_request(
"GET",
f"/mjr/am/workflows/thumbnail?filepath={missing.as_posix()}",
app=app,
)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "NOT_FOUND"
@pytest.mark.asyncio
async def test_workflow_graph_map_thumbnail_returns_svg(monkeypatch, tmp_path):
workflow_dir = tmp_path / "workflows"
workflow_dir.mkdir()
workflow_path = workflow_dir / "graph-map.json"
workflow_path.write_text(json.dumps({"name": "Graph Map", "nodes": [{"id": 1, "type": "KSampler"}]}), encoding="utf-8")
monkeypatch.setenv("MJR_AM_WORKFLOW_DIRECTORY", str(workflow_dir))
app = _build_app()
req = make_mocked_request(
"GET",
f"/mjr/am/workflows/graph-map-thumbnail?filepath={workflow_path.as_posix()}",
app=app,
)
match = await app.router.resolve(req)
resp = await match.handler(req)
assert resp.content_type == "image/svg+xml"
assert "<svg" in resp.text
@pytest.mark.asyncio
async def test_workflow_model_families_returns_indexed_values(monkeypatch, tmp_path):
workflow_dir = tmp_path / "workflows"
workflow_dir.mkdir()
(workflow_dir / "flux.json").write_text(
json.dumps({"name": "Flux Demo", "model_family": "Flux", "nodes": [{"id": 1, "type": "KSampler"}]}),
encoding="utf-8",
)
monkeypatch.setenv("MJR_AM_WORKFLOW_DIRECTORY", str(workflow_dir))
monkeypatch.setattr(workflows_routes, "list_workflow_model_families", lambda: Result.Ok({"model_families": [{"label": "Flux", "value": "Flux", "count": 1}]}))
app = _build_app()
req = make_mocked_request("GET", "/mjr/am/workflows/model-families", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is True
families = body.get("data", {}).get("model_families", [])
assert families and families[0]["value"] == "Flux"
@pytest.mark.asyncio
async def test_workflow_graph_map_thumbnail_rejects_outside_path(monkeypatch, tmp_path):
workflow_dir = tmp_path / "workflows"
workflow_dir.mkdir()
outside = tmp_path / "outside.json"
outside.write_text(json.dumps({"name": "Outside", "nodes": []}), encoding="utf-8")
monkeypatch.setenv("MJR_AM_WORKFLOW_DIRECTORY", str(workflow_dir))
app = _build_app()
req = make_mocked_request(
"GET",
f"/mjr/am/workflows/graph-map-thumbnail?filepath={outside.as_posix()}",
app=app,
)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "FORBIDDEN"
@pytest.mark.asyncio
async def test_workflow_write_remote_denial(monkeypatch):
monkeypatch.setattr(workflows_routes, "_csrf_error", lambda request: None)
monkeypatch.setattr(
workflows_routes,
"_require_write_access",
lambda request: Result.Err("AUTH_REQUIRED", "Write access denied"),
)
app = _build_app()
req = make_mocked_request("POST", "/mjr/am/workflows/delete", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "AUTH_REQUIRED"
@pytest.mark.asyncio
async def test_workflow_favorite_rejects_missing_filepath(monkeypatch):
monkeypatch.setattr(workflows_routes, "_csrf_error", lambda request: None)
monkeypatch.setattr(workflows_routes, "_require_write_access", lambda request: Result.Ok(True))
async def _json_without_filepath(_request):
return Result.Ok({"favorite": True})
monkeypatch.setattr(workflows_routes, "_read_json", _json_without_filepath)
app = _build_app()
req = make_mocked_request("POST", "/mjr/am/workflows/favorite", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "INVALID_INPUT"
@pytest.mark.asyncio
async def test_workflow_tags_rejects_missing_filepath(monkeypatch):
monkeypatch.setattr(workflows_routes, "_csrf_error", lambda request: None)
monkeypatch.setattr(workflows_routes, "_require_write_access", lambda request: Result.Ok(True))
async def _json_without_filepath(_request):
return Result.Ok({"tags": ["cinematic"]})
monkeypatch.setattr(workflows_routes, "_read_json", _json_without_filepath)
app = _build_app()
req = make_mocked_request("POST", "/mjr/am/workflows/tags", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "INVALID_INPUT"
@pytest.mark.asyncio
async def test_workflow_tags_accepts_valid_payload(monkeypatch, tmp_path):
workflow_dir = tmp_path / "workflows"
index_db = tmp_path / "index.sqlite"
workflow_dir.mkdir()
workflow_path = workflow_dir / "taggable.json"
original = json.dumps({"name": "Taggable", "nodes": [{"id": 1, "type": "KSampler"}]})
workflow_path.write_text(original, encoding="utf-8")
monkeypatch.setenv("MJR_AM_WORKFLOW_DIRECTORY", str(workflow_dir))
monkeypatch.setattr(workflows_service, "get_runtime_index_db_path", lambda: index_db)
monkeypatch.setattr(workflows_routes, "_csrf_error", lambda request: None)
monkeypatch.setattr(workflows_routes, "_require_write_access", lambda request: Result.Ok(True))
async def _json_with_tags(_request):
return Result.Ok({"filepath": str(workflow_path), "tags": ["cinematic", "flux"]})
monkeypatch.setattr(workflows_routes, "_read_json", _json_with_tags)
app = _build_app()
req = make_mocked_request("POST", "/mjr/am/workflows/tags", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is True
assert body.get("data", {}).get("tags") == ["cinematic", "flux"]
assert workflow_path.read_text(encoding="utf-8") == original
@pytest.mark.asyncio
async def test_workflow_thumbnail_candidates_rejects_missing_filepath(monkeypatch):
app = _build_app()
req = make_mocked_request("GET", "/mjr/am/workflows/thumbnail-candidates", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "INVALID_INPUT"
@pytest.mark.asyncio
async def test_workflow_thumbnail_rejects_missing_source(monkeypatch):
workflow_dir = tempfile.mkdtemp()
monkeypatch.setenv("MJR_AM_WORKFLOW_DIRECTORY", workflow_dir)
monkeypatch.setattr(workflows_routes, "_csrf_error", lambda request: None)
monkeypatch.setattr(workflows_routes, "_require_write_access", lambda request: Result.Ok(True))
workflow_path = f"{workflow_dir}/demo.json"
with open(workflow_path, "w", encoding="utf-8") as handle:
json.dump({"name": "Demo", "nodes": []}, handle)
async def _json_without_source(_request):
return Result.Ok({"filepath": workflow_path})
monkeypatch.setattr(workflows_routes, "_read_json", _json_without_source)
app = _build_app()
req = make_mocked_request("POST", "/mjr/am/workflows/thumbnail/set", app=app)
match = await app.router.resolve(req)
resp = await match.handler(req)
body = json.loads(resp.text)
assert body.get("ok") is False
assert body.get("code") == "INVALID_INPUT"