solorpower/crawler/tests/test_database_history.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

172 lines
5.4 KiB
Python

import unittest
from unittest.mock import patch
import database
class FakeResponse:
def __init__(self, data=None):
self.data = data or []
class FakeRpc:
def __init__(self, client, params):
self.client = client
self.params = params
def execute(self):
if self.client.fail_rpc:
raise RuntimeError("rpc failed")
saved = []
for row in self.params["p_records"]:
key = (row["plant_id"], row["date"])
existing = self.client.daily.get(key)
incoming = row["total_generation"]
if (
existing is None
or self.params["p_allow_decrease"]
or incoming > existing
):
self.client.daily[key] = incoming
saved.append({**row, "total_generation": self.client.daily[key]})
self.client.rpc_calls.append(self.params)
return FakeResponse(saved)
class FakeQuery:
def __init__(self, client, table_name):
self.client = client
self.table_name = table_name
self.action = None
self.payload = None
self.filters = {}
def select(self, _columns):
self.action = "select"
return self
def upsert(self, payload, on_conflict=None):
self.action = "upsert"
self.payload = payload
self.on_conflict = on_conflict
return self
def eq(self, column, value):
self.filters[column] = ("eq", value)
return self
def in_(self, column, values):
self.filters[column] = ("in", set(values))
return self
def gte(self, column, value):
self.filters[f"{column}_gte"] = ("gte", value)
return self
def lte(self, column, value):
self.filters[f"{column}_lte"] = ("lte", value)
return self
def execute(self):
if self.client.fail_select and self.action == "select":
raise RuntimeError("select failed")
if self.action == "select" and self.table_name == "daily_stats":
rows = []
for (plant_id, date), value in self.client.daily.items():
if "plant_id" in self.filters and plant_id != self.filters["plant_id"][1]:
continue
if "date" in self.filters and date not in self.filters["date"][1]:
continue
if "date_gte" in self.filters and date < self.filters["date_gte"][1]:
continue
if "date_lte" in self.filters and date > self.filters["date_lte"][1]:
continue
rows.append({
"plant_id": plant_id,
"date": date,
"total_generation": value,
})
return FakeResponse(rows)
if self.action == "upsert" and self.table_name == "daily_stats":
for row in self.payload:
self.client.daily[(row["plant_id"], row["date"])] = row["total_generation"]
self.client.daily_upserts.append(self.payload)
return FakeResponse(self.payload)
if self.action == "upsert" and self.table_name == "monthly_stats":
self.client.monthly_upserts.append(self.payload)
return FakeResponse(self.payload)
raise AssertionError(f"unexpected query: {self.table_name} {self.action}")
class FakeClient:
def __init__(self, daily=None, fail_rpc=False):
self.daily = daily or {}
self.fail_select = False
self.fail_rpc = fail_rpc
self.daily_upserts = []
self.monthly_upserts = []
self.rpc_calls = []
def table(self, table_name):
return FakeQuery(self, table_name)
def rpc(self, function_name, params):
if function_name != "upsert_daily_stats":
raise AssertionError(function_name)
return FakeRpc(self, params)
class DatabaseHistoryTest(unittest.TestCase):
def save_daily(self, client, generation):
with patch.object(database, "get_supabase_client", return_value=client):
return database.save_history([{
"plant_id": "plant-a",
"date": "2026-08-05",
"generation_kwh": generation,
}], "daily")
def test_existing_positive_value_is_not_replaced_by_zero(self):
client = FakeClient({("plant-a", "2026-08-05"): 100.0})
result = self.save_daily(client, 0.0)
self.assertTrue(result)
self.assertEqual(client.daily[("plant-a", "2026-08-05")], 100.0)
self.assertEqual(client.daily_upserts, [])
self.assertEqual(client.rpc_calls[0]["p_source"], "history")
self.assertFalse(client.rpc_calls[0]["p_allow_decrease"])
def test_larger_value_updates_existing_daily_stat(self):
client = FakeClient({("plant-a", "2026-08-05"): 100.0})
result = self.save_daily(client, 120.0)
self.assertTrue(result)
self.assertEqual(client.daily[("plant-a", "2026-08-05")], 120.0)
self.assertEqual(len(client.rpc_calls), 1)
def test_rpc_failure_prevents_write(self):
client = FakeClient(fail_rpc=True)
result = self.save_daily(client, 120.0)
self.assertFalse(result)
self.assertEqual(client.rpc_calls, [])
def test_negative_generation_is_rejected(self):
client = FakeClient()
result = self.save_daily(client, -1.0)
self.assertFalse(result)
self.assertEqual(client.rpc_calls, [])
if __name__ == "__main__":
unittest.main()