"""Verifies _update_stage_bb_phase actually writes to burnin_stages and the migration adds the columns idempotently. The drive-drawer's 4-meter UI depends on these columns being populated on every parser tick. If a future refactor drops the call or breaks the migration, this test catches it before users see the meters go blank. Run inside the container image so app deps are present. """ from __future__ import annotations import os import tempfile import unittest import aiosqlite async def _setup_db_with_stage() -> str: fd, path = tempfile.mkstemp(suffix=".db") os.close(fd) from app.config import settings settings.db_path = path from app.database import init_db await init_db() async with aiosqlite.connect(path) as db: await db.execute( "INSERT INTO drives " "(truenas_disk_id, devname, serial, model, size_bytes, " " temperature_c, smart_health, last_seen_at, last_polled_at) " "VALUES ('id-1', 'sda', 'SER1', 'TestModel', 14000000000000, " " 30, 'PASSED', '2026-05-09T00:00:00+00:00', " " '2026-05-09T00:00:00+00:00')" ) await db.execute( "INSERT INTO burnin_jobs " "(drive_id, profile, state, operator, created_at) " "VALUES (1, 'surface', 'running', 'op', " " '2026-05-09T00:00:00+00:00')" ) await db.execute( "INSERT INTO burnin_stages " "(burnin_job_id, stage_name, state) " "VALUES (1, 'surface_validate', 'running')" ) await db.commit() return path class TestBBPhasePersistence(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self): self.path = await _setup_db_with_stage() async def asyncTearDown(self): try: os.unlink(self.path) except OSError: pass async def test_columns_exist_after_init(self): async with aiosqlite.connect(self.path) as db: cur = await db.execute("PRAGMA table_info(burnin_stages)") cols = {r[1] for r in await cur.fetchall()} self.assertIn("bb_phase", cols) self.assertIn("bb_phase_pct", cols) async def test_update_writes_phase_and_pct(self): from app.burnin._common import _update_stage_bb_phase await _update_stage_bb_phase(1, "surface_validate", 3, 47.5) async with aiosqlite.connect(self.path) as db: cur = await db.execute( "SELECT bb_phase, bb_phase_pct FROM burnin_stages " "WHERE burnin_job_id=1 AND stage_name='surface_validate'" ) row = await cur.fetchone() self.assertEqual(row[0], 3) self.assertAlmostEqual(row[1], 47.5) async def test_update_overwrites(self): """Each tick should replace the previous value, not accumulate.""" from app.burnin._common import _update_stage_bb_phase await _update_stage_bb_phase(1, "surface_validate", 1, 10.0) await _update_stage_bb_phase(1, "surface_validate", 2, 80.0) async with aiosqlite.connect(self.path) as db: cur = await db.execute( "SELECT bb_phase, bb_phase_pct FROM burnin_stages " "WHERE burnin_job_id=1 AND stage_name='surface_validate'" ) row = await cur.fetchone() self.assertEqual(row[0], 2) self.assertAlmostEqual(row[1], 80.0) if __name__ == "__main__": unittest.main()