Files
factoryOps-v2/tests/test_equipment_parts.py
Johngreen 035d62f0e0
All checks were successful
Deploy to Production / deploy (push) Successful in 1m5s
feat: Phase 5 — part counter update, replacement, replacement history
2026-02-10 13:55:49 +09:00

354 lines
11 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": "T-001"},
headers=headers,
)
return resp.json()["id"]
@pytest.mark.asyncio
async def test_create_part(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "상부 금형",
"category": "mold",
"lifecycle_type": "count",
"lifecycle_limit": 10000,
"alarm_threshold": 90.0,
"counter_source": "auto_plc",
"installed_at": "2026-02-01T00:00:00+09:00",
},
headers=headers,
)
assert resp.status_code == 200
data = resp.json()
assert data["name"] == "상부 금형"
assert data["lifecycle_type"] == "count"
assert data["lifecycle_limit"] == 10000
assert data["counter"]["current_value"] == 0
assert data["counter"]["lifecycle_pct"] == 0
@pytest.mark.asyncio
async def test_create_part_auto_creates_counter(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "히터",
"lifecycle_type": "hours",
"lifecycle_limit": 5000,
"installed_at": "2026-01-15T00:00:00Z",
},
headers=headers,
)
assert resp.status_code == 200
data = resp.json()
assert "counter" in data
assert data["counter"]["current_value"] == 0
assert data["counter"]["version"] == 0
@pytest.mark.asyncio
async def test_duplicate_part_name_rejected(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
part_data = {
"name": "동일 부품",
"lifecycle_type": "count",
"lifecycle_limit": 1000,
"installed_at": "2026-02-01T00:00:00Z",
}
resp1 = await client.post(
f"/api/test-co/machines/{machine_id}/parts", json=part_data, headers=headers
)
assert resp1.status_code == 200
resp2 = await client.post(
f"/api/test-co/machines/{machine_id}/parts", json=part_data, headers=headers
)
assert resp2.status_code == 409
@pytest.mark.asyncio
async def test_list_parts(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
for name in ["부품A", "부품B", "부품C"]:
await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": name,
"lifecycle_type": "count",
"lifecycle_limit": 1000,
"installed_at": "2026-02-01T00:00:00Z",
},
headers=headers,
)
resp = await client.get(
f"/api/test-co/machines/{machine_id}/parts", headers=headers
)
assert resp.status_code == 200
assert len(resp.json()["parts"]) == 3
@pytest.mark.asyncio
async def test_get_part_detail(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
create_resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "센서",
"lifecycle_type": "hours",
"lifecycle_limit": 8000,
"installed_at": "2026-02-01T00:00:00Z",
},
headers=headers,
)
part_id = create_resp.json()["id"]
resp = await client.get(f"/api/test-co/parts/{part_id}", headers=headers)
assert resp.status_code == 200
assert resp.json()["name"] == "센서"
assert resp.json()["lifecycle_limit"] == 8000
@pytest.mark.asyncio
async def test_update_part(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
create_resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "금형",
"lifecycle_type": "count",
"lifecycle_limit": 5000,
"installed_at": "2026-02-01T00:00:00Z",
},
headers=headers,
)
part_id = create_resp.json()["id"]
resp = await client.put(
f"/api/test-co/parts/{part_id}",
json={"name": "상부 금형", "lifecycle_limit": 8000},
headers=headers,
)
assert resp.status_code == 200
assert resp.json()["name"] == "상부 금형"
assert resp.json()["lifecycle_limit"] == 8000
@pytest.mark.asyncio
async def test_delete_part(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
create_resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "삭제 부품",
"lifecycle_type": "date",
"lifecycle_limit": 30,
"installed_at": "2026-02-01T00:00:00Z",
},
headers=headers,
)
part_id = create_resp.json()["id"]
resp = await client.delete(f"/api/test-co/parts/{part_id}", headers=headers)
assert resp.status_code == 200
assert resp.json()["status"] == "success"
list_resp = await client.get(
f"/api/test-co/machines/{machine_id}/parts", headers=headers
)
assert len(list_resp.json()["parts"]) == 0
@pytest.mark.asyncio
async def test_invalid_lifecycle_type(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "잘못된 부품",
"lifecycle_type": "invalid",
"lifecycle_limit": 100,
"installed_at": "2026-02-01T00:00:00Z",
},
headers=headers,
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_delete_machine_with_parts_rejected(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "부품",
"lifecycle_type": "count",
"lifecycle_limit": 1000,
"installed_at": "2026-02-01T00:00:00Z",
},
headers=headers,
)
resp = await client.delete(f"/api/test-co/machines/{machine_id}", headers=headers)
assert resp.status_code == 409
async def _create_part(client: AsyncClient, headers: dict, machine_id: str) -> dict:
resp = await client.post(
f"/api/test-co/machines/{machine_id}/parts",
json={
"name": "카운터 테스트 부품",
"lifecycle_type": "count",
"lifecycle_limit": 1000,
"alarm_threshold": 80.0,
"counter_source": "manual",
},
headers=headers,
)
assert resp.status_code == 200
return resp.json()
@pytest.mark.asyncio
async def test_update_counter(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
part = await _create_part(client, headers, machine_id)
resp = await client.put(
f"/api/test-co/parts/{part['id']}/counter",
json={"value": 500},
headers=headers,
)
assert resp.status_code == 200
data = resp.json()
assert data["counter"]["current_value"] == 500
assert data["counter"]["lifecycle_pct"] == 50.0
assert data["counter"]["version"] == 1
@pytest.mark.asyncio
async def test_update_counter_exceeds_limit(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
part = await _create_part(client, headers, machine_id)
resp = await client.put(
f"/api/test-co/parts/{part['id']}/counter",
json={"value": 1200},
headers=headers,
)
assert resp.status_code == 200
assert resp.json()["counter"]["lifecycle_pct"] == 120.0
@pytest.mark.asyncio
async def test_update_counter_negative_rejected(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
part = await _create_part(client, headers, machine_id)
resp = await client.put(
f"/api/test-co/parts/{part['id']}/counter",
json={"value": -10},
headers=headers,
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_replace_part_resets_counter(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
part = await _create_part(client, headers, machine_id)
await client.put(
f"/api/test-co/parts/{part['id']}/counter",
json={"value": 800},
headers=headers,
)
resp = await client.post(
f"/api/test-co/parts/{part['id']}/replace",
json={"reason": "수명 도달", "notes": "교체 완료"},
headers=headers,
)
assert resp.status_code == 200
data = resp.json()
assert data["status"] == "success"
assert data["part"]["counter"]["current_value"] == 0
assert data["part"]["counter"]["lifecycle_pct"] == 0
@pytest.mark.asyncio
async def test_list_replacements(client: AsyncClient, seeded_db):
headers = await get_auth_headers(client)
machine_id = await _create_machine(client, headers)
part = await _create_part(client, headers, machine_id)
await client.put(
f"/api/test-co/parts/{part['id']}/counter",
json={"value": 500},
headers=headers,
)
await client.post(
f"/api/test-co/parts/{part['id']}/replace",
json={"reason": "1차 교체"},
headers=headers,
)
await client.put(
f"/api/test-co/parts/{part['id']}/counter",
json={"value": 300},
headers=headers,
)
await client.post(
f"/api/test-co/parts/{part['id']}/replace",
json={"reason": "2차 교체"},
headers=headers,
)
resp = await client.get(
f"/api/test-co/parts/{part['id']}/replacements",
headers=headers,
)
assert resp.status_code == 200
data = resp.json()
assert len(data["replacements"]) == 2
assert data["replacements"][0]["reason"] == "2차 교체"
assert data["replacements"][0]["counter_at_replacement"] == 300
assert data["replacements"][1]["reason"] == "1차 교체"
assert data["replacements"][1]["counter_at_replacement"] == 500