Files
svcforge/tests/integration/test_instances.py
T
Nguyen Minh Phuc 50c2fe2a1e
ci / lint (push) Successful in 1m19s
ci / unit (push) Failing after 1m2s
ci / integration (push) Has been skipped
ci / types (push) Successful in 1m37s
ci / security (push) Failing after 38s
ci / dockerfile (push) Successful in 14s
ci / image (api) (push) Has been skipped
ci / image (reconciler) (push) Has been skipped
ci / image (worker) (push) Has been skipped
ci / bump (push) Has been skipped
svcforge: reference implementation
Complete working build of the system learn-python/ teaches.
164 tests, mypy --strict clean, domain coverage 99%.
2026-07-17 10:44:54 +00:00

105 lines
3.9 KiB
Python

"""InstanceRepo against real SQL."""
from __future__ import annotations
import pytest
from svcforge_core.domain.models import TaskKind
from svcforge_core.domain.states import InstanceState
from svcforge_core.repo.db import DictPool
from svcforge_core.repo.instances import InstanceRepo
from svcforge_core.repo.tasks import TaskRepo
from tests.integration.helpers import build_instance
async def test_create_then_get_round_trips(pool: DictPool) -> None:
repo = InstanceRepo(pool)
inst = build_instance()
async with pool.connection() as conn:
created = await repo.create(conn, inst)
assert created.id == inst.id
assert created.release_name == inst.release_name
got = await repo.get(inst.id, team="platform")
assert got is not None
assert got.service_type == "elasticsearch"
assert got.state is InstanceState.REQUESTED
# timestamptz round-trips as aware, or every later comparison raises TypeError.
assert got.created_at.tzinfo is not None
async def test_get_by_other_team_is_none_not_403(pool: DictPool) -> None:
"""A wrong-team id is indistinguishable from a missing one."""
repo = InstanceRepo(pool)
inst = build_instance(team="platform")
async with pool.connection() as conn:
await repo.create(conn, inst)
assert await repo.get(inst.id, team="quant") is None
async def test_list_is_filtered_by_team(pool: DictPool) -> None:
repo = InstanceRepo(pool)
async with pool.connection() as conn:
await repo.create(conn, build_instance(team="platform"))
await repo.create(conn, build_instance(team="quant"))
mine = await repo.list(team="platform")
assert len(mine) == 1
assert all(i.team == "platform" for i in mine)
async def test_update_state_cas_rejects_stale_expectation(pool: DictPool) -> None:
repo = InstanceRepo(pool)
inst = build_instance()
async with pool.connection() as conn:
await repo.create(conn, inst)
ok = await repo.update_state(inst.id, InstanceState.REQUESTED, InstanceState.PROVISIONING)
assert ok is True
# The row already moved: the second caller must lose, and must not raise.
lost = await repo.update_state(inst.id, InstanceState.REQUESTED, InstanceState.PROVISIONING)
assert lost is False
async def test_update_state_sets_endpoint(pool: DictPool) -> None:
repo = InstanceRepo(pool)
inst = build_instance()
async with pool.connection() as conn:
await repo.create(conn, inst)
await repo.update_state(inst.id, InstanceState.REQUESTED, InstanceState.PROVISIONING)
ok = await repo.update_state(
inst.id, InstanceState.PROVISIONING, InstanceState.READY, endpoint="http://es:9200"
)
assert ok is True
got = await repo.get(inst.id, team="platform")
assert got is not None
assert got.endpoint == "http://es:9200"
assert got.state is InstanceState.READY
@pytest.mark.parametrize("explode", [True])
async def test_instance_and_task_roll_back_together(pool: DictPool, explode: bool) -> None:
"""The reason the queue is in Postgres, as an executable claim.
If the transaction aborts, BOTH the instance and its provision task must vanish.
An instance with no task never gets built; a task with no instance is an orphan.
"""
instances, tasks = InstanceRepo(pool), TaskRepo(pool)
inst = build_instance()
with pytest.raises(RuntimeError):
async with pool.connection() as conn, conn.transaction():
await instances.create(conn, inst)
await tasks.enqueue(conn, inst.id, TaskKind.PROVISION)
if explode:
raise RuntimeError("boom, mid-transaction")
assert await instances.get(inst.id, team="platform") is None
async with pool.connection() as conn, conn.cursor() as cur:
await cur.execute("select count(*) as n from tasks")
row = await cur.fetchone()
assert row is not None
assert row["n"] == 0