diff options
| author | Christian Cleberg <[email protected]> | 2026-04-11 13:58:01 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-11 13:58:01 -0500 |
| commit | 6149ca9c394df7d316f80db05ad0fc2aea6c550d (patch) | |
| tree | 11f6cebc445e14b4a79adae648eb46d4be710071 /tests | |
| parent | b6d1348abf907b8b9c16ab87487eb9384dc4cc89 (diff) | |
| download | hutch-stats-6149ca9c394df7d316f80db05ad0fc2aea6c550d.tar.gz hutch-stats-6149ca9c394df7d316f80db05ad0fc2aea6c550d.tar.bz2 hutch-stats-6149ca9c394df7d316f80db05ad0fc2aea6c550d.zip | |
feat: add lazy actor indexing for contribution lookups
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_contributions_api.py | 21 | ||||
| -rw-r--r-- | tests/test_ingestion.py | 30 | ||||
| -rw-r--r-- | tests/test_migrations.py | 3 | ||||
| -rw-r--r-- | tests/test_polling_api.py | 22 |
4 files changed, 73 insertions, 3 deletions
diff --git a/tests/test_contributions_api.py b/tests/test_contributions_api.py index e221602..22d0af3 100644 --- a/tests/test_contributions_api.py +++ b/tests/test_contributions_api.py @@ -1,9 +1,10 @@ from datetime import UTC, datetime from fastapi.testclient import TestClient +from sqlalchemy import select from srht_contrib.main import create_app -from srht_contrib.models import ContributionEvent +from srht_contrib.models import ContributionEvent, TrackedActor def test_read_only_contribution_routes_are_public_and_write_routes_require_api_key(settings, db_engine, session_factory) -> None: @@ -45,6 +46,8 @@ def test_contributions_api_returns_zero_filled_range(client: TestClient, db_sess response = client.get("/api/contributions/~ccleberg?from=2026-03-28&to=2026-03-30") assert response.status_code == 200 + assert response.json()["is_indexed"] is True + assert response.json()["indexing_state"] == "indexed" assert response.json()["days"] == [ {"date": "2026-03-28", "count": 0, "score": 0.0}, {"date": "2026-03-29", "count": 0, "score": 0.0}, @@ -52,6 +55,20 @@ def test_contributions_api_returns_zero_filled_range(client: TestClient, db_sess ] +def test_public_read_registers_actor_for_lazy_indexing(client: TestClient, db_session) -> None: + response = client.get("/api/contributions/~ccleberg?from=2026-03-28&to=2026-03-30") + + tracked_actor = db_session.scalar(select(TrackedActor).where(TrackedActor.actor == "~ccleberg")) + + assert response.status_code == 200 + assert response.json()["is_indexed"] is False + assert response.json()["indexing_state"] == "pending" + assert response.json()["last_polled_at"] is None + assert tracked_actor is not None + assert tracked_actor.is_active is True + assert tracked_actor.last_requested_at is not None + + def test_contribution_stats_api(client: TestClient, db_session) -> None: db_session.add_all( [ @@ -88,6 +105,8 @@ def test_contribution_stats_api(client: TestClient, db_session) -> None: assert response.json()["total_score"] == 1.5 assert response.json()["longest_streak"] == 2 assert response.json()["current_streak"] == 2 + assert response.json()["is_indexed"] is True + assert response.json()["indexing_state"] == "indexed" def test_invalid_date_input_returns_400(client: TestClient) -> None: diff --git a/tests/test_ingestion.py b/tests/test_ingestion.py index 2322b4a..788ec81 100644 --- a/tests/test_ingestion.py +++ b/tests/test_ingestion.py @@ -4,7 +4,7 @@ from sqlalchemy import select from srht_contrib.config import Settings from srht_contrib.jobs.poller import PollerService -from srht_contrib.models import SyncState, TrackedRepository +from srht_contrib.models import SyncState, TrackedActor, TrackedRepository from srht_contrib.schemas import NormalizedEvent from srht_contrib.services.git import GitIngestionService, GitPollResult from srht_contrib.services.todo import TodoIngestionService, TodoPollResult @@ -335,3 +335,31 @@ def test_sync_overlap_reuses_cursor_window_and_suppresses_duplicates(db_session) assert state is not None assert len(todo_service.calls) == 2 assert todo_service.calls[1].isoformat() == "2026-03-30T00:00:00+00:00" + + +def test_scheduled_poll_polls_known_actors_and_seeds_default_actor(db_session) -> None: + event = NormalizedEvent( + service="todo", + event_type="ticket_created", + actor="~known", + repo_name="todo", + resource_id="123", + external_uid="todo:event:known:created:123", + occurred_at=datetime(2026, 3, 30, 10, 0, tzinfo=UTC), + weight=1.0, + raw_payload_json=None, + ) + todo_service = RecordingTodoService(events_by_call=[[], [event]]) + poller = PollerService(todo_service=todo_service, git_service=EmptyGitService()) + + db_session.add(TrackedActor(actor="~known", is_active=True)) + db_session.commit() + + results = poller.poll_tracked_actors(db_session, default_actor="~default") + + tracked_actors = db_session.scalars(select(TrackedActor).order_by(TrackedActor.actor)).all() + + assert results == {"~default": 0, "~known": 1} + assert [actor.actor for actor in tracked_actors] == ["~default", "~known"] + assert all(actor.last_poll_status == "indexed" for actor in tracked_actors) + assert all(actor.last_polled_at is not None for actor in tracked_actors) diff --git a/tests/test_migrations.py b/tests/test_migrations.py index d572af2..505ddb3 100644 --- a/tests/test_migrations.py +++ b/tests/test_migrations.py @@ -22,6 +22,7 @@ def test_alembic_upgrade_creates_schema(tmp_path) -> None: "alembic_version", "contribution_events", "sync_states", + "tracked_actors", "tracked_repositories", ] @@ -115,6 +116,7 @@ def test_alembic_upgrade_adopts_legacy_schema(tmp_path) -> None: assert columns["actor"]["nullable"] is False assert "uq_tracked_repository_service_actor_name" in unique_constraints assert actor == Settings().default_actor + assert "tracked_actors" in inspector.get_table_names() def test_alembic_prefers_database_url_from_environment(tmp_path, monkeypatch) -> None: @@ -128,3 +130,4 @@ def test_alembic_prefers_database_url_from_environment(tmp_path, monkeypatch) -> inspector = inspect(create_engine(database_url)) assert "actor_aliases" in inspector.get_table_names() + assert "tracked_actors" in inspector.get_table_names() diff --git a/tests/test_polling_api.py b/tests/test_polling_api.py index b07b877..75270e0 100644 --- a/tests/test_polling_api.py +++ b/tests/test_polling_api.py @@ -1,10 +1,11 @@ from datetime import UTC, datetime from fastapi.testclient import TestClient +from sqlalchemy import select from srht_contrib.config import Settings from srht_contrib.main import create_app -from srht_contrib.models import ContributionEvent +from srht_contrib.models import ContributionEvent, TrackedActor from srht_contrib.services.srht_client import SourceHutClientError @@ -19,6 +20,16 @@ class InsertingPoller: self.todo_service = service self.git_service = service + def track_actor_request(self, db, actor: str, *, update_last_requested: bool = True): + tracked_actor = db.scalar(select(TrackedActor).where(TrackedActor.actor == actor)) + if tracked_actor is None: + tracked_actor = TrackedActor(actor=actor, is_active=True) + db.add(tracked_actor) + if update_last_requested: + tracked_actor.last_requested_at = datetime(2026, 3, 30, 9, 0, tzinfo=UTC) + db.flush() + return tracked_actor + def poll_all(self, db, actor: str) -> int: db.add( ContributionEvent( @@ -43,6 +54,14 @@ class FailingPoller: self.todo_service = service self.git_service = service + def track_actor_request(self, db, actor: str, *, update_last_requested: bool = True): + tracked_actor = db.scalar(select(TrackedActor).where(TrackedActor.actor == actor)) + if tracked_actor is None: + tracked_actor = TrackedActor(actor=actor, is_active=True) + db.add(tracked_actor) + db.flush() + return tracked_actor + def poll_all(self, db, actor: str) -> int: raise SourceHutClientError("boom") @@ -58,6 +77,7 @@ def test_manual_poll_uses_same_database_session(settings: Settings, db_engine, s assert poll_response.status_code == 200 assert poll_response.json()["inserted_events"] == 1 assert calendar_response.status_code == 200 + assert calendar_response.json()["is_indexed"] is True assert calendar_response.json()["days"] == [{"date": "2026-03-30", "count": 1, "score": 1.0}] |
