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"] async def _create_template( client: AsyncClient, headers: dict, tenant_id: str = "test-co", name: str = "일일 설비검사", subject_type: str = "equipment", schedule_type: str = "daily", machine_id: str = None, items: list = None, ) -> dict: body = { "name": name, "subject_type": subject_type, "schedule_type": schedule_type, "inspection_mode": "measurement", } if machine_id: body["machine_id"] = machine_id if items: body["items"] = items resp = await client.post(f"/api/{tenant_id}/templates", json=body, headers=headers) assert resp.status_code == 200, f"Template create failed: {resp.json()}" return resp.json() @pytest.mark.asyncio async def test_create_template_with_items(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) machine_id = await _create_machine(client, headers) data = await _create_template( client, headers, machine_id=machine_id, items=[ { "name": "유압 압력", "data_type": "numeric", "unit": "bar", "spec_min": 10.0, "spec_max": 20.0, }, { "name": "외관 상태", "data_type": "boolean", }, { "name": "비고", "data_type": "text", "is_required": False, }, ], ) assert data["name"] == "일일 설비검사" assert data["subject_type"] == "equipment" assert data["machine_id"] == machine_id assert data["version"] == 1 assert data["items_count"] == 3 assert len(data["items"]) == 3 assert data["items"][0]["name"] == "유압 압력" assert data["items"][0]["sort_order"] == 1 assert data["items"][0]["spec_min"] == 10.0 assert data["items"][1]["name"] == "외관 상태" assert data["items"][1]["sort_order"] == 2 assert data["items"][2]["is_required"] is False @pytest.mark.asyncio async def test_create_template_no_items(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) data = await _create_template( client, headers, name="빈 템플릿", subject_type="product", schedule_type="weekly", ) assert data["subject_type"] == "product" assert data["items_count"] == 0 @pytest.mark.asyncio async def test_list_templates(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) await _create_template(client, headers, name="설비검사A", subject_type="equipment") await _create_template(client, headers, name="품질검사A", subject_type="product") await _create_template(client, headers, name="설비검사B", subject_type="equipment") resp = await client.get("/api/test-co/templates", headers=headers) assert resp.status_code == 200 assert len(resp.json()["templates"]) == 3 resp2 = await client.get( "/api/test-co/templates?subject_type=equipment", headers=headers ) assert len(resp2.json()["templates"]) == 2 resp3 = await client.get( "/api/test-co/templates?subject_type=product", headers=headers ) assert len(resp3.json()["templates"]) == 1 @pytest.mark.asyncio async def test_get_template_detail(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template( client, headers, items=[{"name": "온도", "data_type": "numeric", "unit": "℃"}], ) template_id = created["id"] resp = await client.get(f"/api/test-co/templates/{template_id}", headers=headers) assert resp.status_code == 200 data = resp.json() assert data["id"] == template_id assert len(data["items"]) == 1 assert data["items"][0]["unit"] == "℃" @pytest.mark.asyncio async def test_update_template_increments_version(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template(client, headers) template_id = created["id"] assert created["version"] == 1 resp = await client.put( f"/api/test-co/templates/{template_id}", json={"name": "수정된 설비검사", "schedule_type": "weekly"}, headers=headers, ) assert resp.status_code == 200 data = resp.json() assert data["name"] == "수정된 설비검사" assert data["schedule_type"] == "weekly" assert data["version"] == 2 @pytest.mark.asyncio async def test_delete_template_soft_delete(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template(client, headers) template_id = created["id"] resp = await client.delete(f"/api/test-co/templates/{template_id}", headers=headers) assert resp.status_code == 200 assert resp.json()["status"] == "success" list_resp = await client.get("/api/test-co/templates", headers=headers) assert len(list_resp.json()["templates"]) == 0 detail_resp = await client.get( f"/api/test-co/templates/{template_id}", headers=headers ) assert detail_resp.status_code == 404 @pytest.mark.asyncio async def test_add_item_to_template(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template(client, headers) template_id = created["id"] resp = await client.post( f"/api/test-co/templates/{template_id}/items", json={ "name": "습도", "data_type": "numeric", "unit": "%", "spec_min": 30.0, "spec_max": 70.0, }, headers=headers, ) assert resp.status_code == 200 item = resp.json() assert item["name"] == "습도" assert item["sort_order"] == 1 assert item["spec_min"] == 30.0 detail = await client.get(f"/api/test-co/templates/{template_id}", headers=headers) assert detail.json()["version"] == 2 @pytest.mark.asyncio async def test_update_item(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template( client, headers, items=[{"name": "압력", "data_type": "numeric", "unit": "bar"}], ) template_id = created["id"] item_id = created["items"][0]["id"] resp = await client.put( f"/api/test-co/templates/{template_id}/items/{item_id}", json={"name": "유압 압력", "spec_min": 5.0, "spec_max": 15.0}, headers=headers, ) assert resp.status_code == 200 assert resp.json()["name"] == "유압 압력" assert resp.json()["spec_min"] == 5.0 @pytest.mark.asyncio async def test_delete_item_reorders(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template( client, headers, items=[ {"name": "항목1", "data_type": "boolean"}, {"name": "항목2", "data_type": "boolean"}, {"name": "항목3", "data_type": "boolean"}, ], ) template_id = created["id"] item_to_delete = created["items"][0]["id"] resp = await client.delete( f"/api/test-co/templates/{template_id}/items/{item_to_delete}", headers=headers, ) assert resp.status_code == 200 detail = await client.get(f"/api/test-co/templates/{template_id}", headers=headers) items = detail.json()["items"] assert len(items) == 2 assert items[0]["name"] == "항목2" assert items[0]["sort_order"] == 1 assert items[1]["name"] == "항목3" assert items[1]["sort_order"] == 2 @pytest.mark.asyncio async def test_reorder_items(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template( client, headers, items=[ {"name": "A", "data_type": "boolean"}, {"name": "B", "data_type": "boolean"}, {"name": "C", "data_type": "boolean"}, ], ) template_id = created["id"] ids = [item["id"] for item in created["items"]] resp = await client.put( f"/api/test-co/templates/{template_id}/items/reorder", json={"item_ids": [ids[2], ids[0], ids[1]]}, headers=headers, ) assert resp.status_code == 200 items = resp.json()["items"] assert items[0]["name"] == "C" assert items[0]["sort_order"] == 1 assert items[1]["name"] == "A" assert items[1]["sort_order"] == 2 assert items[2]["name"] == "B" assert items[2]["sort_order"] == 3 @pytest.mark.asyncio async def test_select_data_type_with_options(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) created = await _create_template( client, headers, items=[ { "name": "외관 상태", "data_type": "select", "select_options": ["양호", "불량", "N/A"], }, ], ) item = created["items"][0] assert item["data_type"] == "select" assert item["select_options"] == ["양호", "불량", "N/A"] @pytest.mark.asyncio async def test_invalid_subject_type(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) resp = await client.post( "/api/test-co/templates", json={ "name": "잘못된 템플릿", "subject_type": "invalid", "schedule_type": "daily", }, headers=headers, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_invalid_data_type(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) resp = await client.post( "/api/test-co/templates", json={ "name": "잘못된 항목", "subject_type": "equipment", "schedule_type": "daily", "items": [{"name": "잘못", "data_type": "invalid_type"}], }, headers=headers, ) assert resp.status_code == 400 @pytest.mark.asyncio async def test_tenant_isolation(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) await _create_template(client, headers, name="테스트 템플릿") other_headers = await get_auth_headers( client, email="admin@test-co.com", password="pass1234" ) resp = await client.get("/api/other-co/templates", headers=other_headers) assert resp.status_code == 403 @pytest.mark.asyncio async def test_equipment_part_link(client: AsyncClient, seeded_db): headers = await get_auth_headers(client) machine_id = await _create_machine(client, headers) part_resp = await client.post( f"/api/test-co/machines/{machine_id}/parts", json={ "name": "베어링", "lifecycle_type": "hours", "lifecycle_limit": 5000, }, headers=headers, ) part_id = part_resp.json()["id"] created = await _create_template( client, headers, machine_id=machine_id, items=[ { "name": "베어링 온도", "data_type": "numeric", "unit": "℃", "equipment_part_id": part_id, }, ], ) assert created["items"][0]["equipment_part_id"] == part_id