mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-03 12:46:49 +02:00
135 lines
5.0 KiB
Python
135 lines
5.0 KiB
Python
"""Regression tests for finance tables on upgraded databases."""
|
|
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from sqlalchemy import text
|
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
|
|
from backend.app.core.database import _migrate_create_finance_indexes, _migrate_create_finance_tables
|
|
|
|
EXPECTED_TABLES = {
|
|
"cost_centers",
|
|
"wallet_transactions",
|
|
"budget_reservations",
|
|
"cost_center_members",
|
|
"cost_center_invitations",
|
|
"user_wallets",
|
|
}
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_finance_tables_are_created_idempotently_on_sqlite():
|
|
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
|
|
try:
|
|
async with engine.begin() as conn:
|
|
with patch("backend.app.core.database.is_sqlite", return_value=True):
|
|
await _migrate_create_finance_tables(conn)
|
|
await _migrate_create_finance_tables(conn)
|
|
|
|
rows = await conn.execute(
|
|
text(
|
|
"SELECT name FROM sqlite_master "
|
|
"WHERE type = 'table' AND name IN "
|
|
"('cost_centers', 'wallet_transactions', 'budget_reservations', "
|
|
"'cost_center_members', 'cost_center_invitations', 'user_wallets')"
|
|
)
|
|
)
|
|
|
|
assert {row[0] for row in rows} == EXPECTED_TABLES
|
|
finally:
|
|
await engine.dispose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_legacy_cost_center_indexes_are_delayed_until_columns_exist():
|
|
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
|
|
|
|
try:
|
|
async with engine.begin() as conn:
|
|
await conn.execute(text("CREATE TABLE cost_centers (id INTEGER PRIMARY KEY, name VARCHAR(150) NOT NULL)"))
|
|
|
|
with patch("backend.app.core.database.is_sqlite", return_value=True):
|
|
await _migrate_create_finance_tables(conn)
|
|
|
|
await conn.execute(text("ALTER TABLE cost_centers ADD COLUMN code VARCHAR(32)"))
|
|
await _migrate_create_finance_indexes(conn)
|
|
|
|
result = await conn.execute(
|
|
text("SELECT name FROM sqlite_master WHERE type = 'index' AND name = 'ix_cost_centers_code'")
|
|
)
|
|
|
|
assert result.scalar_one() == "ix_cost_centers_code"
|
|
finally:
|
|
await engine.dispose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_postgres_finance_ddl_uses_postgres_types():
|
|
statements: list[str] = []
|
|
|
|
async def capture_statement(_conn, sql: str) -> None:
|
|
statements.append(sql)
|
|
|
|
with (
|
|
patch("backend.app.core.database.is_sqlite", return_value=False),
|
|
patch("backend.app.core.database._safe_execute", side_effect=capture_statement),
|
|
):
|
|
await _migrate_create_finance_tables(object())
|
|
|
|
create_statements = [sql for sql in statements if "CREATE TABLE" in sql]
|
|
assert len(create_statements) == len(EXPECTED_TABLES)
|
|
assert all("IF NOT EXISTS" in sql for sql in create_statements)
|
|
assert all("DATETIME" not in sql for sql in create_statements)
|
|
assert all("id SERIAL PRIMARY KEY" in sql for sql in create_statements)
|
|
assert "TIMESTAMP" in "\n".join(create_statements)
|
|
|
|
created_tables = {
|
|
sql.split("CREATE TABLE IF NOT EXISTS", 1)[1].split("(", 1)[0].strip() for sql in create_statements
|
|
}
|
|
assert created_tables == EXPECTED_TABLES
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_finance_tables_are_created_idempotently_on_postgres():
|
|
database_url = os.getenv("BAMBUDDY_TEST_POSTGRES_URL")
|
|
if not database_url:
|
|
pytest.skip("BAMBUDDY_TEST_POSTGRES_URL is not configured")
|
|
|
|
engine = create_async_engine(database_url)
|
|
try:
|
|
async with engine.begin() as conn:
|
|
# Minimal pre-billing schema: these are the only tables referenced
|
|
# by foreign keys in the new finance tables.
|
|
await conn.execute(text("CREATE TABLE users (id SERIAL PRIMARY KEY)"))
|
|
await conn.execute(text("CREATE TABLE print_archives (id SERIAL PRIMARY KEY)"))
|
|
await conn.execute(text("CREATE TABLE print_queue (id SERIAL PRIMARY KEY)"))
|
|
|
|
with patch("backend.app.core.database.is_sqlite", return_value=False):
|
|
await _migrate_create_finance_tables(conn)
|
|
await _migrate_create_finance_tables(conn)
|
|
await _migrate_create_finance_indexes(conn)
|
|
await _migrate_create_finance_indexes(conn)
|
|
|
|
rows = await conn.execute(
|
|
text(
|
|
"SELECT table_name FROM information_schema.tables "
|
|
"WHERE table_schema = 'public' AND table_name = ANY(:tables)"
|
|
),
|
|
{"tables": sorted(EXPECTED_TABLES)},
|
|
)
|
|
timestamp_type = await conn.execute(
|
|
text(
|
|
"SELECT data_type FROM information_schema.columns "
|
|
"WHERE table_schema = 'public' "
|
|
"AND table_name = 'cost_centers' AND column_name = 'created_at'"
|
|
)
|
|
)
|
|
|
|
assert {row[0] for row in rows} == EXPECTED_TABLES
|
|
assert timestamp_type.scalar_one() == "timestamp without time zone"
|
|
finally:
|
|
await engine.dispose()
|