feat: add repository rename lifecycle API
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 25s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a049a4-ee9f-78e1-9d66-2cb0f9bea3e3
This commit is contained in:
tegwick 2026-08-29 03:17:37 +02:00
parent 9e77a4a9a2
commit 82ea38b180
11 changed files with 2464 additions and 3 deletions

View file

@ -421,6 +421,9 @@ async def test_migration_upgrade_and_downgrade_are_additive(test_engine):
migration = importlib.import_module(
"migrations.versions.f3c4d5e6a7b8_repository_rename_identity"
)
rollback_migration = importlib.import_module(
"migrations.versions.a4d5e6f7b8c9_allow_guarded_slug_rollback"
)
schema = f"rename_migration_{uuid.uuid4().hex}"
repo_ids = [uuid.uuid4(), uuid.uuid4()]
sentinel_ids = {
@ -462,9 +465,12 @@ async def test_migration_upgrade_and_downgrade_are_additive(test_engine):
)
original_op = migration.op
original_rollback_op = rollback_migration.op
migration.op = Operations(MigrationContext.configure(sync_connection))
rollback_migration.op = migration.op
try:
migration.upgrade()
rollback_migration.upgrade()
tables = set(inspect(sync_connection).get_table_names(schema=schema))
assert {
"repository_forge_identities",
@ -568,6 +574,57 @@ async def test_migration_upgrade_and_downgrade_are_additive(test_engine):
),
{"id": operation_id},
)
# A forward cutover may make the old canonical an alias. T03
# permits restoring it only while its own operation is in the
# durable rollback-preflight phase.
sync_connection.execute(
text(
"UPDATE repository_slugs SET kind = 'alias', "
"source_operation_id = :operation_id "
"WHERE repo_id = :repo_id AND slug = 'flex-auth'"
),
{"operation_id": operation_id, "repo_id": repo_ids[0]},
)
sync_connection.execute(
text(
"INSERT INTO repository_slugs "
"(id, repo_id, slug, kind, protected, source_operation_id, created_at, updated_at) "
"VALUES (gen_random_uuid(), :repo_id, 'access-engine', 'canonical', true, "
":operation_id, now(), now())"
),
{"operation_id": operation_id, "repo_id": repo_ids[0]},
)
sync_connection.execute(
text(
"UPDATE repository_slugs SET kind = 'alias' "
"WHERE repo_id = :repo_id AND slug = 'access-engine'"
),
{"repo_id": repo_ids[0]},
)
with pytest.raises(DBAPIError):
with sync_connection.begin_nested():
sync_connection.execute(
text(
"UPDATE repository_slugs SET kind = 'canonical' "
"WHERE repo_id = :repo_id AND slug = 'flex-auth'"
),
{"repo_id": repo_ids[0]},
)
sync_connection.execute(
text(
"UPDATE repository_rename_operations SET "
"phase = 'rollback-preflight', phase_changed_at = now() "
"WHERE id = :id"
),
{"id": operation_id},
)
sync_connection.execute(
text(
"UPDATE repository_slugs SET kind = 'canonical' "
"WHERE repo_id = :repo_id AND slug = 'flex-auth'"
),
{"repo_id": repo_ids[0]},
)
with pytest.raises(DBAPIError):
with sync_connection.begin_nested():
sync_connection.execute(
@ -588,6 +645,7 @@ async def test_migration_upgrade_and_downgrade_are_additive(test_engine):
text(f"SELECT id, marker FROM {table_name}")
).one() == (sentinel_id, "keep")
rollback_migration.downgrade()
migration.downgrade()
tables = set(inspect(sync_connection).get_table_names(schema=schema))
assert "repository_forge_identities" not in tables
@ -599,6 +657,7 @@ async def test_migration_upgrade_and_downgrade_are_additive(test_engine):
).one() == (sentinel_id, "keep")
finally:
migration.op = original_op
rollback_migration.op = original_rollback_op
sync_connection.exec_driver_sql("SET LOCAL search_path TO public")
sync_connection.exec_driver_sql(f"DROP SCHEMA {quoted_schema} CASCADE")