"""Migration 030 — the global-scope grant (§22 three-tier S3). Proves: `memberships.scope_type` now admits 'global' alongside 'project' and 'collection' (§B.2's four-layer resolver), existing rows survive the rebuild, and the UNIQUE(scope_type, scope_id, user_id) shape is preserved. Template: test_migration_029_collections.py. """ from __future__ import annotations import sqlite3 import tempfile from pathlib import Path import pytest from app import db class _Cfg: def __init__(self, path): self.database_path = path def _fresh_db(): d = tempfile.mkdtemp() path = Path(d) / "t.db" db.run_migrations(_Cfg(str(path))) return db.connect(str(path)) def _add_user(conn, uid, login): conn.execute( "INSERT INTO users (id, gitea_id, gitea_login, display_name, role) " "VALUES (?, ?, ?, ?, 'contributor')", (uid, uid, login, login.capitalize()), ) def test_global_scope_type_is_accepted(): conn = _fresh_db() _add_user(conn, 1, "cleo") # global grant — the new tier — is accepted. conn.execute( "INSERT INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('global', '*', 1, 'contributor')" ) row = conn.execute( "SELECT scope_type, scope_id, role FROM memberships WHERE user_id = 1" ).fetchone() assert row["scope_type"] == "global" assert row["scope_id"] == "*" assert row["role"] == "contributor" def test_project_and_collection_scopes_still_accepted(): conn = _fresh_db() _add_user(conn, 1, "ben") conn.execute( "INSERT INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('project', 'default', 1, 'owner')" ) conn.execute( "INSERT INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('collection', 'default', 1, 'contributor')" ) n = conn.execute("SELECT COUNT(*) AS n FROM memberships WHERE user_id = 1").fetchone()["n"] assert n == 2 def test_unknown_scope_type_still_rejected(): conn = _fresh_db() _add_user(conn, 1, "x") with pytest.raises(sqlite3.IntegrityError): conn.execute( "INSERT INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('deployment', '*', 1, 'owner')" ) def test_one_global_grant_per_user(): conn = _fresh_db() _add_user(conn, 1, "cleo") conn.execute( "INSERT INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('global', '*', 1, 'contributor')" ) with pytest.raises(sqlite3.IntegrityError): conn.execute( "INSERT INTO memberships (scope_type, scope_id, user_id, role) " "VALUES ('global', '*', 1, 'owner')" )