"""Tests for Alembic integration.""" from unittest.mock import MagicMock from sqlalchemy import Column, Integer, MetaData, String, Table, func, select from sqlalchemy.engine import Engine from sqlalchemy_pgview import MaterializedView, View from sqlalchemy_pgview.alembic import ( CreateMaterializedViewOp, CreateViewOp, DropMaterializedViewOp, DropViewOp, RefreshMaterializedViewOp, ) from sqlalchemy_pgview.alembic.autogenerate import ( _get_db_views, _normalize_definition, compare_views, render_create_materialized_view, render_create_view, render_drop_materialized_view, render_drop_view, ) from sqlalchemy_pgview.alembic.ops import ( _create_materialized_view_impl, _create_view_impl, _drop_materialized_view_impl, _drop_view_impl, _refresh_materialized_view_impl, ) class TestAlembicOperations: """Tests for Alembic operation classes.""" def test_create_view_op(self) -> None: """Test CreateViewOp initialization.""" op = CreateViewOp( "test_view", "SELECT id, name FROM users", schema="public", or_replace=True, ) assert op.view_name == "test_view" assert op.select_query == "SELECT id, name FROM users" assert op.schema == "public" assert op.or_replace is True def test_create_view_op_defaults(self) -> None: """Test CreateViewOp default values.""" op = CreateViewOp("test_view", "SELECT 1") assert op.schema is None assert op.or_replace is False def test_create_view_op_reverse(self) -> None: """Test CreateViewOp reverse returns DropViewOp.""" op = CreateViewOp("test_view", "SELECT 1", schema="analytics") reverse = op.reverse() assert isinstance(reverse, DropViewOp) assert reverse.view_name == "test_view" assert reverse.schema == "analytics" def test_drop_view_op(self) -> None: """Test DropViewOp initialization.""" op = DropViewOp( "test_view", schema="public", if_exists=True, cascade=True, ) assert op.view_name == "test_view" assert op.schema == "public" assert op.if_exists is True assert op.cascade is True def test_drop_view_op_defaults(self) -> None: """Test DropViewOp default values.""" op = DropViewOp("test_view") assert op.schema is None assert op.if_exists is True assert op.cascade is False def test_create_materialized_view_op(self) -> None: """Test CreateMaterializedViewOp initialization.""" op = CreateMaterializedViewOp( "test_mview", "SELECT id FROM users", schema="analytics", with_data=True, if_not_exists=True, ) assert op.view_name == "test_mview" assert op.schema == "analytics" assert op.with_data is True assert op.if_not_exists is True def test_create_materialized_view_op_defaults(self) -> None: """Test CreateMaterializedViewOp default values.""" op = CreateMaterializedViewOp("test_mview", "SELECT 1") assert op.schema is None assert op.with_data is True assert op.if_not_exists is False def test_create_materialized_view_op_reverse(self) -> None: """Test CreateMaterializedViewOp reverse returns DropMaterializedViewOp.""" op = CreateMaterializedViewOp("test_mview", "SELECT 1", schema="public") reverse = op.reverse() assert isinstance(reverse, DropMaterializedViewOp) assert reverse.view_name == "test_mview" assert reverse.schema == "public" def test_drop_materialized_view_op(self) -> None: """Test DropMaterializedViewOp initialization.""" op = DropMaterializedViewOp( "test_mview", schema="public", if_exists=False, cascade=True, ) assert op.view_name == "test_mview" assert op.if_exists is False assert op.cascade is True def test_drop_materialized_view_op_defaults(self) -> None: """Test DropMaterializedViewOp default values.""" op = DropMaterializedViewOp("test_mview") assert op.schema is None assert op.if_exists is True assert op.cascade is False def test_refresh_materialized_view_op(self) -> None: """Test RefreshMaterializedViewOp initialization.""" op = RefreshMaterializedViewOp( "test_mview", schema="analytics", concurrently=True, with_data=True, ) assert op.view_name == "test_mview" assert op.schema == "analytics" assert op.concurrently is True assert op.with_data is True def test_refresh_materialized_view_op_defaults(self) -> None: """Test RefreshMaterializedViewOp default values.""" op = RefreshMaterializedViewOp("test_mview") assert op.schema is None assert op.concurrently is False assert op.with_data is True class TestAlembicImplementations: """Tests for Alembic operation implementations.""" def test_create_view_impl(self) -> None: """Test _create_view_impl generates correct SQL.""" mock_ops = MagicMock() op = CreateViewOp("test_view", "SELECT id FROM users") _create_view_impl(mock_ops, op) mock_ops.execute.assert_called_once() sql = mock_ops.execute.call_args[0][0] assert "CREATE VIEW test_view AS SELECT id FROM users" in sql def test_create_view_impl_or_replace(self) -> None: """Test _create_view_impl with OR REPLACE.""" mock_ops = MagicMock() op = CreateViewOp("test_view", "SELECT 1", or_replace=True) _create_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "CREATE OR REPLACE VIEW" in sql def test_create_view_impl_with_schema(self) -> None: """Test _create_view_impl with schema.""" mock_ops = MagicMock() op = CreateViewOp("test_view", "SELECT 1", schema="analytics") _create_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "analytics.test_view" in sql def test_drop_view_impl(self) -> None: """Test _drop_view_impl generates correct SQL.""" mock_ops = MagicMock() op = DropViewOp("test_view", if_exists=False) _drop_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert sql == "DROP VIEW test_view" def test_drop_view_impl_if_exists(self) -> None: """Test _drop_view_impl with IF EXISTS.""" mock_ops = MagicMock() op = DropViewOp("test_view", if_exists=True) _drop_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "IF EXISTS" in sql def test_drop_view_impl_cascade(self) -> None: """Test _drop_view_impl with CASCADE.""" mock_ops = MagicMock() op = DropViewOp("test_view", cascade=True, if_exists=False) _drop_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "CASCADE" in sql def test_drop_view_impl_with_schema(self) -> None: """Test _drop_view_impl with schema.""" mock_ops = MagicMock() op = DropViewOp("test_view", schema="analytics", if_exists=False) _drop_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "analytics.test_view" in sql def test_create_materialized_view_impl(self) -> None: """Test _create_materialized_view_impl generates correct SQL.""" mock_ops = MagicMock() op = CreateMaterializedViewOp("test_mview", "SELECT id FROM users") _create_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "CREATE MATERIALIZED VIEW test_mview" in sql assert "WITH DATA" in sql def test_create_materialized_view_impl_without_data(self) -> None: """Test _create_materialized_view_impl with WITH NO DATA.""" mock_ops = MagicMock() op = CreateMaterializedViewOp("test_mview", "SELECT 1", with_data=False) _create_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "WITH NO DATA" in sql def test_create_materialized_view_impl_if_not_exists(self) -> None: """Test _create_materialized_view_impl with IF NOT EXISTS.""" mock_ops = MagicMock() op = CreateMaterializedViewOp("test_mview", "SELECT 1", if_not_exists=True) _create_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "IF NOT EXISTS" in sql def test_create_materialized_view_impl_with_schema(self) -> None: """Test _create_materialized_view_impl with schema.""" mock_ops = MagicMock() op = CreateMaterializedViewOp("test_mview", "SELECT 1", schema="analytics") _create_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "analytics.test_mview" in sql def test_drop_materialized_view_impl(self) -> None: """Test _drop_materialized_view_impl generates correct SQL.""" mock_ops = MagicMock() op = DropMaterializedViewOp("test_mview", if_exists=False) _drop_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert sql == "DROP MATERIALIZED VIEW test_mview" def test_drop_materialized_view_impl_if_exists(self) -> None: """Test _drop_materialized_view_impl with IF EXISTS.""" mock_ops = MagicMock() op = DropMaterializedViewOp("test_mview", if_exists=True) _drop_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "IF EXISTS" in sql def test_drop_materialized_view_impl_cascade(self) -> None: """Test _drop_materialized_view_impl with CASCADE.""" mock_ops = MagicMock() op = DropMaterializedViewOp("test_mview", cascade=True, if_exists=False) _drop_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "CASCADE" in sql def test_drop_materialized_view_impl_with_schema(self) -> None: """Test _drop_materialized_view_impl with schema.""" mock_ops = MagicMock() op = DropMaterializedViewOp("test_mview", schema="analytics", if_exists=False) _drop_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "analytics.test_mview" in sql def test_refresh_materialized_view_impl(self) -> None: """Test _refresh_materialized_view_impl generates correct SQL.""" mock_ops = MagicMock() op = RefreshMaterializedViewOp("test_mview") _refresh_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "REFRESH MATERIALIZED VIEW test_mview" in sql assert "WITH DATA" in sql def test_refresh_materialized_view_impl_concurrently(self) -> None: """Test _refresh_materialized_view_impl with CONCURRENTLY.""" mock_ops = MagicMock() op = RefreshMaterializedViewOp("test_mview", concurrently=True) _refresh_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "CONCURRENTLY" in sql def test_refresh_materialized_view_impl_without_data(self) -> None: """Test _refresh_materialized_view_impl with WITH NO DATA.""" mock_ops = MagicMock() op = RefreshMaterializedViewOp("test_mview", with_data=False) _refresh_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "WITH NO DATA" in sql def test_refresh_materialized_view_impl_with_schema(self) -> None: """Test _refresh_materialized_view_impl with schema.""" mock_ops = MagicMock() op = RefreshMaterializedViewOp("test_mview", schema="analytics") _refresh_materialized_view_impl(mock_ops, op) sql = mock_ops.execute.call_args[0][0] assert "analytics.test_mview" in sql class TestAlembicClassMethods: """Tests for Alembic operation classmethods.""" def test_create_view_classmethod(self) -> None: """Test CreateViewOp.create_view classmethod.""" mock_ops = MagicMock() CreateViewOp.create_view( mock_ops, "test_view", "SELECT 1", schema="public", or_replace=True ) mock_ops.invoke.assert_called_once() invoked_op = mock_ops.invoke.call_args[0][0] assert isinstance(invoked_op, CreateViewOp) assert invoked_op.view_name == "test_view" assert invoked_op.or_replace is True def test_drop_view_classmethod(self) -> None: """Test DropViewOp.drop_view classmethod.""" mock_ops = MagicMock() DropViewOp.drop_view(mock_ops, "test_view", schema="public", cascade=True) mock_ops.invoke.assert_called_once() invoked_op = mock_ops.invoke.call_args[0][0] assert isinstance(invoked_op, DropViewOp) assert invoked_op.cascade is True def test_create_materialized_view_classmethod(self) -> None: """Test CreateMaterializedViewOp.create_materialized_view classmethod.""" mock_ops = MagicMock() CreateMaterializedViewOp.create_materialized_view( mock_ops, "test_mview", "SELECT 1", with_data=False, if_not_exists=True ) mock_ops.invoke.assert_called_once() invoked_op = mock_ops.invoke.call_args[0][0] assert isinstance(invoked_op, CreateMaterializedViewOp) assert invoked_op.with_data is False assert invoked_op.if_not_exists is True def test_drop_materialized_view_classmethod(self) -> None: """Test DropMaterializedViewOp.drop_materialized_view classmethod.""" mock_ops = MagicMock() DropMaterializedViewOp.drop_materialized_view( mock_ops, "test_mview", cascade=True ) mock_ops.invoke.assert_called_once() invoked_op = mock_ops.invoke.call_args[0][0] assert isinstance(invoked_op, DropMaterializedViewOp) assert invoked_op.cascade is True def test_refresh_materialized_view_classmethod(self) -> None: """Test RefreshMaterializedViewOp.refresh_materialized_view classmethod.""" mock_ops = MagicMock() RefreshMaterializedViewOp.refresh_materialized_view( mock_ops, "test_mview", concurrently=True, with_data=False ) mock_ops.invoke.assert_called_once() invoked_op = mock_ops.invoke.call_args[0][0] assert isinstance(invoked_op, RefreshMaterializedViewOp) assert invoked_op.concurrently is True assert invoked_op.with_data is False class TestAutogenerateHelpers: """Tests for autogenerate helper functions.""" def test_normalize_definition_none(self) -> None: """Test _normalize_definition with None.""" assert _normalize_definition(None) == "" def test_normalize_definition_empty(self) -> None: """Test _normalize_definition with empty string.""" assert _normalize_definition("") == "" def test_normalize_definition_whitespace(self) -> None: """Test _normalize_definition normalizes whitespace.""" definition = "SELECT id,\n name FROM users" normalized = _normalize_definition(definition) assert normalized == "select id, name from users" def test_normalize_definition_semicolon(self) -> None: """Test _normalize_definition removes trailing semicolon.""" definition = "SELECT id FROM users;" normalized = _normalize_definition(definition) assert not normalized.endswith(";") def test_normalize_definition_case(self) -> None: """Test _normalize_definition converts to lowercase.""" definition = "SELECT ID FROM USERS" normalized = _normalize_definition(definition) assert normalized == "select id from users" class TestAutogenerateGetDbViews: """Tests for _get_db_views function.""" def test_get_db_views(self, pg_engine: Engine) -> None: """Test _get_db_views retrieves views from database.""" metadata = MetaData() users = Table( "test_users_autogen", metadata, Column("id", Integer, primary_key=True), Column("name", String(100)), ) view = View( "test_view_autogen", select(users.c.id, users.c.name), ) from sqlalchemy_pgview import CreateView, DropView try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: conn.execute(CreateView(view, or_replace=True)) db_views = _get_db_views(conn) assert "test_view_autogen" in db_views assert db_views["test_view_autogen"]["is_materialized"] is False conn.execute(DropView(view, if_exists=True)) finally: metadata.drop_all(pg_engine) def test_get_db_views_with_schema_filter(self, pg_engine: Engine) -> None: """Test _get_db_views with schema filter.""" metadata = MetaData() users = Table( "test_users_schema", metadata, Column("id", Integer, primary_key=True), ) view = View("test_view_schema", select(users.c.id)) from sqlalchemy_pgview import CreateView, DropView try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: conn.execute(CreateView(view, or_replace=True)) # Filter by public schema db_views = _get_db_views(conn, schema="public") assert "test_view_schema" in db_views # Filter by non-existent schema db_views = _get_db_views(conn, schema="nonexistent") assert "test_view_schema" not in db_views conn.execute(DropView(view, if_exists=True)) finally: metadata.drop_all(pg_engine) def test_get_db_views_materialized(self, pg_engine: Engine) -> None: """Test _get_db_views identifies materialized views.""" metadata = MetaData() users = Table( "test_users_mv_autogen", metadata, Column("id", Integer, primary_key=True), ) mview = MaterializedView( "test_mview_autogen", select(func.count(users.c.id).label("count")), with_data=True, ) from sqlalchemy_pgview import CreateMaterializedView, DropMaterializedView try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: conn.execute(CreateMaterializedView(mview, if_not_exists=True)) db_views = _get_db_views(conn) assert "test_mview_autogen" in db_views assert db_views["test_mview_autogen"]["is_materialized"] is True conn.execute(DropMaterializedView(mview, if_exists=True)) finally: metadata.drop_all(pg_engine) class TestAutogenerateRenderers: """Tests for autogenerate renderer functions.""" def test_render_create_view(self) -> None: """Test render_create_view generates correct Python code.""" op = CreateViewOp("test_view", "SELECT id FROM users") mock_context = MagicMock() result = render_create_view(mock_context, op) assert "op.create_view(" in result assert "'test_view'" in result assert "SELECT id FROM users" in result def test_render_create_view_with_schema(self) -> None: """Test render_create_view with schema.""" op = CreateViewOp("test_view", "SELECT 1", schema="analytics") mock_context = MagicMock() result = render_create_view(mock_context, op) assert "schema='analytics'" in result def test_render_create_view_or_replace(self) -> None: """Test render_create_view with or_replace.""" op = CreateViewOp("test_view", "SELECT 1", or_replace=True) mock_context = MagicMock() result = render_create_view(mock_context, op) assert "or_replace=True" in result def test_render_drop_view(self) -> None: """Test render_drop_view generates correct Python code.""" op = DropViewOp("test_view") mock_context = MagicMock() result = render_drop_view(mock_context, op) assert "op.drop_view(" in result assert "'test_view'" in result def test_render_drop_view_with_options(self) -> None: """Test render_drop_view with options.""" op = DropViewOp("test_view", schema="analytics", if_exists=False, cascade=True) mock_context = MagicMock() result = render_drop_view(mock_context, op) assert "schema='analytics'" in result assert "if_exists=False" in result assert "cascade=True" in result def test_render_create_materialized_view(self) -> None: """Test render_create_materialized_view generates correct Python code.""" op = CreateMaterializedViewOp("test_mview", "SELECT 1") mock_context = MagicMock() result = render_create_materialized_view(mock_context, op) assert "op.create_materialized_view(" in result assert "'test_mview'" in result def test_render_create_materialized_view_without_data(self) -> None: """Test render_create_materialized_view with with_data=False.""" op = CreateMaterializedViewOp("test_mview", "SELECT 1", with_data=False) mock_context = MagicMock() result = render_create_materialized_view(mock_context, op) assert "with_data=False" in result def test_render_create_materialized_view_with_schema(self) -> None: """Test render_create_materialized_view with schema.""" op = CreateMaterializedViewOp("test_mview", "SELECT 1", schema="analytics") mock_context = MagicMock() result = render_create_materialized_view(mock_context, op) assert "schema='analytics'" in result def test_render_drop_materialized_view(self) -> None: """Test render_drop_materialized_view generates correct Python code.""" op = DropMaterializedViewOp("test_mview") mock_context = MagicMock() result = render_drop_materialized_view(mock_context, op) assert "op.drop_materialized_view(" in result assert "'test_mview'" in result def test_render_drop_materialized_view_with_options(self) -> None: """Test render_drop_materialized_view with options.""" op = DropMaterializedViewOp( "test_mview", schema="analytics", if_exists=False, cascade=True ) mock_context = MagicMock() result = render_drop_materialized_view(mock_context, op) assert "schema='analytics'" in result assert "if_exists=False" in result assert "cascade=True" in result class TestCompareViews: """Tests for compare_views autogenerate function.""" def test_compare_views_non_postgresql(self) -> None: """Test compare_views returns early for non-PostgreSQL.""" mock_context = MagicMock() mock_context.connection.dialect.name = "sqlite" mock_context.metadata = MetaData() mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should not add any ops for non-PostgreSQL assert len(mock_upgrade_ops.ops) == 0 def test_compare_views_none_connection(self) -> None: """Test compare_views returns early for None connection.""" mock_context = MagicMock() mock_context.connection = None mock_context.metadata = MetaData() mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should not add any ops for None connection assert len(mock_upgrade_ops.ops) == 0 def test_compare_views_creates_new_view(self, pg_engine: Engine) -> None: """Test compare_views detects view to create.""" # First create tables only (no views) table_metadata = MetaData() Table( "test_users_compare", table_metadata, Column("id", Integer, primary_key=True), Column("name", String(100)), ) try: table_metadata.create_all(pg_engine) # Now create a separate metadata with a view registered view_metadata = MetaData() users_ref = Table( "test_users_compare", view_metadata, Column("id", Integer, primary_key=True), Column("name", String(100)), ) # Register a view in the new metadata (not in database yet) View( "test_view_compare", select(users_ref.c.id, users_ref.c.name), metadata=view_metadata, ) with pg_engine.connect() as conn: mock_context = MagicMock() mock_context.connection = conn mock_context.metadata = view_metadata mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should detect view to create create_ops = [ op for op in mock_upgrade_ops.ops if isinstance(op, CreateViewOp) ] assert len(create_ops) == 1 assert create_ops[0].view_name == "test_view_compare" finally: table_metadata.drop_all(pg_engine) def test_compare_views_creates_new_materialized_view( self, pg_engine: Engine ) -> None: """Test compare_views detects materialized view to create.""" # First create tables only (no views) table_metadata = MetaData() Table( "test_users_compare_mv", table_metadata, Column("id", Integer, primary_key=True), ) try: table_metadata.create_all(pg_engine) # Now create a separate metadata with a materialized view registered view_metadata = MetaData() users_ref = Table( "test_users_compare_mv", view_metadata, Column("id", Integer, primary_key=True), ) # Register a materialized view in the new metadata (not in database yet) MaterializedView( "test_mview_compare", select(func.count(users_ref.c.id).label("count")), metadata=view_metadata, with_data=True, ) with pg_engine.connect() as conn: mock_context = MagicMock() mock_context.connection = conn mock_context.metadata = view_metadata mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should detect materialized view to create create_ops = [ op for op in mock_upgrade_ops.ops if isinstance(op, CreateMaterializedViewOp) ] assert len(create_ops) == 1 assert create_ops[0].view_name == "test_mview_compare" finally: table_metadata.drop_all(pg_engine) def test_compare_views_drops_orphan_view(self, pg_engine: Engine) -> None: """Test compare_views detects view to drop.""" metadata = MetaData() users = Table( "test_users_drop_compare", metadata, Column("id", Integer, primary_key=True), ) # Create a view in the database but NOT in metadata orphan_view = View( "test_orphan_view", select(users.c.id), ) from sqlalchemy_pgview import CreateView, DropView try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: conn.execute(CreateView(orphan_view, or_replace=True)) # Now compare with empty metadata (no views registered) empty_metadata = MetaData() with pg_engine.connect() as conn: mock_context = MagicMock() mock_context.connection = conn mock_context.metadata = empty_metadata mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should detect orphan view to drop drop_ops = [ op for op in mock_upgrade_ops.ops if isinstance(op, DropViewOp) ] orphan_drop = [ op for op in drop_ops if op.view_name == "test_orphan_view" ] assert len(orphan_drop) == 1 # Cleanup with pg_engine.begin() as conn: conn.execute(DropView(orphan_view, if_exists=True)) finally: metadata.drop_all(pg_engine) def test_compare_views_drops_orphan_materialized_view( self, pg_engine: Engine ) -> None: """Test compare_views detects materialized view to drop.""" metadata = MetaData() users = Table( "test_users_drop_mv_compare", metadata, Column("id", Integer, primary_key=True), ) # Create a materialized view in the database but NOT in metadata orphan_mview = MaterializedView( "test_orphan_mview", select(func.count(users.c.id).label("cnt")), with_data=True, ) from sqlalchemy_pgview import CreateMaterializedView, DropMaterializedView try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: conn.execute(CreateMaterializedView(orphan_mview, if_not_exists=True)) # Now compare with empty metadata (no views registered) empty_metadata = MetaData() with pg_engine.connect() as conn: mock_context = MagicMock() mock_context.connection = conn mock_context.metadata = empty_metadata mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should detect orphan materialized view to drop drop_ops = [ op for op in mock_upgrade_ops.ops if isinstance(op, DropMaterializedViewOp) ] orphan_drop = [ op for op in drop_ops if op.view_name == "test_orphan_mview" ] assert len(orphan_drop) == 1 # Cleanup with pg_engine.begin() as conn: conn.execute(DropMaterializedView(orphan_mview, if_exists=True)) finally: metadata.drop_all(pg_engine) def test_compare_views_no_changes(self, pg_engine: Engine) -> None: """Test compare_views when views are in sync.""" metadata = MetaData() users = Table( "test_users_sync", metadata, Column("id", Integer, primary_key=True), ) # Register a view in metadata view = View( "test_view_sync", select(users.c.id), metadata=metadata, ) from sqlalchemy_pgview import CreateView, DropView try: metadata.create_all(pg_engine) # Create the same view in database with pg_engine.begin() as conn: conn.execute(CreateView(view, or_replace=True)) with pg_engine.connect() as conn: mock_context = MagicMock() mock_context.connection = conn mock_context.metadata = metadata mock_upgrade_ops = MagicMock() mock_upgrade_ops.ops = [] compare_views(mock_context, mock_upgrade_ops, [None]) # Should not detect any changes for this specific view # (there might be other system views to drop) create_ops = [ op for op in mock_upgrade_ops.ops if isinstance(op, CreateViewOp) and op.view_name == "test_view_sync" ] assert len(create_ops) == 0 # Cleanup with pg_engine.begin() as conn: conn.execute(DropView(view, if_exists=True)) finally: metadata.drop_all(pg_engine) class TestAlembicIntegration: """Integration tests for Alembic operations with real database.""" def test_create_and_drop_view_integration(self, pg_engine: Engine) -> None: """Test creating and dropping a view using Alembic ops.""" metadata = MetaData() Table( "test_users_alembic_int", metadata, Column("id", Integer, primary_key=True), Column("name", String(100)), ) try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: # Create view using raw SQL (simulating Alembic implementation) conn.exec_driver_sql( "CREATE VIEW test_view_alembic_int AS SELECT id, name FROM test_users_alembic_int" ) # Verify view exists from sqlalchemy import text result = conn.execute( text( "SELECT COUNT(*) FROM pg_class WHERE relname = 'test_view_alembic_int'" ) ).scalar() assert result == 1 # Drop view conn.exec_driver_sql("DROP VIEW IF EXISTS test_view_alembic_int") # Verify view is gone result = conn.execute( text( "SELECT COUNT(*) FROM pg_class WHERE relname = 'test_view_alembic_int'" ) ).scalar() assert result == 0 finally: metadata.drop_all(pg_engine) def test_create_and_drop_materialized_view_integration( self, pg_engine: Engine ) -> None: """Test creating and dropping a materialized view using Alembic ops.""" metadata = MetaData() Table( "test_users_mview_int", metadata, Column("id", Integer, primary_key=True), ) try: metadata.create_all(pg_engine) with pg_engine.begin() as conn: # Create materialized view conn.exec_driver_sql( "CREATE MATERIALIZED VIEW test_mview_alembic_int AS " "SELECT COUNT(*) as cnt FROM test_users_mview_int WITH DATA" ) # Verify materialized view exists from sqlalchemy import text result = conn.execute( text( "SELECT COUNT(*) FROM pg_class WHERE relname = 'test_mview_alembic_int' AND relkind = 'm'" ) ).scalar() assert result == 1 # Refresh materialized view conn.exec_driver_sql( "REFRESH MATERIALIZED VIEW test_mview_alembic_int WITH DATA" ) # Drop materialized view conn.exec_driver_sql( "DROP MATERIALIZED VIEW IF EXISTS test_mview_alembic_int" ) # Verify materialized view is gone result = conn.execute( text( "SELECT COUNT(*) FROM pg_class WHERE relname = 'test_mview_alembic_int'" ) ).scalar() assert result == 0 finally: metadata.drop_all(pg_engine)