| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576 |
- """wallet_transactions.user_id becomes nullable, ON DELETE SET NULL, on existing SQLite databases.
- It was NOT NULL with ON DELETE CASCADE: deleting a user deleted their ledger
- entries, which freed what they had spent of a shared cost centre's budget.
- The entries stay now, without a user. On SQLite the NOT NULL has to go from
- tables made before, or the delete fails on it. (PostgreSQL is checked by
- scripts/check_postgres_upgrade.py.)
- """
- import pytest
- from sqlalchemy import text
- from sqlalchemy.ext.asyncio import create_async_engine
- from backend.app.core import database
- # As create_all made the table from the old model ...
- CREATED_BY_THE_MODEL = """
- CREATE TABLE wallet_transactions (
- \tid INTEGER NOT NULL,
- \tuser_id INTEGER NOT NULL,
- \tcost_center_id INTEGER,
- \ttransaction_type VARCHAR(40) NOT NULL,
- \tamount NUMERIC(14, 2) NOT NULL,
- \tcreated_by_user_id INTEGER,
- \tPRIMARY KEY (id),
- \tFOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE,
- \tFOREIGN KEY(cost_center_id) REFERENCES cost_centers (id) ON DELETE SET NULL,
- \tFOREIGN KEY(created_by_user_id) REFERENCES users (id) ON DELETE SET NULL
- )
- """
- # ... and as the finance migration's own CREATE TABLE made it.
- CREATED_BY_THE_MIGRATION = """
- CREATE TABLE wallet_transactions (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
- cost_center_id INTEGER REFERENCES cost_centers(id) ON DELETE SET NULL,
- transaction_type VARCHAR(40) NOT NULL,
- amount NUMERIC(14,2) NOT NULL,
- created_by_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL
- )
- """
- @pytest.fixture(autouse=True)
- def sqlite_dialect(monkeypatch):
- monkeypatch.setattr(database, "is_sqlite", lambda: True)
- @pytest.mark.asyncio
- @pytest.mark.parametrize("legacy", [CREATED_BY_THE_MODEL, CREATED_BY_THE_MIGRATION], ids=["model", "migration"])
- async def test_the_user_column_takes_null_and_says_set_null(legacy):
- engine = create_async_engine("sqlite+aiosqlite:///:memory:")
- try:
- async with engine.begin() as conn:
- await conn.execute(text(legacy))
- await conn.execute(
- text(
- "INSERT INTO wallet_transactions (id, user_id, transaction_type, amount) VALUES (1, 7, 'deposit', 5)"
- )
- )
- await database._migrate_wallet_transactions_keep_deleted_users(conn)
- await database._migrate_wallet_transactions_keep_deleted_users(conn) # a restart changes nothing
- await conn.execute(text("UPDATE wallet_transactions SET user_id = NULL WHERE id = 1"))
- assert (await conn.execute(text("SELECT user_id, amount FROM wallet_transactions"))).one() == (None, 5)
- sql = (
- await conn.execute(text("SELECT sql FROM sqlite_master WHERE name = 'wallet_transactions'"))
- ).scalar_one()
- assert "CASCADE" not in sql
- # Only the user column changed.
- assert "transaction_type VARCHAR(40) NOT NULL" in sql
- assert sql.count("ON DELETE SET NULL") == 3
- finally:
- await engine.dispose()
|