This commit is contained in:
2026-10-11 16:00:53 +07:00
parent 4f4857aa53
commit 818eeaa1ac
7 changed files with 100 additions and 12 deletions
+66
View File
@@ -0,0 +1,66 @@
"""Migration checks on a private schema inside the disposable flow PostgreSQL."""
import json
import uuid
def check_migration(sql, migrations):
schema = 'team_uuid_migration_test'
sql(f'CREATE SCHEMA {schema};')
def query(statement):
return sql(f'SET search_path={schema};\n{statement}')
try:
migration = migrations / '20261013120000_team_uuid.sql'
for path in sorted(migrations.glob('*.sql')):
if path.name >= migration.name:
break
query('BEGIN;\n' + path.read_text().split('-- +goose Down')[0] + '\nCOMMIT;')
query("""
INSERT INTO users(id, username, email, password_hash) VALUES(1, 'Test', 'test@example.test', 'unused');
INSERT INTO scenarios(id, name, author_id) VALUES(1, 'Test', 1);
INSERT INTO games(id, name, start_at, scenario_id) VALUES(1, 'Test', NOW(), 1);
INSERT INTO teams(id, name, game_id, creator_id, password) VALUES(1, 'First', 1, 1, 'qr'), (2, 'Second', 1, 1, 'qr2');
INSERT INTO actions(team_id, code) VALUES(1, 'p'), (NULL, 'unbound');
INSERT INTO applications(team_id, name, image) VALUES(1, 'Clue', 'original.pdf'), (NULL, 'Unbound', 'unbound.pdf');
INSERT INTO team_answers(team_id, question_code, answer, score) VALUES(1, 'q', 'Answer', 5);
INSERT INTO game_reviews(game_id, team_id, rating, text) VALUES(1, 1, 5, 'Review');
INSERT INTO game_map_items(game_id, kind, item_id) VALUES(1, 'team', 1), (1, 'team', 2), (1, 'screen', 3), (1, 'organizer_desk', 1);
INSERT INTO game_exports(id, game_id, snapshot, source_server, object_key) VALUES('export', 1, '{}', 'https://example.test', 'old.zip');
INSERT INTO game_returns(package_id, sha256, export_id, source_game_id, prepared_at, mappings, local_actors) VALUES('return', 'hash', 'export', 1, 'old', '{}', '{}');
INSERT INTO game_imports(id, package_id, source_server, source_game_id, sha256, manifest, original_data, object_keys) VALUES('import', 'package', 'https://example.test', 1, 'hash', '{}', '{}', ARRAY['original.pdf']);
""")
query('BEGIN;\n' + migration.read_text().split('-- +goose Down')[0] + '\nCOMMIT;')
result = json.loads(query("""
SELECT json_build_object(
'teams', (SELECT json_agg(json_build_object('id', id, 'name', name, 'password', password) ORDER BY name) FROM teams),
'linked', (SELECT count(*) FROM teams t JOIN actions a ON a.team_id=t.id JOIN applications p ON p.team_id=t.id JOIN team_answers n ON n.team_id=t.id JOIN game_reviews r ON r.team_id=t.id WHERE t.name='First' AND a.code='p' AND p.image='original.pdf' AND n.answer='Answer' AND n.score=5 AND r.text='Review'),
'unbound', (SELECT count(*) FROM actions WHERE team_id IS NULL) + (SELECT count(*) FROM applications WHERE team_id IS NULL),
'map', (SELECT json_object_agg(kind || '/' || item_id, item_id) FROM game_map_items),
'registries', (SELECT count(*) FROM game_exports) + (SELECT count(*) FROM game_imports) + (SELECT count(*) FROM game_returns),
'sequence', to_regclass('teams_id_seq')
);
""").splitlines()[-1])
assert [team['name'] for team in result['teams']] == ['First', 'Second']
assert [team['password'] for team in result['teams']] == ['qr', 'qr2']
ids = [team['id'] for team in result['teams']]
assert len(set(ids)) == 2 and all(uuid.UUID(id).version == 7 for id in ids)
assert result['linked'] == 1 and result['unbound'] == 2
assert result['registries'] == 0 and result['sequence'] is None
assert set(result['map']) == {*(f'team/{id}' for id in ids), 'screen/3', 'organizer_desk/1'}
for statement in (
f"INSERT INTO team_answers(team_id, question_code) VALUES('{ids[0]}','q');",
f"INSERT INTO game_reviews(game_id, team_id, rating, text) VALUES(1,'{ids[0]}',5,'Duplicate');",
"INSERT INTO actions(team_id) VALUES('019a1234-5678-7abc-8def-000000000999');",
):
try:
query(statement)
except RuntimeError:
pass
else:
raise AssertionError('Migration lost a uniqueness or foreign key constraint')
query(f"DELETE FROM teams WHERE id='{ids[0]}';")
assert query('SELECT (SELECT count(*) FROM team_answers) + (SELECT count(*) FROM game_reviews);').splitlines()[-1] == '0'
print('PASS: populated UUID v7 migration preserves data, nullable links, map, unique/FK/cascade constraints and clears legacy transfer registries', flush=True)
finally:
sql(f'DROP SCHEMA {schema} CASCADE;')