Skip to content

Commit 4bb9e17

Browse files
committed
test: add integration and E2E tests (#14, #15)
1 parent 9520536 commit 4bb9e17

File tree

4 files changed

+323
-0
lines changed

4 files changed

+323
-0
lines changed

tests/test_audit_workflow_e2e.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# tests/test_audit_workflow_e2e.py
2+
"""E2E tests for audit workflow.
3+
4+
REFERENCE: principles/automation/testing-strategy.md
5+
- Tests business-impacting workflows only
6+
- 5-15 E2E tests maximum
7+
"""
8+
9+
import tempfile
10+
from pathlib import Path
11+
12+
from typer.testing import CliRunner
13+
14+
from workflow_as_list.cli import app
15+
from workflow_as_list.constants import ensure_directories
16+
17+
runner = CliRunner()
18+
19+
20+
def test_complete_audit_workflow():
21+
"""Scenario: User completes full audit workflow (check → approve → reject).
22+
Expected: Workflow can be approved and rejected.
23+
If fails: Audit workflow broken, security model compromised.
24+
"""
25+
ensure_directories()
26+
27+
# Create test workflow with unique name
28+
with tempfile.NamedTemporaryFile(
29+
mode="w", suffix=".workflow.list", delete=False, prefix="test_"
30+
) as f:
31+
f.write("# Test workflow\n- (start) Test\n- End\n")
32+
f.flush()
33+
workflow_path = Path(f.name)
34+
35+
# Workflow name is derived from filename (remove .workflow.list suffix)
36+
# e.g., test_abc.workflow.list -> test_abc.workflow
37+
workflow_name = workflow_path.stem
38+
39+
try:
40+
# Check
41+
result = runner.invoke(app, ["check", str(workflow_path)])
42+
assert result.exit_code == 0, f"Check failed: {result.output}"
43+
assert "Workflow registered" in result.output
44+
45+
# Approve
46+
result = runner.invoke(app, ["approve", workflow_name])
47+
assert result.exit_code == 0, f"Approve failed: {result.output}"
48+
assert "approved" in result.output.lower()
49+
50+
# Reject (to test reject flow)
51+
result = runner.invoke(app, ["reject", workflow_name])
52+
assert result.exit_code == 0, f"Reject failed: {result.output}"
53+
assert "rejected" in result.output.lower()
54+
55+
finally:
56+
workflow_path.unlink()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# tests/test_execution_workflow_e2e.py
2+
"""E2E tests for execution workflow with progressive reading.
3+
4+
REFERENCE: principles/automation/testing-strategy.md
5+
- Tests business-impacting workflows only
6+
- 5-15 E2E tests maximum
7+
"""
8+
9+
import tempfile
10+
from pathlib import Path
11+
12+
from typer.testing import CliRunner
13+
14+
from workflow_as_list.cli import app
15+
from workflow_as_list.constants import ensure_directories
16+
17+
runner = CliRunner()
18+
19+
20+
def test_complete_execution_workflow():
21+
"""Scenario: User completes full execution workflow with progressive reading.
22+
Expected: Can read steps, advance only after reading, complete workflow.
23+
If fails: Progressive reading model broken, core value proposition compromised.
24+
"""
25+
ensure_directories()
26+
27+
# Create test workflow with unique name
28+
with tempfile.NamedTemporaryFile(
29+
mode="w", suffix=".workflow.list", delete=False, prefix="test_"
30+
) as f:
31+
f.write("# Test workflow\n- (start) Step 1\n- Step 2\n- End\n")
32+
f.flush()
33+
workflow_path = Path(f.name)
34+
35+
# Workflow name is derived from filename (remove .workflow.list suffix)
36+
# e.g., test_abc.workflow.list -> test_abc.workflow
37+
workflow_name = workflow_path.stem
38+
39+
try:
40+
# Check and approve
41+
result = runner.invoke(app, ["check", str(workflow_path)])
42+
assert result.exit_code == 0
43+
44+
result = runner.invoke(app, ["approve", workflow_name])
45+
assert result.exit_code == 0
46+
47+
# Run to create execution
48+
result = runner.invoke(app, ["run", workflow_name])
49+
assert result.exit_code == 0
50+
51+
# Extract execution ID
52+
import re
53+
54+
pattern = rf"Execution started: ({workflow_name}-\w+)"
55+
match = re.search(pattern, result.output)
56+
assert match, f"Could not find execution ID: {result.output}"
57+
execution_id = match.group(1)
58+
59+
# Progressive reading: read → next → read → next
60+
for step_num in range(1, 4):
61+
# Read
62+
result = runner.invoke(app, ["exec", "read", execution_id])
63+
assert result.exit_code == 0, (
64+
f"Read step {step_num} failed: {result.output}"
65+
)
66+
67+
# Next
68+
result = runner.invoke(app, ["exec", "next", execution_id])
69+
if step_num < 3:
70+
assert result.exit_code == 0, (
71+
f"Next step {step_num} failed: {result.output}"
72+
)
73+
else:
74+
# Last step should complete
75+
assert "completed" in result.output.lower()
76+
77+
finally:
78+
workflow_path.unlink()

tests/test_pipeline_integration.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# tests/test_pipeline_integration.py
2+
"""Integration tests for critical data flows.
3+
4+
REFERENCE: principles/automation/testing-strategy.md
5+
- Tests real integration points, not hypothetical combinations
6+
- Verifies necessary conditions, not sufficient conditions
7+
"""
8+
9+
import tempfile
10+
import uuid
11+
from pathlib import Path
12+
13+
from typer.testing import CliRunner
14+
15+
from workflow_as_list.cli import app
16+
from workflow_as_list.constants import ensure_directories
17+
18+
runner = CliRunner()
19+
20+
21+
def test_check_approve_run_pipeline():
22+
"""Scenario: User completes full workflow pipeline (check → approve → run).
23+
Expected: Workflow registered, approved, execution created.
24+
If fails: Core user workflow broken.
25+
"""
26+
ensure_directories()
27+
28+
# Use unique workflow name
29+
workflow_name = f"test_{uuid.uuid4().hex[:8]}"
30+
31+
# Create test workflow
32+
with tempfile.NamedTemporaryFile(
33+
mode="w", suffix=".workflow.list", delete=False
34+
) as f:
35+
f.write(f"- (start) Test {workflow_name}\n- End\n")
36+
f.flush()
37+
workflow_path = Path(f.name)
38+
39+
try:
40+
# Workflow name is derived from filename (remove .workflow.list suffix)
41+
# e.g., test_abc.workflow.list -> test_abc.workflow
42+
workflow_name = workflow_path.stem
43+
44+
# Check
45+
result = runner.invoke(app, ["check", str(workflow_path)])
46+
assert result.exit_code == 0, f"Check failed: {result.output}"
47+
48+
# Approve
49+
result = runner.invoke(app, ["approve", workflow_name])
50+
assert result.exit_code == 0, f"Approve failed: {result.output}"
51+
52+
# Run
53+
result = runner.invoke(app, ["run", workflow_name])
54+
assert result.exit_code == 0, f"Run failed: {result.output}"
55+
assert "Execution started" in result.output
56+
57+
finally:
58+
workflow_path.unlink()
59+
60+
61+
def test_exec_read_next_pipeline():
62+
"""Scenario: User uses progressive reading (read → next).
63+
Expected: Can read step, advance only after reading.
64+
If fails: Progressive reading enforcement broken.
65+
"""
66+
ensure_directories()
67+
68+
# Create test workflow with unique name
69+
with tempfile.NamedTemporaryFile(
70+
mode="w", suffix=".workflow.list", delete=False, prefix="test_"
71+
) as f:
72+
f.write("- (start) Step 1\n- Step 2\n- End\n")
73+
f.flush()
74+
workflow_path = Path(f.name)
75+
76+
# Workflow name is derived from filename (remove .workflow.list suffix)
77+
# e.g., test_abc.workflow.list -> test_abc.workflow
78+
workflow_name = workflow_path.stem
79+
80+
try:
81+
# Check and approve
82+
runner.invoke(app, ["check", str(workflow_path)])
83+
runner.invoke(app, ["approve", workflow_name])
84+
85+
# Run to create execution
86+
result = runner.invoke(app, ["run", workflow_name])
87+
assert result.exit_code == 0
88+
89+
# Extract execution ID from output
90+
# Format: "Execution started: test_abc.workflow-xyz"
91+
import re
92+
93+
pattern = rf"Execution started: ({workflow_name}-\w+)"
94+
match = re.search(pattern, result.output)
95+
assert match, f"Could not find execution ID: {result.output}"
96+
execution_id = match.group(1)
97+
98+
# Read current step
99+
result = runner.invoke(app, ["exec", "read", execution_id])
100+
assert result.exit_code == 0, f"Read failed: {result.output}"
101+
assert "Step 1" in result.output
102+
103+
# Next should succeed (step was read)
104+
result = runner.invoke(app, ["exec", "next", execution_id])
105+
assert result.exit_code == 0, f"Next failed: {result.output}"
106+
assert "Advanced to step" in result.output
107+
108+
# Try next again (should fail - step 2 not read)
109+
result = runner.invoke(app, ["exec", "next", execution_id])
110+
assert result.exit_code != 0, "Next should fail without reading first"
111+
assert "not read" in result.output.lower()
112+
113+
finally:
114+
workflow_path.unlink()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# tests/test_state_persistence_e2e.py
2+
"""E2E tests for state persistence.
3+
4+
REFERENCE: principles/automation/testing-strategy.md
5+
- Tests business-impacting workflows only
6+
- 5-15 E2E tests maximum
7+
"""
8+
9+
import tempfile
10+
from pathlib import Path
11+
12+
from typer.testing import CliRunner
13+
14+
from workflow_as_list.cli import app
15+
from workflow_as_list.constants import ensure_directories
16+
from workflow_as_list.executor import Executor
17+
18+
runner = CliRunner()
19+
20+
21+
def test_state_persistence_after_restart():
22+
"""Scenario: Execution state persists after "restart" (new Executor instance).
23+
Expected: Can resume execution from saved state.
24+
If fails: State persistence broken, cannot resume interrupted executions.
25+
"""
26+
ensure_directories()
27+
28+
# Create test workflow with unique name
29+
with tempfile.NamedTemporaryFile(
30+
mode="w", suffix=".workflow.list", delete=False, prefix="test_"
31+
) as f:
32+
f.write("# Test workflow\n- (start) Step 1\n- Step 2\n- End\n")
33+
f.flush()
34+
workflow_path = Path(f.name)
35+
36+
# Workflow name is derived from filename (remove .workflow.list suffix)
37+
# e.g., test_abc.workflow.list -> test_abc.workflow
38+
workflow_name = workflow_path.stem
39+
40+
try:
41+
# Check and approve
42+
runner.invoke(app, ["check", str(workflow_path)])
43+
runner.invoke(app, ["approve", workflow_name])
44+
45+
# Run to create execution
46+
result = runner.invoke(app, ["run", workflow_name])
47+
assert result.exit_code == 0
48+
49+
# Extract execution ID
50+
import re
51+
52+
pattern = rf"Execution started: ({workflow_name}-\w+)"
53+
match = re.search(pattern, result.output)
54+
assert match, f"Could not find execution ID: {result.output}"
55+
execution_id = match.group(1)
56+
57+
# Read step 1
58+
result = runner.invoke(app, ["exec", "read", execution_id])
59+
assert result.exit_code == 0
60+
61+
# "Restart" - create new Executor instance
62+
executor = Executor()
63+
64+
# Verify state persisted
65+
execution = executor.get_execution(execution_id)
66+
assert execution is not None, "Execution state not persisted"
67+
assert execution.workflow_name == workflow_name
68+
assert execution.steps_total == 3
69+
70+
# Can continue from saved state
71+
result = runner.invoke(app, ["exec", "next", execution_id])
72+
assert result.exit_code == 0, f"Next after restart failed: {result.output}"
73+
74+
finally:
75+
workflow_path.unlink()

0 commit comments

Comments
 (0)