test(persistence): backfill --dry-run end-to-end no-write proof
Snapshots workspace / membership / users.default_workspace_id row counts before backfill(dry_run=True), runs the orchestrator, asserts no counts changed. Pins the dry-run report's per-step semantics: Step 1 counts candidates without populating defaults, so Step 2's JOIN reports 0; Step 3 picks up all NULL business rows untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -257,6 +257,50 @@ async def test_ensure_legacy_workspace_refuses_when_no_users(tmp_path):
|
|||||||
await _close()
|
await _close()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_backfill_dry_run_does_not_write(tmp_path):
|
||||||
|
"""``backfill(..., dry_run=True)`` reports counts but writes nothing."""
|
||||||
|
sf = await _init_engine(tmp_path)
|
||||||
|
try:
|
||||||
|
await _seed_user(sf, email="admin@example.com", system_role="admin")
|
||||||
|
user_id = await _seed_user(sf, email="helen@example.com")
|
||||||
|
await _seed_business_row(sf, ThreadMetaRow, thread_id="t-owned", user_id=user_id)
|
||||||
|
await _seed_business_row(sf, ThreadMetaRow, thread_id="t-orphan", user_id=None)
|
||||||
|
await _seed_business_row(sf, RunRow, run_id="r-owned", thread_id="t-owned", user_id=user_id)
|
||||||
|
|
||||||
|
# Snapshot row counts BEFORE the dry run so we can confirm
|
||||||
|
# nothing changed AFTER.
|
||||||
|
async with sf() as session:
|
||||||
|
ws_before = len((await session.execute(select(WorkspaceRow))).scalars().all())
|
||||||
|
mem_before = len((await session.execute(select(WorkspaceMembershipRow))).scalars().all())
|
||||||
|
users_with_default_before = len((await session.execute(select(UserRow).where(UserRow.default_workspace_id.is_not(None)))).scalars().all())
|
||||||
|
|
||||||
|
report = await backfill(sf, dry_run=True)
|
||||||
|
assert report["dry_run"] is True
|
||||||
|
# Step 1 reports 2 candidates (admin + helen, both without default).
|
||||||
|
assert report["users_workspaces_created"] == 2
|
||||||
|
# Step 2 reports 0 because Step 1 didn't actually populate
|
||||||
|
# users.default_workspace_id under dry_run — the JOIN comes up empty.
|
||||||
|
assert report["threads_meta_from_users"] == 0
|
||||||
|
assert report["runs_from_users"] == 0
|
||||||
|
# Step 3 reports the 3 NULL business rows (t-owned, t-orphan, r-owned).
|
||||||
|
assert report["legacy_workspace_created"] is True
|
||||||
|
assert report["threads_meta_legacy"] == 2
|
||||||
|
assert report["runs_legacy"] == 1
|
||||||
|
|
||||||
|
# State did not change.
|
||||||
|
async with sf() as session:
|
||||||
|
ws_after = len((await session.execute(select(WorkspaceRow))).scalars().all())
|
||||||
|
mem_after = len((await session.execute(select(WorkspaceMembershipRow))).scalars().all())
|
||||||
|
users_with_default_after = len((await session.execute(select(UserRow).where(UserRow.default_workspace_id.is_not(None)))).scalars().all())
|
||||||
|
rows = (await session.execute(select(ThreadMetaRow.workspace_id))).scalars().all()
|
||||||
|
assert ws_after == ws_before
|
||||||
|
assert mem_after == mem_before
|
||||||
|
assert users_with_default_after == users_with_default_before
|
||||||
|
assert all(w is None for w in rows)
|
||||||
|
finally:
|
||||||
|
await _close()
|
||||||
|
|
||||||
|
|
||||||
async def test_full_backfill_orchestrator(tmp_path):
|
async def test_full_backfill_orchestrator(tmp_path):
|
||||||
"""End-to-end: backfill() runs all three steps and reports per-step counts."""
|
"""End-to-end: backfill() runs all three steps and reports per-step counts."""
|
||||||
sf = await _init_engine(tmp_path)
|
sf = await _init_engine(tmp_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user