solorpower/api_server/tests/test_stats_timezone.py
haneulai a716dbef96
Some checks are pending
CI / Crawler (Python ${{ matrix.python-version }}) (3.10) (push) Waiting to run
CI / Crawler (Python ${{ matrix.python-version }}) (3.11) (push) Waiting to run
CI / API (Python 3.11) (push) Waiting to run
CI / Database migration (push) Waiting to run
CI / App web build (Node 20) (push) Waiting to run
feat: harden solar monitoring through stage 7
2026-08-07 14:07:22 +09:00

171 lines
5.6 KiB
Python

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_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()