krz/hutch-stats

Server-side utility for calculating contributions for sourcehut users.

clone: git clone https://gitbay.org/krz/hutch-stats.git

main: tests/test_migrations.py · raw

  1from pathlib import Path
  2
  3from alembic import command
  4from alembic.config import Config
  5from sqlalchemy import create_engine, inspect, text
  6
  7from srht_contrib.config import Settings
  8
  9
 10def test_alembic_upgrade_creates_schema(tmp_path) -> None:
 11    database_path = tmp_path / "migrated.db"
 12    database_url = f"sqlite:///{database_path}"
 13    config = Config(str(Path(__file__).resolve().parent.parent / "alembic.ini"))
 14    config.set_main_option("script_location", str(Path(__file__).resolve().parent.parent / "alembic"))
 15    config.set_main_option("sqlalchemy.url", database_url)
 16
 17    command.upgrade(config, "head")
 18
 19    inspector = inspect(create_engine(database_url))
 20    assert sorted(inspector.get_table_names()) == [
 21        "actor_aliases",
 22        "alembic_version",
 23        "contribution_events",
 24        "discovered_repositories",
 25        "service_backfill_states",
 26        "sync_states",
 27        "tracked_actors",
 28        "tracked_repositories",
 29    ]
 30
 31
 32def test_alembic_upgrade_adopts_legacy_schema(tmp_path) -> None:
 33    database_path = tmp_path / "legacy.db"
 34    database_url = f"sqlite:///{database_path}"
 35    engine = create_engine(database_url)
 36    with engine.begin() as connection:
 37        connection.execute(
 38            text(
 39                """
 40                CREATE TABLE contribution_events (
 41                    id INTEGER NOT NULL PRIMARY KEY,
 42                    service VARCHAR(32) NOT NULL,
 43                    event_type VARCHAR(64) NOT NULL,
 44                    actor VARCHAR(255) NOT NULL,
 45                    repo_name VARCHAR(255),
 46                    resource_id VARCHAR(255) NOT NULL,
 47                    external_uid VARCHAR(255) NOT NULL,
 48                    occurred_at DATETIME NOT NULL,
 49                    weight FLOAT NOT NULL,
 50                    raw_payload_json JSON,
 51                    CONSTRAINT uq_contribution_event_service_uid UNIQUE (service, external_uid)
 52                )
 53                """
 54            )
 55        )
 56        connection.execute(text("CREATE INDEX ix_contribution_events_actor_occurred_at ON contribution_events (actor, occurred_at)"))
 57        connection.execute(text("CREATE INDEX ix_contribution_events_service_occurred_at ON contribution_events (service, occurred_at)"))
 58        connection.execute(
 59            text(
 60                """
 61                CREATE TABLE sync_states (
 62                    id INTEGER NOT NULL PRIMARY KEY,
 63                    service VARCHAR(32) NOT NULL,
 64                    actor VARCHAR(255) NOT NULL,
 65                    cursor_value TEXT,
 66                    updated_at DATETIME NOT NULL,
 67                    CONSTRAINT uq_sync_state_service_actor UNIQUE (service, actor)
 68                )
 69                """
 70            )
 71        )
 72        connection.execute(
 73            text(
 74                """
 75                CREATE TABLE tracked_repositories (
 76                    id INTEGER NOT NULL PRIMARY KEY,
 77                    service VARCHAR(32) NOT NULL,
 78                    repo_name VARCHAR(255) NOT NULL,
 79                    actor VARCHAR(255),
 80                    CONSTRAINT uq_tracked_repository_service_name UNIQUE (service, repo_name)
 81                )
 82                """
 83            )
 84        )
 85        connection.execute(
 86            text(
 87                """
 88                INSERT INTO tracked_repositories (id, service, repo_name, actor)
 89                VALUES (1, 'git', 'Hutch', NULL)
 90                """
 91            )
 92        )
 93        connection.execute(
 94            text(
 95                """
 96                CREATE TABLE actor_aliases (
 97                    id INTEGER NOT NULL PRIMARY KEY,
 98                    canonical_actor VARCHAR(255) NOT NULL,
 99                    alias VARCHAR(255) NOT NULL,
100                    CONSTRAINT uq_actor_alias_alias UNIQUE (alias)
101                )
102                """
103            )
104        )
105
106    config = Config(str(Path(__file__).resolve().parent.parent / "alembic.ini"))
107    config.set_main_option("script_location", str(Path(__file__).resolve().parent.parent / "alembic"))
108    config.set_main_option("sqlalchemy.url", database_url)
109
110    command.upgrade(config, "head")
111
112    inspector = inspect(create_engine(database_url))
113    columns = {column["name"]: column for column in inspector.get_columns("tracked_repositories")}
114    tracked_actor_columns = {column["name"] for column in inspector.get_columns("tracked_actors")}
115    unique_constraints = {constraint["name"] for constraint in inspector.get_unique_constraints("tracked_repositories")}
116    with create_engine(database_url).connect() as connection:
117        actor = connection.execute(text("SELECT actor FROM tracked_repositories WHERE id = 1")).scalar_one()
118
119    assert columns["actor"]["nullable"] is False
120    assert "uq_tracked_repository_service_actor_name" in unique_constraints
121    assert actor == Settings().default_actor
122    assert "discovered_repositories" in inspector.get_table_names()
123    assert "tracked_actors" in inspector.get_table_names()
124    assert "service_backfill_states" in inspector.get_table_names()
125    assert {
126        "discovery_state",
127        "queued_for_discovery_at",
128        "priority_boosted_at",
129        "next_poll_after",
130        "last_claimed_at",
131        "poll_attempts",
132    } <= tracked_actor_columns
133
134
135def test_alembic_prefers_database_url_from_environment(tmp_path, monkeypatch) -> None:
136    database_path = tmp_path / "env-selected.db"
137    database_url = f"sqlite:///{database_path}"
138    config = Config(str(Path(__file__).resolve().parent.parent / "alembic.ini"))
139    config.set_main_option("script_location", str(Path(__file__).resolve().parent.parent / "alembic"))
140    monkeypatch.setenv("DATABASE_URL", database_url)
141
142    command.upgrade(config, "head")
143
144    inspector = inspect(create_engine(database_url))
145    assert "actor_aliases" in inspector.get_table_names()
146    assert "discovered_repositories" in inspector.get_table_names()
147    assert "tracked_actors" in inspector.get_table_names()
148    assert "service_backfill_states" in inspector.get_table_names()