Files
turnstone/tests/test_services_storage.py
T
Patrick Buckley ab1a71c86c feat: add PostgreSQL CI integration tests (#156)
* feat: add PostgreSQL CI integration tests

Add --storage-backend pytest option and shared storage_backend fixture
in conftest.py that creates SQLiteBackend or PostgreSQLBackend based
on the flag. Migrate 13 storage test files to use shared fixture
instead of local SQLiteBackend fixtures.

Add test-postgres CI job with PostgreSQL 17 service container that
runs the full test suite against real PostgreSQL.

* fix: use TRUNCATE CASCADE for PG cleanup, wrap in try/finally

TRUNCATE is faster than per-table DELETE and resets autoincrement
sequences. try/except ensures reset_storage() always runs even if
cleanup fails due to a corrupted connection from a failing test.

* fix: document _engine coupling in PG cleanup comment
2026-03-23 11:11:40 -07:00

78 lines
3.2 KiB
Python

"""Tests for the services registry storage methods."""
from __future__ import annotations
class TestServiceRegistry:
def test_register_and_list(self, storage):
storage.register_service("channel", "ch-1", "http://localhost:8091")
services = storage.list_services("channel", max_age_seconds=120)
assert len(services) == 1
assert services[0]["service_type"] == "channel"
assert services[0]["service_id"] == "ch-1"
assert services[0]["url"] == "http://localhost:8091"
def test_register_upsert(self, storage):
storage.register_service("channel", "ch-1", "http://old:8091")
storage.register_service("channel", "ch-1", "http://new:8091")
services = storage.list_services("channel", max_age_seconds=120)
assert len(services) == 1
assert services[0]["url"] == "http://new:8091"
def test_heartbeat(self, storage):
storage.register_service("channel", "ch-1", "http://localhost:8091")
result = storage.heartbeat_service("channel", "ch-1")
assert result is True
def test_heartbeat_nonexistent(self, storage):
result = storage.heartbeat_service("channel", "nonexistent")
assert result is False
def test_list_filters_stale(self, storage):
storage.register_service("channel", "ch-1", "http://localhost:8091")
# Manually set heartbeat to the past so it's stale
from datetime import UTC, datetime, timedelta
import sqlalchemy as sa
from turnstone.core.storage._schema import services
old_time = (datetime.now(UTC) - timedelta(seconds=300)).strftime("%Y-%m-%dT%H:%M:%S")
with storage._engine.connect() as conn:
conn.execute(sa.update(services).values(last_heartbeat=old_time))
conn.commit()
# Should be excluded with 120s max age
result = storage.list_services("channel", max_age_seconds=120)
assert len(result) == 0
def test_list_empty(self, storage):
services = storage.list_services("channel", max_age_seconds=120)
assert services == []
def test_list_filters_by_type(self, storage):
storage.register_service("channel", "ch-1", "http://localhost:8091")
storage.register_service("bridge", "br-1", "http://localhost:8080")
channels = storage.list_services("channel", max_age_seconds=120)
bridges = storage.list_services("bridge", max_age_seconds=120)
assert len(channels) == 1
assert len(bridges) == 1
def test_deregister(self, storage):
storage.register_service("channel", "ch-1", "http://localhost:8091")
result = storage.deregister_service("channel", "ch-1")
assert result is True
services = storage.list_services("channel", max_age_seconds=120)
assert services == []
def test_deregister_nonexistent(self, storage):
result = storage.deregister_service("channel", "nonexistent")
assert result is False
def test_metadata(self, storage):
storage.register_service(
"channel", "ch-1", "http://localhost:8091", metadata='{"adapter": "discord"}'
)
services = storage.list_services("channel", max_age_seconds=120)
assert services[0]["metadata"] == '{"adapter": "discord"}'