Skip to content

Commit 5a9664f

Browse files
author
Modal Agent Runner
committed
test: add unit/integration coverage and CI pipeline
1 parent 565e5eb commit 5a9664f

5 files changed

Lines changed: 166 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
backend:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Setup Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.11"
19+
20+
- name: Install backend dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install -r requirements.txt
24+
25+
- name: Run tests
26+
run: pytest -q
27+
28+
frontend:
29+
runs-on: ubuntu-latest
30+
defaults:
31+
run:
32+
working-directory: frontend
33+
steps:
34+
- name: Checkout
35+
uses: actions/checkout@v4
36+
37+
- name: Setup Node
38+
uses: actions/setup-node@v4
39+
with:
40+
node-version: "20"
41+
cache: "npm"
42+
cache-dependency-path: frontend/package.json
43+
44+
- name: Install frontend dependencies
45+
run: npm install
46+
47+
- name: Build frontend
48+
run: npm run build

pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
pythonpath = .
3+
asyncio_mode = auto
4+
testpaths = tests
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import importlib
2+
import os
3+
from pathlib import Path
4+
5+
from fastapi.testclient import TestClient
6+
7+
8+
TEST_DB = Path("./test_ic_workflow.db")
9+
os.environ["DATABASE_URL"] = "sqlite+aiosqlite:///./test_ic_workflow.db"
10+
os.environ["OPENAI_API_KEY"] = ""
11+
12+
from src.core.config import get_settings
13+
14+
get_settings.cache_clear()
15+
16+
import src.database.session as session_module
17+
import src.main as main_module
18+
19+
importlib.reload(session_module)
20+
importlib.reload(main_module)
21+
22+
app = main_module.app
23+
24+
25+
def setup_module() -> None:
26+
if TEST_DB.exists():
27+
TEST_DB.unlink()
28+
29+
30+
def teardown_module() -> None:
31+
if TEST_DB.exists():
32+
TEST_DB.unlink()
33+
34+
35+
def test_health_endpoint() -> None:
36+
with TestClient(app) as client:
37+
response = client.get("/api/v1/health")
38+
assert response.status_code == 200
39+
payload = response.json()
40+
assert payload["status"] == "ok"
41+
42+
43+
def test_create_and_list_deals() -> None:
44+
with TestClient(app) as client:
45+
create_response = client.post(
46+
"/api/v1/deals",
47+
json={
48+
"deal_name": "Integration Deal",
49+
"target_name": "Integration Target",
50+
"deal_type": "equity",
51+
"industry": "Fintech",
52+
"geography": "US",
53+
"deal_size": 2500000,
54+
"valuation": 20000000,
55+
"currency": "USD",
56+
},
57+
headers={"X-User": "integration@test.local"},
58+
)
59+
assert create_response.status_code == 201
60+
deal = create_response.json()
61+
assert deal["deal_name"] == "Integration Deal"
62+
63+
list_response = client.get("/api/v1/deals")
64+
assert list_response.status_code == 200
65+
deals = list_response.json()
66+
assert any(item["id"] == deal["id"] for item in deals)

tests/unit/test_ic_decision.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from src.api.routes.ic_decisions import compute_approval_percentage
2+
3+
4+
def test_compute_approval_percentage() -> None:
5+
value = compute_approval_percentage(
6+
{
7+
"a": "approve",
8+
"b": "approved",
9+
"c": "reject",
10+
"d": "yes",
11+
}
12+
)
13+
14+
assert value == 0.75
15+
16+
17+
def test_compute_approval_percentage_empty_votes() -> None:
18+
assert compute_approval_percentage({}) == 0.0

tests/unit/test_workflow_engine.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from src.models.database import DealStatus
2+
from src.workflows.state_machine import DealWorkflowEngine
3+
4+
5+
def test_transition_matrix_allows_expected_path() -> None:
6+
engine = DealWorkflowEngine()
7+
8+
assert engine.can_transition(DealStatus.SUBMITTED, DealStatus.ANALYST_REVIEW)
9+
assert engine.can_transition(DealStatus.ANALYST_REVIEW, DealStatus.RISK_REVIEW)
10+
assert engine.can_transition(DealStatus.RISK_REVIEW, DealStatus.LEGAL_REVIEW)
11+
assert engine.can_transition(DealStatus.LEGAL_REVIEW, DealStatus.IC_PENDING)
12+
assert engine.can_transition(DealStatus.IC_PENDING, DealStatus.APPROVED)
13+
14+
15+
def test_transition_matrix_blocks_invalid_jump() -> None:
16+
engine = DealWorkflowEngine()
17+
18+
assert not engine.can_transition(DealStatus.SUBMITTED, DealStatus.APPROVED)
19+
assert not engine.can_transition(DealStatus.APPROVED, DealStatus.SUBMITTED)
20+
21+
22+
def test_task_templates_exist_for_review_stages() -> None:
23+
engine = DealWorkflowEngine()
24+
25+
analyst_tasks = engine.next_tasks(DealStatus.ANALYST_REVIEW)
26+
ic_tasks = engine.next_tasks(DealStatus.IC_PENDING)
27+
28+
assert analyst_tasks
29+
assert ic_tasks
30+
assert analyst_tasks[0].due_in_hours > 0

0 commit comments

Comments
 (0)