Squashed 'pm/' content from commit 91f3271
git-subtree-dir: pm git-subtree-split: 91f3271d866fa8ee471cfb6793e334b83e6d2b6e
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
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
|
||||
Reference in New Issue
Block a user