212 lines
8.5 KiB
Python
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
|