import unittest from datetime import date from unittest.mock import patch from fastapi import HTTPException from app.routers import stats class FakeResponse: def __init__(self, data=None): self.data = data or [] class FakeQuery: def __init__(self, db, table_name): self.db = db self.table_name = table_name self.filters = [] self.db.queries.append(self) def select(self, _columns): return self def eq(self, column, value): self.filters.append(("eq", column, value)) return self def gte(self, column, value): self.filters.append(("gte", column, value)) return self def lte(self, column, value): self.filters.append(("lte", column, value)) return self def lt(self, column, value): self.filters.append(("lt", column, value)) return self def order(self, _column, desc=False): return self def limit(self, _count): return self def execute(self): if self.table_name == "plants": if not self.db.plant_exists: return FakeResponse() return FakeResponse([{"id": "plant-a", "name": "1호기", "capacity": 100}]) if self.table_name == "solar_logs": return FakeResponse(self.db.solar_logs) if self.table_name == "monthly_stats": return FakeResponse(self.db.monthly_stats) return FakeResponse() class FakeDb: def __init__(self, solar_logs=None, monthly_stats=None, plant_exists=True): self.solar_logs = solar_logs or [] self.monthly_stats = monthly_stats or [] self.plant_exists = plant_exists self.queries = [] def table(self, table_name): return FakeQuery(self, table_name) def latest_query(self, table_name): return next(query for query in reversed(self.queries) if query.table_name == table_name) class StatsTimezoneTest(unittest.TestCase): def test_comparison_today_uses_kst_utc_half_open_bounds(self): db = FakeDb([{"plant_id": "plant-a", "today_kwh": 123.4}]) with patch.object(stats, "today_kst", return_value=date(2026, 1, 1)): result = stats.get_all_plants_comparison( period="day", date=None, year=None, month=None, db=db ) query = db.latest_query("solar_logs") self.assertIn(("gte", "created_at", "2025-12-31T15:00:00+00:00"), query.filters) self.assertIn(("lt", "created_at", "2026-01-01T15:00:00+00:00"), query.filters) self.assertEqual(123.4, result.data[0].generation) def test_comparison_filters_plants_by_company_when_requested(self): db = FakeDb() stats.get_all_plants_comparison( period="day", date="2026-01-01", year=None, month=None, db=db, company_id=7, ) query = db.latest_query("plants") self.assertIn(("eq", "company_id", 7), query.filters) def test_plant_today_query_has_upper_bound(self): db = FakeDb([{"today_kwh": 77.0}]) with patch.object(stats, "today_kst", return_value=date(2026, 8, 6)): result = stats.get_plant_stats( plant_id="plant-a", period="day", year=2026, month=8, db=db ) query = db.latest_query("solar_logs") self.assertIn(("gte", "created_at", "2026-08-05T15:00:00+00:00"), query.filters) self.assertIn(("lt", "created_at", "2026-08-06T15:00:00+00:00"), query.filters) self.assertEqual(77.0, result.data[5].value) def test_hourly_stats_map_utc_edges_to_kst_hours(self): db = FakeDb([ {"created_at": "2026-12-30T15:00:00Z", "current_kw": 1, "today_kwh": 1}, {"created_at": "2026-12-31T14:59:59Z", "current_kw": 2, "today_kwh": 2}, {"created_at": "2026-12-31T15:00:00Z", "current_kw": 3, "today_kwh": 3}, ]) result = stats.get_plant_hourly_stats( plant_id="plant-a", date="2026-12-31", db=db ) query = db.latest_query("solar_logs") self.assertIn(("gte", "created_at", "2026-12-30T15:00:00+00:00"), query.filters) self.assertIn(("lt", "created_at", "2026-12-31T15:00:00+00:00"), query.filters) self.assertTrue(result.data[0].has_data) self.assertTrue(result.data[23].has_data) self.assertEqual(2, result.count) def test_invalid_hourly_date_remains_400(self): with self.assertRaises(HTTPException) as raised: stats.get_plant_hourly_stats( plant_id="plant-a", date="2026-13-01", db=FakeDb() ) self.assertEqual(400, raised.exception.status_code) def test_invalid_comparison_date_is_not_replaced_with_today(self): db = FakeDb() with self.assertRaises(HTTPException) as raised: stats.get_all_plants_comparison( period="day", date="2026-8-07", year=None, month=None, db=db ) self.assertEqual(400, raised.exception.status_code) self.assertEqual([], db.queries) def test_unknown_plant_stats_returns_404(self): with self.assertRaises(HTTPException) as raised: stats.get_plant_stats( plant_id="missing", period="day", year=2026, month=8, db=FakeDb(plant_exists=False), ) self.assertEqual(404, raised.exception.status_code) def test_leap_year_uses_366_days(self): db = FakeDb(monthly_stats=[{ "plant_id": "plant-a", "total_generation": 36600, }]) result = stats.get_all_plants_comparison( period="year", date="2024-12-31", year=2024, month=None, db=db ) self.assertEqual(1.0, result.data[0].generation_hours) def test_selected_year_is_used_as_yearly_query_end(self): db = FakeDb() stats.get_plant_stats( plant_id="plant-a", period="year", year=2024, month=None, db=db ) query = db.latest_query("monthly_stats") self.assertIn(("lte", "month", "2024-12"), query.filters) if __name__ == "__main__": unittest.main()