Files
ERP/backend/tests/test_lifecycle_dates.py
T
MMD 934b74ebbd Squashed 'pm/' content from commit 91f3271
git-subtree-dir: pm
git-subtree-split: 91f3271d866fa8ee471cfb6793e334b83e6d2b6e
2026-10-07 15:14:20 +03:30

156 lines
5.9 KiB
Python

import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from app.db import get_db
from app.main import app
from app.models import Base, PMUser
from app.security import create_token, hash_password
from app.utils.date_service import date_service
@pytest.fixture
def anyio_backend():
return "asyncio"
@pytest.mark.asyncio
async def test_automated_lifecycle_dates():
engine = create_async_engine("sqlite+aiosqlite:///:memory:", echo=False)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
admin = PMUser(
username="admin",
password_hash=hash_password("admin123"),
display_name="مدیر ارشد",
role="project_manager",
)
session.add(admin)
await session.commit()
admin_id = admin.id
token = create_token(admin_id)
headers = {"Authorization": f"Bearer {token}"}
async def override_get_db():
async with async_session() as s:
yield s
app.dependency_overrides[get_db] = override_get_db
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
# 1. Create a project
p_res = await client.post(
"/api/v1/projects",
json={"name": "پروژه بیمارستان البرز"},
headers=headers,
)
assert p_res.status_code == 200
project_id = p_res.json()["data"]["id"]
assert p_res.json()["data"]["planned_start"] is None
assert p_res.json()["data"]["planned_end"] is None
# 2. Create Task 1 (earliest: 2026-10-01 to 2026-10-10)
t1_res = await client.post(
"/api/v1/tasks",
json={
"project_id": project_id,
"title": "گودبرداری و خاکبرداری",
"planned_start": "2026-10-01",
"planned_end": "2026-10-10",
},
headers=headers,
)
assert t1_res.status_code == 200
t1_id = t1_res.json()["data"]["id"]
assert t1_res.json()["data"]["actual_start"] is None
assert t1_res.json()["data"]["actual_end"] is None
# Verify project planned dates matched Task 1
p_get1 = await client.get(f"/api/v1/projects/{project_id}", headers=headers)
assert p_get1.json()["data"]["planned_start"] == "2026-10-01"
assert p_get1.json()["data"]["planned_end"] == "2026-10-10"
assert p_get1.json()["data"]["actual_start"] is None
assert p_get1.json()["data"]["actual_end"] is None
# 3. Create Task 2 (later: 2026-10-11 to 2026-10-25)
t2_res = await client.post(
"/api/v1/tasks",
json={
"project_id": project_id,
"title": "اجرای فونداسیون",
"planned_start": "2026-10-11",
"planned_end": "2026-10-25",
},
headers=headers,
)
assert t2_res.status_code == 200
t2_id = t2_res.json()["data"]["id"]
# Verify project planned dates span from Task 1 (first) to Task 2 (last)
p_get2 = await client.get(f"/api/v1/projects/{project_id}", headers=headers)
assert p_get2.json()["data"]["planned_start"] == "2026-10-01"
assert p_get2.json()["data"]["planned_end"] == "2026-10-25"
# 4. Progress on Task 1 moves from 0 to 25%
today_iso = date_service.today().isoformat()
prog1_res = await client.patch(
f"/api/v1/tasks/{t1_id}/progress",
json={"percentage": 25},
headers=headers,
)
assert prog1_res.status_code == 200
assert prog1_res.json()["data"]["actual_start"] == today_iso
assert prog1_res.json()["data"]["actual_end"] is None
# Check project dates: project.actual_start is now set, actual_end is None
p_get3 = await client.get(f"/api/v1/projects/{project_id}", headers=headers)
assert p_get3.json()["data"]["actual_start"] == today_iso
assert p_get3.json()["data"]["actual_end"] is None
# 5. Complete Task 1 (100%)
prog1_100 = await client.patch(
f"/api/v1/tasks/{t1_id}/progress",
json={"percentage": 100},
headers=headers,
)
assert prog1_100.status_code == 200
assert prog1_100.json()["data"]["actual_end"] == today_iso
# Project actual_end should still be None because Task 2 (the last task) hasn't finished
p_get4 = await client.get(f"/api/v1/projects/{project_id}", headers=headers)
assert p_get4.json()["data"]["actual_end"] is None
# 6. Complete Task 2 (last task) (100%)
prog2_100 = await client.patch(
f"/api/v1/tasks/{t2_id}/progress",
json={"percentage": 100},
headers=headers,
)
assert prog2_100.status_code == 200
assert prog2_100.json()["data"]["actual_end"] == today_iso
# Now project actual_end should match Task 2's actual_end
p_get5 = await client.get(f"/api/v1/projects/{project_id}", headers=headers)
assert p_get5.json()["data"]["actual_end"] == today_iso
assert p_get5.json()["data"]["progress"] == 100.0
# 7. Test reset to 0% on Task 2: actual_end and actual_start are cleared
prog2_0 = await client.patch(
f"/api/v1/tasks/{t2_id}/progress",
json={"percentage": 0},
headers=headers,
)
assert prog2_0.status_code == 200
assert prog2_0.json()["data"]["actual_start"] is None
assert prog2_0.json()["data"]["actual_end"] is None
p_get6 = await client.get(f"/api/v1/projects/{project_id}", headers=headers)
assert p_get6.json()["data"]["actual_end"] is None