summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-11 13:58:01 -0500
committerChristian Cleberg <[email protected]>2026-04-11 13:58:01 -0500
commit6149ca9c394df7d316f80db05ad0fc2aea6c550d (patch)
tree11f6cebc445e14b4a79adae648eb46d4be710071 /tests
parentb6d1348abf907b8b9c16ab87487eb9384dc4cc89 (diff)
downloadhutch-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.py21
-rw-r--r--tests/test_ingestion.py30
-rw-r--r--tests/test_migrations.py3
-rw-r--r--tests/test_polling_api.py22
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}]