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
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
import sqlite3
|
|
import tempfile
|
|
import unittest
|
|
from contextlib import closing
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
import backward_backfill
|
|
|
|
|
|
def make_plant(site_id="plant-a", start_date="2026-08-01"):
|
|
return {
|
|
"id": site_id,
|
|
"name": site_id,
|
|
"type": "fake",
|
|
"start_date": start_date,
|
|
"options": {},
|
|
}
|
|
|
|
|
|
class BackwardBackfillTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
self.db_path = str(Path(self.temp_dir.name) / "crawler_manager.db")
|
|
self.db_path_patch = patch.object(backward_backfill, "DB_PATH", self.db_path)
|
|
self.db_path_patch.start()
|
|
|
|
def tearDown(self):
|
|
self.db_path_patch.stop()
|
|
self.temp_dir.cleanup()
|
|
|
|
def read_state(self, site_id):
|
|
with closing(sqlite3.connect(self.db_path)) as conn:
|
|
return conn.execute(
|
|
"""
|
|
SELECT last_backfilled_date, consecutive_zero_count, status
|
|
FROM backfill_state
|
|
WHERE site_id = ?
|
|
""",
|
|
(site_id,),
|
|
).fetchone()
|
|
|
|
def init_sites(self, plants, first_target="2026-08-05"):
|
|
with patch.object(backward_backfill, "get_all_plants", return_value=plants):
|
|
backward_backfill.init_backfill_states(first_target)
|
|
|
|
def run_process(self, plants, crawler, save_result=True, days=1):
|
|
with (
|
|
patch.object(backward_backfill, "get_all_plants", return_value=plants),
|
|
patch.object(backward_backfill, "get_history_crawler", return_value=crawler),
|
|
patch.object(backward_backfill, "save_history", return_value=save_result),
|
|
):
|
|
backward_backfill.process_backfill(
|
|
max_days_per_run=days,
|
|
delay_sec=0,
|
|
)
|
|
|
|
def test_first_requested_date_is_not_skipped(self):
|
|
plant = make_plant()
|
|
requested_dates = []
|
|
self.init_sites([plant])
|
|
|
|
self.assertEqual(self.read_state("plant-a")[0], "2026-08-06")
|
|
|
|
def crawler(_plant, start_date, _end_date):
|
|
requested_dates.append(start_date)
|
|
return [{
|
|
"plant_id": "plant-a",
|
|
"date": start_date,
|
|
"generation_kwh": 12.0,
|
|
}]
|
|
|
|
self.run_process([plant], crawler)
|
|
|
|
self.assertEqual(requested_dates, ["2026-08-05"])
|
|
self.assertEqual(self.read_state("plant-a"), ("2026-08-05", 0, "RUNNING"))
|
|
|
|
def test_fetch_failure_keeps_cursor_and_zero_count(self):
|
|
plant = make_plant()
|
|
self.init_sites([plant])
|
|
backward_backfill.update_backfill_state("plant-a", "2026-08-06", 7, "RUNNING")
|
|
|
|
self.run_process([plant], lambda *_args: None)
|
|
|
|
self.assertEqual(self.read_state("plant-a"), ("2026-08-06", 7, "RUNNING"))
|
|
|
|
def test_save_failure_keeps_cursor_and_zero_count(self):
|
|
plant = make_plant()
|
|
self.init_sites([plant])
|
|
|
|
def crawler(_plant, start_date, _end_date):
|
|
return [{
|
|
"plant_id": "plant-a",
|
|
"date": start_date,
|
|
"generation_kwh": 5.0,
|
|
}]
|
|
|
|
self.run_process([plant], crawler, save_result=False)
|
|
|
|
self.assertEqual(self.read_state("plant-a"), ("2026-08-06", 0, "RUNNING"))
|
|
|
|
def test_successful_no_data_advances_without_counting_zero(self):
|
|
plant = make_plant()
|
|
self.init_sites([plant])
|
|
backward_backfill.update_backfill_state("plant-a", "2026-08-06", 4, "RUNNING")
|
|
|
|
self.run_process([plant], lambda *_args: [])
|
|
|
|
self.assertEqual(self.read_state("plant-a"), ("2026-08-05", 0, "RUNNING"))
|
|
|
|
def test_thirtieth_actual_zero_completes_site(self):
|
|
plant = make_plant(start_date="2026-01-01")
|
|
self.init_sites([plant], first_target="2026-08-30")
|
|
backward_backfill.update_backfill_state("plant-a", "2026-08-31", 29, "RUNNING")
|
|
|
|
def crawler(_plant, start_date, _end_date):
|
|
return [{
|
|
"plant_id": "plant-a",
|
|
"date": start_date,
|
|
"generation_kwh": 0.0,
|
|
}]
|
|
|
|
self.run_process([plant], crawler)
|
|
|
|
self.assertEqual(self.read_state("plant-a"), ("2026-08-30", 30, "COMPLETED"))
|
|
|
|
def test_one_site_failure_does_not_block_another_site(self):
|
|
plants = [make_plant("plant-a"), make_plant("plant-b")]
|
|
self.init_sites(plants)
|
|
|
|
def crawler(plant, start_date, _end_date):
|
|
if plant["id"] == "plant-a":
|
|
return None
|
|
return [{
|
|
"plant_id": "plant-b",
|
|
"date": start_date,
|
|
"generation_kwh": 9.0,
|
|
}]
|
|
|
|
self.run_process(plants, crawler)
|
|
|
|
self.assertEqual(self.read_state("plant-a")[0], "2026-08-06")
|
|
self.assertEqual(self.read_state("plant-b")[0], "2026-08-05")
|
|
|
|
def test_start_date_is_processed_before_completion(self):
|
|
plant = make_plant(start_date="2026-08-05")
|
|
self.init_sites([plant])
|
|
|
|
def crawler(_plant, start_date, _end_date):
|
|
return [{
|
|
"plant_id": "plant-a",
|
|
"date": start_date,
|
|
"generation_kwh": 1.0,
|
|
}]
|
|
|
|
self.run_process([plant], crawler, days=2)
|
|
|
|
self.assertEqual(self.read_state("plant-a"), ("2026-08-05", 0, "COMPLETED"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|