# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """Carrying the pre-0.1.0 counter into the event log.""" from __future__ import annotations from typing import TYPE_CHECKING from signal_bot.stats.backfill import ( UNKNOWN_HOUR, backfilled_day, has_legacy_counter, seed_from_counter, ) from signal_bot.stats.queries_day import day_rows, day_totals if TYPE_CHECKING: from signal_bot.store import Store GROUP = "group-a==" OTHER = "group-b==" DAY = "2026-08-30" LEGACY = """ CREATE TABLE message_counts ( group_id TEXT NOT NULL, uuid TEXT NOT NULL, display_name TEXT NOT NULL, count INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (group_id, uuid) ); """ def give_legacy_counter(store: Store, rows: list[tuple[str, str, str, int]]) -> None: """Recreate the pre-0.1.0 table and fill it, as a real old database has.""" store.events.connection.executescript(LEGACY) store.events.connection.executemany( "INSERT INTO message_counts (group_id, uuid, display_name, count) VALUES (?, ?, ?, ?)", rows, ) store.events.connection.commit() def test_a_fresh_database_has_no_legacy_counter(store: Store) -> None: """0.1.0 stopped declaring the table, so a new file never has one.""" assert has_legacy_counter(store) is False def test_an_old_database_is_detected(store: Store) -> None: give_legacy_counter(store, [(GROUP, "u1", "Ala", 3)]) assert has_legacy_counter(store) is True def test_counts_become_events_on_the_named_day(store: Store) -> None: give_legacy_counter( store, [(GROUP, "u1", "Ala", 3), (GROUP, "u2", "kuhy", 2), (OTHER, "u1", "Ala", 1)], ) seeded = seed_from_counter(store, DAY) assert seeded == {GROUP: 5, OTHER: 1} rows = day_rows(store.events, GROUP, DAY) assert [(r.display_name, r.messages) for r in rows] == [("Ala", 3), ("kuhy", 2)] assert day_totals(rows).messages == 5 def test_the_seeded_day_is_marked_per_group(store: Store) -> None: """Marked, so records and hour statistics can leave it out.""" give_legacy_counter(store, [(GROUP, "u1", "Ala", 1), (OTHER, "u2", "kuhy", 1)]) seed_from_counter(store, DAY) assert backfilled_day(store, GROUP) == DAY assert backfilled_day(store, OTHER) == DAY def test_an_unseeded_group_is_not_marked(store: Store) -> None: give_legacy_counter(store, [(GROUP, "u1", "Ala", 1)]) seed_from_counter(store, DAY) assert backfilled_day(store, OTHER) is None def test_zero_counts_are_skipped(store: Store) -> None: """A row at zero is somebody who was reset, not somebody who spoke.""" give_legacy_counter(store, [(GROUP, "u1", "Ala", 0)]) assert seed_from_counter(store, DAY) == {} assert day_rows(store.events, GROUP, DAY) == [] assert backfilled_day(store, GROUP) is None def test_seeded_people_are_known_to_the_roster(store: Store) -> None: """Otherwise they would show as unnamed strangers in the first report.""" give_legacy_counter(store, [(GROUP, "u1", "Ala", 2)]) seed_from_counter(store, DAY) assert [p.display_name for p in store.events.roster(GROUP, ["u1"])] == ["Ala"] def test_seeded_events_carry_no_invented_detail(store: Store) -> None: """No hours, no lengths, no media -- the counter knew none of them. The hour is -1 rather than any real hour. It was noon once, and favourite_hour then told all seven people they wrote most at midday: 319 synthetic noons outweighed every genuine evening. """ give_legacy_counter(store, [(GROUP, "u1", "Ala", 2)]) seed_from_counter(store, DAY) rows = store.events.connection.execute( "SELECT local_hour, char_len, media_count, is_command FROM message_events" ).fetchall() assert len(rows) == 2 for row in rows: assert row["local_hour"] == UNKNOWN_HOUR assert (row["char_len"], row["media_count"], row["is_command"]) == (0, 0, 0) def test_seeding_twice_does_not_double_the_totals(store: Store) -> None: """The synthetic keys are deterministic, so a rerun is absorbed.""" give_legacy_counter(store, [(GROUP, "u1", "Ala", 3)]) seed_from_counter(store, DAY) seed_from_counter(store, DAY) assert day_totals(day_rows(store.events, GROUP, DAY)).messages == 3