Backend tests run on SQLite by default (78 passed, 1 skipped); no Postgres needed
- conftest: default SHONAR_TEST_DATABASE_URL is a temp SQLite file, matching the bundled-lite engine; set the env var to a PG URL to exercise that path - models.py: add sqlite_where to the two partial unique indexes — without it SQLite built a FULL unique index on recording_id (WHERE not carried over), wrongly blocking a second export asset per recording - test_m9: pass UUID objects (not str) to direct ORM inserts/gets; SQLite's GUID bind processor rejects strings (asyncpg tolerated them) Verified: pytest -q = 78 passed, 1 skipped; ruff check clean
This commit is contained in:
parent
adc40201a1
commit
a94609c07b
3 changed files with 35 additions and 9 deletions
|
|
@ -1,7 +1,10 @@
|
|||
"""Pytest fixtures.
|
||||
|
||||
Tests run against a real PostgreSQL (deploy/docker-compose.dev.yml) using a
|
||||
dedicated ``shonar_test`` database, plus a temporary local storage root.
|
||||
Default: SQLite (aiosqlite) in a temp dir — matches the desktop
|
||||
bundled-lite engine and needs no services running. Set
|
||||
``SHONAR_TEST_DATABASE_URL`` to a PostgreSQL URL (e.g.
|
||||
``postgresql+asyncpg://shonar:shonar@localhost:5432/shonar_test``) to
|
||||
exercise the Postgres path, including the tsvector FTS columns.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -15,9 +18,11 @@ import pytest
|
|||
import pytest_asyncio
|
||||
|
||||
# Configure env BEFORE importing the app so Settings picks it up.
|
||||
_db_dir = tempfile.mkdtemp(prefix="shonar-test-db-")
|
||||
TEST_DB = os.environ.get(
|
||||
"SHONAR_TEST_DATABASE_URL",
|
||||
"postgresql+asyncpg://shonar:shonar@localhost:5432/shonar_test",
|
||||
# Bundled-lite default: throwaway SQLite file; no services required.
|
||||
f"sqlite+aiosqlite:///{os.path.join(_db_dir, 'test.db')}",
|
||||
)
|
||||
os.environ["SHONAR_DATABASE_URL"] = TEST_DB
|
||||
os.environ["SHONAR_SECRET_KEY"] = "test-secret-key-0123456789abcdef0123456789abcdef"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ via SHONAR_TEST_DATABASE_URL) — both paths share the endpoint contract.
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import uuid
|
||||
import zipfile
|
||||
|
||||
from tests.test_recordings import auth, user_tokens, wav_bytes
|
||||
|
|
@ -44,7 +45,15 @@ async def add_transcript(rec_id: str, text: str, segments=None):
|
|||
from shonar.db.session import session_factory
|
||||
|
||||
async with session_factory()() as s:
|
||||
s.add(Transcript(recording_id=rec_id, text=text, segments=segments, provider="test"))
|
||||
# UUID object (not str): SQLite's GUID bind processor requires it.
|
||||
s.add(
|
||||
Transcript(
|
||||
recording_id=uuid.UUID(rec_id) if isinstance(rec_id, str) else rec_id,
|
||||
text=text,
|
||||
segments=segments,
|
||||
provider="test",
|
||||
)
|
||||
)
|
||||
await s.commit()
|
||||
|
||||
|
||||
|
|
@ -53,7 +62,13 @@ async def add_summary(rec_id: str, content: dict):
|
|||
from shonar.db.session import session_factory
|
||||
|
||||
async with session_factory()() as s:
|
||||
s.add(Summary(recording_id=rec_id, content=content, provider="test"))
|
||||
s.add(
|
||||
Summary(
|
||||
recording_id=uuid.UUID(rec_id) if isinstance(rec_id, str) else rec_id,
|
||||
content=content,
|
||||
provider="test",
|
||||
)
|
||||
)
|
||||
await s.commit()
|
||||
|
||||
|
||||
|
|
@ -241,7 +256,7 @@ async def test_retention_sweep_purges_expired(client):
|
|||
r = await client.delete(f"/api/v1/recordings/{rid}", headers=h)
|
||||
assert r.status_code == 204
|
||||
async with session_factory()() as s:
|
||||
rec = await s.get(Recording, rid)
|
||||
rec = await s.get(Recording, uuid.UUID(rid))
|
||||
rec.deleted_at = utcnow() - timedelta(days=31)
|
||||
# remember the storage key before the row vanishes
|
||||
from shonar.db.models import Asset
|
||||
|
|
@ -260,7 +275,7 @@ async def test_retention_sweep_purges_expired(client):
|
|||
assert purged["files"] >= 1
|
||||
|
||||
async with session_factory()() as s:
|
||||
assert await s.get(Recording, rid) is None
|
||||
assert await s.get(Recording, uuid.UUID(rid)) is None
|
||||
from shonar.storage import get_storage
|
||||
|
||||
assert not await get_storage().exists(storage_key) # file gone too
|
||||
|
|
@ -271,7 +286,7 @@ async def test_retention_sweep_purges_expired(client):
|
|||
purged = await retention.sweep_deleted()
|
||||
assert purged["recordings"] == 0
|
||||
async with session_factory()() as s:
|
||||
assert await s.get(Recording, rid2) is not None
|
||||
assert await s.get(Recording, uuid.UUID(rid2)) is not None
|
||||
|
||||
|
||||
async def test_retention_sweep_account(client):
|
||||
|
|
@ -292,7 +307,7 @@ async def test_retention_sweep_account(client):
|
|||
purged = await retention.sweep_deleted()
|
||||
assert purged["users"] == 1
|
||||
async with session_factory()() as s:
|
||||
assert await s.get(Recording, rid) is None # cascade
|
||||
assert await s.get(Recording, uuid.UUID(rid)) is None # cascade
|
||||
assert await s.scalar(select_user("m9r2@example.com")) is None
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue