Files
Live-streaming/tests/test_stats_store.py
T

106 lines
4.7 KiB
Python

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