Files
factoryOps-v2/tests/test_alarms.py
Johngreen 843f72e048
All checks were successful
Deploy to Production / deploy (push) Successful in 1m17s
feat: Phase 6 — alarm system (auto-generate, list, acknowledge)
- Add Alarm model with tenant/part/machine relationships and 3 indexes
- Add alembic migration c5d6e7f8a9b0_add_alarms
- Add alarms API: list (filter by ack/severity), summary, acknowledge
- Auto-generate alarms on counter update (threshold warning, critical at 100%)
- Duplicate alarm prevention for same part+alarm_type
- Add alarms frontend page with active/acknowledged tabs, summary badges
- 9 new tests (67/67 total passing)

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-02-10 14:39:03 +09:00

226 lines
6.7 KiB
Python

import pytest
from httpx import AsyncClient
from tests.conftest import get_auth_headers
async def _create_machine(
client: AsyncClient, headers: dict, tenant_id: str = "test-co"
) -> str:
resp = await client.post(
f"/api/{tenant_id}/machines",
json={"name": "알람 테스트 설비", "equipment_code": "A-001"},
headers=headers,
)
return resp.json()["id"]
async def _create_part(
client: AsyncClient,
headers: dict,
machine_id: str,
tenant_id: str = "test-co",
alarm_threshold: float = 80.0,
) -> str:
resp = await client.post(
f"/api/{tenant_id}/machines/{machine_id}/parts",
json={
"name": "알람 부품",
"lifecycle_type": "hours",
"lifecycle_limit": 1000,
"alarm_threshold": alarm_threshold,
"counter_source": "manual",
},
headers=headers,
)
return resp.json()["id"]
@pytest.mark.asyncio
async def test_no_alarm_below_threshold(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 700},
headers=headers,
)
resp = await client.get("/api/test-co/alarms", headers=headers)
assert resp.status_code == 200
assert len(resp.json()["alarms"]) == 0
@pytest.mark.asyncio
async def test_warning_alarm_at_threshold(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 850},
headers=headers,
)
resp = await client.get("/api/test-co/alarms", headers=headers)
assert resp.status_code == 200
alarms = resp.json()["alarms"]
assert len(alarms) == 1
assert alarms[0]["severity"] == "warning"
assert alarms[0]["alarm_type"] == "threshold"
assert alarms[0]["is_acknowledged"] is False
assert "85.0%" in alarms[0]["message"]
@pytest.mark.asyncio
async def test_critical_alarm_at_100pct(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 1100},
headers=headers,
)
resp = await client.get("/api/test-co/alarms", headers=headers)
assert resp.status_code == 200
alarms = resp.json()["alarms"]
assert len(alarms) == 1
assert alarms[0]["severity"] == "critical"
assert alarms[0]["alarm_type"] == "critical"
@pytest.mark.asyncio
async def test_no_duplicate_alarm(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 900},
headers=headers,
)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 950},
headers=headers,
)
resp = await client.get("/api/test-co/alarms", headers=headers)
alarms = resp.json()["alarms"]
assert len(alarms) == 1
@pytest.mark.asyncio
async def test_acknowledge_alarm(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 850},
headers=headers,
)
resp = await client.get("/api/test-co/alarms", headers=headers)
alarm_id = resp.json()["alarms"][0]["id"]
ack_resp = await client.post(
f"/api/test-co/alarms/{alarm_id}/acknowledge",
headers=headers,
)
assert ack_resp.status_code == 200
assert ack_resp.json()["is_acknowledged"] is True
assert ack_resp.json()["acknowledged_by"] is not None
@pytest.mark.asyncio
async def test_acknowledge_already_acknowledged(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 850},
headers=headers,
)
resp = await client.get("/api/test-co/alarms", headers=headers)
alarm_id = resp.json()["alarms"][0]["id"]
await client.post(f"/api/test-co/alarms/{alarm_id}/acknowledge", headers=headers)
ack2 = await client.post(
f"/api/test-co/alarms/{alarm_id}/acknowledge", headers=headers
)
assert ack2.status_code == 400
@pytest.mark.asyncio
async def test_filter_unacknowledged(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 850},
headers=headers,
)
resp = await client.get(
"/api/test-co/alarms?is_acknowledged=false", headers=headers
)
assert len(resp.json()["alarms"]) == 1
alarm_id = resp.json()["alarms"][0]["id"]
await client.post(f"/api/test-co/alarms/{alarm_id}/acknowledge", headers=headers)
resp2 = await client.get(
"/api/test-co/alarms?is_acknowledged=false", headers=headers
)
assert len(resp2.json()["alarms"]) == 0
@pytest.mark.asyncio
async def test_alarm_summary(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 850},
headers=headers,
)
resp = await client.get("/api/test-co/alarms/summary", headers=headers)
assert resp.status_code == 200
data = resp.json()
assert data["active_count"] == 1
assert data["warning_count"] == 1
assert data["critical_count"] == 0
@pytest.mark.asyncio
async def test_tenant_isolation(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
mid = await _create_machine(client, headers)
pid = await _create_part(client, headers, mid)
await client.put(
f"/api/test-co/parts/{pid}/counter",
json={"value": 900},
headers=headers,
)
resp_other = await client.get("/api/other-co/alarms", headers=headers)
assert resp_other.status_code == 200
assert len(resp_other.json()["alarms"]) == 0