infrasynth-backend-kit/tests/test_workflows/test_views.py
2026-08-28 14:38:47 -05:00

212 lines
8.5 KiB
Python

import pytest
from infrasynth.features.models import FeatureFlag
from infrasynth.workflows.models import (
Transition,
Workflow,
WorkflowInstance,
WorkflowNode,
WorkflowObserver,
)
pytestmark = pytest.mark.django_db
DEFINITIONS_URL = "/api/workflows/definitions/"
INSTANCES_URL = "/api/workflows/instances/"
NODES_URL = "/api/workflows/nodes/"
TRANSITIONS_URL = "/api/workflows/transitions/"
ASSIGNMENTS_URL = "/api/workflows/assignments/"
OBSERVERS_URL = "/api/workflows/observers/"
@pytest.fixture
def workflow(user):
return Workflow.objects.create(slug="approval", name="Approval", created_by=user)
@pytest.fixture
def workflow_graph(workflow):
start = WorkflowNode.objects.create(workflow=workflow, name="Start", node_type=WorkflowNode.NodeType.START)
end = WorkflowNode.objects.create(workflow=workflow, name="End", node_type=WorkflowNode.NodeType.END)
Transition.objects.create(from_node=start, to_node=end, condition_slug="approved")
return {"start": start, "end": end}
class TestWorkflowViewSet:
def test_list_workflows(self, authenticated_client, workflow):
response = authenticated_client.get(DEFINITIONS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["slug"] == "approval"
def test_create_workflow(self, authenticated_client):
response = authenticated_client.post(
DEFINITIONS_URL,
{"slug": "leave-request", "name": "Leave Request"},
format="json",
)
assert response.status_code == 201
assert Workflow.objects.filter(slug="leave-request").exists()
def test_requires_auth(self, api_client, workflow):
assert api_client.get(DEFINITIONS_URL).status_code == 401
def test_disabled_feature_returns_404(self, authenticated_client):
FeatureFlag.objects.create(slug="workflows", name="Workflows", is_active=False)
assert authenticated_client.get(DEFINITIONS_URL).status_code == 404
def test_retrieve_delete(self, authenticated_client, workflow):
response = authenticated_client.get(f"{DEFINITIONS_URL}{workflow.id}/")
assert response.status_code == 200
response = authenticated_client.delete(f"{DEFINITIONS_URL}{workflow.id}/")
assert response.status_code == 204
assert not Workflow.objects.filter(pk=workflow.pk).exists()
class TestWorkflowInstanceViewSet:
def test_create_starts_instance(self, authenticated_client, workflow_graph, user):
response = authenticated_client.post(
INSTANCES_URL,
{"workflow": workflow_graph["start"].workflow_id},
format="json",
)
assert response.status_code == 201
instance = WorkflowInstance.objects.get()
assert instance.status == WorkflowInstance.Status.IN_PROGRESS
assert instance.current_node_id == workflow_graph["start"].id
assert instance.owner_id == user.id
def test_list_instances(self, authenticated_client, workflow_graph, user):
from infrasynth.workflows.engine import WorkflowEngine
WorkflowEngine().start("approval", owner=user)
response = authenticated_client.get(INSTANCES_URL)
assert response.status_code == 200
assert response.data["count"] == 1
assert response.data["results"][0]["status"] == "in_progress"
def test_requires_auth(self, api_client, workflow_graph):
assert api_client.get(INSTANCES_URL).status_code == 401
def test_route_action(self, authenticated_client, workflow_graph, user):
from infrasynth.workflows.engine import WorkflowEngine
instance = WorkflowEngine().start("approval", owner=user)
response = authenticated_client.get(f"{INSTANCES_URL}{instance.pk}/route/")
assert response.status_code == 200
assert "route" in response.data
def test_state_action(self, authenticated_client, workflow_graph, user):
from infrasynth.workflows.engine import WorkflowEngine
instance = WorkflowEngine().start("approval", owner=user)
response = authenticated_client.get(f"{INSTANCES_URL}{instance.pk}/state/")
assert response.status_code == 200
assert "state" in response.data
states = response.data["state"]
assert "ACTIVE" in states.values()
assert all(state in ("ACTIVE", "PENDING", "COMPLETED", "REJECTED") for state in states.values())
def test_submit_action(self, authenticated_client, workflow_graph, user):
from infrasynth.workflows.engine import WorkflowEngine
instance = WorkflowEngine().start("approval", owner=user)
assignment = WorkflowEngine().assign_users(instance.pk, instance.current_node_id, [user])[0]
response = authenticated_client.post(
f"{INSTANCES_URL}{instance.pk}/submit/",
{"assignment_id": assignment.id, "decision": "approved"},
format="json",
)
assert response.status_code == 200
assert response.data["status"] == "completed"
assert response.data["current_node"]["name"] == "End"
def test_submit_requires_fields(self, authenticated_client, workflow_graph, user):
from infrasynth.workflows.engine import WorkflowEngine
instance = WorkflowEngine().start("approval", owner=user)
response = authenticated_client.post(f"{INSTANCES_URL}{instance.pk}/submit/", {}, format="json")
assert response.status_code == 400
def test_assign_action(self, authenticated_client, workflow_graph, user):
from infrasynth.workflows.engine import WorkflowEngine
instance = WorkflowEngine().start("approval", owner=user)
response = authenticated_client.post(
f"{INSTANCES_URL}{instance.pk}/assign/",
{
"node_id": instance.current_node_id,
"users": [user.id],
},
format="json",
)
assert response.status_code == 201
assert len(response.data) == 1
def test_observers_add_and_delete(self, authenticated_client, workflow_graph, user, admin_user):
from infrasynth.workflows.engine import WorkflowEngine
instance = WorkflowEngine().start("approval", owner=admin_user)
response = authenticated_client.post(
f"{INSTANCES_URL}{instance.pk}/observers/",
{"user_id": user.id},
format="json",
)
assert response.status_code == 201
assert WorkflowObserver.objects.filter(instance=instance, user=user).exists()
response = authenticated_client.delete(
f"{INSTANCES_URL}{instance.pk}/observers/",
{"user_id": user.id},
format="json",
)
assert response.status_code == 204
assert not WorkflowObserver.objects.filter(instance=instance, user=user).exists()
class TestWorkflowNodeViewSet:
def test_list_nodes(self, authenticated_client, workflow_graph):
response = authenticated_client.get(NODES_URL)
assert response.status_code == 200
assert response.data["count"] == 2
def test_create_node(self, authenticated_client, workflow):
response = authenticated_client.post(
NODES_URL,
{
"workflow": workflow.id,
"name": "QA",
"node_type": "intermediate",
"approval_strategy": "any",
},
format="json",
)
assert response.status_code == 201
assert WorkflowNode.objects.filter(name="QA").exists()
def test_requires_auth(self, api_client, workflow_graph):
assert api_client.get(NODES_URL).status_code == 401
class TestTransitionViewSet:
def test_list_transitions(self, authenticated_client, workflow_graph):
response = authenticated_client.get(TRANSITIONS_URL)
assert response.status_code == 200
assert response.data["count"] == 1
def test_create_transition(self, authenticated_client, workflow_graph):
start, end = workflow_graph["start"], workflow_graph["end"]
response = authenticated_client.post(
TRANSITIONS_URL,
{
"from_node": start.id,
"to_node": end.id,
"condition_slug": "rejected",
},
format="json",
)
assert response.status_code == 201
assert Transition.objects.filter(condition_slug="rejected").exists()
def test_requires_auth(self, api_client, workflow_graph):
assert api_client.get(TRANSITIONS_URL).status_code == 401