first commit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from app.stats_store import StatsStore, business_date
|
||||
|
||||
|
||||
class StatsStoreDurationRepairTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp_dir = tempfile.TemporaryDirectory()
|
||||
self.database_path = Path(self.temp_dir.name) / "statistics.sqlite3"
|
||||
self.connection = sqlite3.connect(self.database_path)
|
||||
StatsStore._migrate(self.connection)
|
||||
self.store = StatsStore(self.database_path)
|
||||
|
||||
def tearDown(self):
|
||||
self.connection.close()
|
||||
self.temp_dir.cleanup()
|
||||
|
||||
def _insert_bilibili(self, connection_id, started, ended=None, reason=None):
|
||||
self.connection.execute(
|
||||
"INSERT INTO bilibili_connections ("
|
||||
"connection_id, connected_at_utc, disconnected_at_utc, business_date, status, disconnect_reason"
|
||||
") VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(connection_id, started, ended, business_date(started), "failed" if ended else "connected", reason),
|
||||
)
|
||||
StatsStore._upsert_live_session(self.connection, {
|
||||
"session_id": connection_id,
|
||||
"kind": "bilibili_connection",
|
||||
"started_at_utc": started,
|
||||
"ended_at_utc": ended,
|
||||
"status": "failed" if ended else "connected",
|
||||
"source": "bilibili_connections",
|
||||
})
|
||||
|
||||
def test_daily_query_merges_overlapping_lifecycles(self):
|
||||
self._insert_bilibili("b1", "2026-07-16T00:00:00Z", "2026-07-16T02:00:00Z")
|
||||
self._insert_bilibili("b2", "2026-07-16T01:00:00Z", "2026-07-16T03:00:00Z")
|
||||
self.connection.execute(
|
||||
"INSERT INTO group_runs (group_run_id, started_at_utc, ended_at_utc, business_date, status) "
|
||||
"VALUES ('g1', '2026-07-16T00:00:00Z', '2026-07-16T02:00:00Z', '2026-07-16', 'completed')"
|
||||
)
|
||||
self.connection.execute(
|
||||
"INSERT INTO group_runs (group_run_id, started_at_utc, ended_at_utc, business_date, status) "
|
||||
"VALUES ('g2', '2026-07-16T01:00:00Z', '2026-07-16T04:00:00Z', '2026-07-16', 'completed')"
|
||||
)
|
||||
self.connection.commit()
|
||||
|
||||
row = self.store._query_daily_sync("2026-07-16", "2026-07-16")[0]
|
||||
|
||||
self.assertEqual(row["bilibili_connection_duration_ms"], 3 * 60 * 60 * 1000)
|
||||
self.assertEqual(row["group_run_total_duration_ms"], 4 * 60 * 60 * 1000)
|
||||
|
||||
def test_daily_query_splits_at_beijing_0400_boundary(self):
|
||||
self.connection.execute(
|
||||
"INSERT INTO group_runs (group_run_id, started_at_utc, ended_at_utc, business_date, status) "
|
||||
"VALUES ('g1', '2026-07-16T19:30:00Z', '2026-07-16T20:30:00Z', '2026-07-16', 'completed')"
|
||||
)
|
||||
self.connection.commit()
|
||||
|
||||
rows = self.store._query_daily_sync("2026-07-16", "2026-07-17")
|
||||
by_day = {row["business_date"]: row for row in rows}
|
||||
|
||||
self.assertEqual(by_day["2026-07-16"]["group_run_total_duration_ms"], 30 * 60 * 1000)
|
||||
self.assertEqual(by_day["2026-07-17"]["group_run_total_duration_ms"], 30 * 60 * 1000)
|
||||
|
||||
def test_reconciliation_ends_stale_connection_at_next_start(self):
|
||||
self._insert_bilibili("b1", "2026-07-16T00:00:00Z")
|
||||
self._insert_bilibili("b2", "2026-07-16T00:05:00Z")
|
||||
self.connection.commit()
|
||||
|
||||
with patch.object(StatsStore, "_pid_is_running", return_value=False):
|
||||
StatsStore._reconcile_stale_lifecycles(self.connection)
|
||||
|
||||
first = self.connection.execute(
|
||||
"SELECT disconnected_at_utc, disconnect_reason FROM bilibili_connections WHERE connection_id='b1'"
|
||||
).fetchone()
|
||||
self.assertEqual(first, ("2026-07-16T00:05:00Z", "startup_reconciliation"))
|
||||
|
||||
def test_historical_repair_is_idempotent_and_rebuilds_daily_duration(self):
|
||||
self._insert_bilibili(
|
||||
"b1", "2026-07-16T00:00:00Z", "2026-07-17T00:00:00Z", "startup_reconciliation"
|
||||
)
|
||||
self._insert_bilibili("b2", "2026-07-16T00:05:00Z", "2026-07-16T00:10:00Z")
|
||||
self.connection.commit()
|
||||
|
||||
first = StatsStore._repair_startup_reconciliation_history(self.connection)
|
||||
second = StatsStore._repair_startup_reconciliation_history(self.connection)
|
||||
|
||||
ended = self.connection.execute(
|
||||
"SELECT disconnected_at_utc FROM bilibili_connections WHERE connection_id='b1'"
|
||||
).fetchone()[0]
|
||||
duration = self.connection.execute(
|
||||
"SELECT SUM(duration_ms) FROM live_session_daily_durations WHERE session_id='b1'"
|
||||
).fetchone()[0]
|
||||
self.assertEqual(first["bilibili_connections"], 1)
|
||||
self.assertEqual(second["bilibili_connections"], 0)
|
||||
self.assertEqual(ended, "2026-07-16T00:05:00Z")
|
||||
self.assertEqual(duration, 5 * 60 * 1000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user