import unittest from types import SimpleNamespace from fastapi import HTTPException from app.routers import plants PLANT = { "id": "nrems-03", "company_id": 1, "name": "3호기", "capacity": 99.0, "location": None, "alerts_enabled": True, "created_at": "2026-01-01T00:00:00+00:00", } LOG = { "id": 10, "plant_id": "nrems-03", "current_kw": 20.0, "today_kwh": 100.0, "status": "정상", "created_at": "2026-08-07T03:00:00+00:00", } class FakeQuery: def __init__(self, db, table_name): self.db = db self.table_name = table_name self.columns = None self.filters = [] self.action = "select" self.payload = None self.db.queries.append(self) def select(self, columns): self.columns = columns return self def eq(self, column, value): self.filters.append((column, value)) return self def order(self, _column, desc=False, foreign_table=None): self.foreign_table = foreign_table return self def limit(self, _count, foreign_table=None): self.limit_foreign_table = foreign_table return self def update(self, payload): self.action = "update" self.payload = payload return self def execute(self): if self.db.query_failure: raise RuntimeError("database unavailable") if self.table_name == "plants": if not self.db.plant_exists: return SimpleNamespace(data=[]) if self.action == "update": updated = dict(PLANT, **self.payload) return SimpleNamespace(data=[updated]) if "solar_logs(" in self.columns: return SimpleNamespace(data=[dict(PLANT, solar_logs=[LOG])]) return SimpleNamespace(data=[PLANT]) if self.table_name == "solar_logs": return SimpleNamespace(data=[LOG]) raise AssertionError(f"unexpected table: {self.table_name}") class FakeDb: def __init__(self, plant_exists=True, query_failure=False): self.plant_exists = plant_exists self.query_failure = query_failure self.queries = [] def table(self, table_name): return FakeQuery(self, table_name) class PlantsApiTest(unittest.TestCase): def test_company_list_uses_one_nested_query(self): db = FakeDb() result = plants.get_plants_by_company(company_id=1, db=db) self.assertEqual(1, len(db.queries)) self.assertEqual("plants", db.queries[0].table_name) self.assertEqual("solar_logs", db.queries[0].foreign_table) self.assertEqual("solar_logs", db.queries[0].limit_foreign_table) self.assertEqual("nrems-03", result.data[0].id) self.assertEqual(10, result.data[0].latest_log.id) def test_company_without_plants_returns_empty_success(self): result = plants.get_plants_by_company( company_id=999, db=FakeDb(plant_exists=False), ) self.assertEqual("success", result.status) self.assertEqual([], result.data) self.assertEqual(0, result.total_count) def test_company_query_failure_returns_500(self): with self.assertRaises(HTTPException) as raised: plants.get_plants_by_company( company_id=1, db=FakeDb(query_failure=True), ) self.assertEqual(500, raised.exception.status_code) def test_string_plant_id_is_used_for_detail_lookup(self): db = FakeDb() result = plants.get_plant_detail( company_id=1, plant_id="nrems-03", db=db ) plant_query = db.queries[0] self.assertIn(("id", "nrems-03"), plant_query.filters) self.assertEqual("nrems-03", result["data"]["plant"]["id"]) self.assertEqual(10, result["data"]["recent_logs"][0]["id"]) def test_missing_detail_returns_404(self): with self.assertRaises(HTTPException) as raised: plants.get_plant_detail( company_id=1, plant_id="missing", db=FakeDb(plant_exists=False), ) self.assertEqual(404, raised.exception.status_code) def test_alert_update_uses_typed_response(self): result = plants.update_plant_alerts( company_id=1, plant_id="nrems-03", alert_update=plants.PlantAlertUpdateRequest(alerts_enabled=False), db=FakeDb(), ) self.assertFalse(result.data.alerts_enabled) if __name__ == "__main__": unittest.main()