354 lines
11 KiB
Python
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
|