"""Tests snowflake connection.""" from contextlib import contextmanager from unittest.mock import MagicMock from unittest.mock import patch import pytest from sound_recordings.utils import snowflake @pytest.fixture def mock_fetchone(): """Mock Snowflake fetchone.""" with patch( 'sound_recordings.utils.snowflake.snowflake_conn.fetchone' ) as fetchone: fetchone.return_value = {} yield fetchone @pytest.fixture def mock_fetchall(): """Mock Snowflake fetchall.""" with patch( 'sound_recordings.utils.snowflake.snowflake_conn.fetchall' ) as fetchall: fetchall.return_value = [] yield fetchall def test_fetchone(mock_fetchone): """Test fetchone.""" sql = 'DATA PLEASE' snowflake.fetchone(sql) mock_fetchone.assert_called_with(sql, **snowflake.DEFAULT_CONFIG) def test_fetchone_with_config(mock_fetchone): """Test fetchone with config.""" sql = 'DATA PLEASE' params = {'param1': 'value1'} config = {'pool_pre_ping': True} snowflake.fetchone(sql, params, **config) mock_fetchone.assert_called_with( sql, params, **{**config, **snowflake.DEFAULT_CONFIG} ) def test_fetchall(mock_fetchall): """Test fetchall.""" sql = 'DATA PLEASE' snowflake.fetchall(sql) mock_fetchall.assert_called_with(sql, **snowflake.DEFAULT_CONFIG) def test_fetchall_with_config(mock_fetchall): """Test fetchall with config.""" sql = 'DATA PLEASE' params = {'param1': 'value1'} config = {'pool_pre_ping': True} snowflake.fetchall(sql, params, **config) mock_fetchall.assert_called_with( sql, params, **{**config, **snowflake.DEFAULT_CONFIG} ) @pytest.fixture def tx_env(): """Mock get_session as a real context manager yielding a mock session. A real context manager (not a blanket MagicMock) is used so exceptions propagate exactly as the live ``get_session`` would, instead of being swallowed by a mock ``__exit__``. """ session = MagicMock() captured = {} @contextmanager def fake_get_session(**kwargs): captured['kwargs'] = kwargs yield session with patch.object( snowflake.snowflake_conn, 'get_session', fake_get_session ), patch.object( snowflake.snowflake_conn, 'text', lambda sql: sql ): yield session, captured def test_transaction_sets_timeouts_and_runs_statements(tx_env): """Transaction sets session timeouts, commits on exit, runs statements.""" session, captured = tx_env with snowflake.transaction() as tx: tx.execute('INSERT INTO t (a) VALUES (:a)', {'a': 1}) assert captured['kwargs'] == {'commit_before_close': True} executed = [c.args[0] for c in session.execute.call_args_list] assert any('LOCK_TIMEOUT' in sql for sql in executed) assert any('STATEMENT_TIMEOUT_IN_SECONDS' in sql for sql in executed) session.execute.assert_called_with( 'INSERT INTO t (a) VALUES (:a)', {'a': 1} ) def test_transaction_execute_defaults_params_to_empty(tx_env): """execute() without params binds an empty dict.""" session, _ = tx_env with snowflake.transaction() as tx: tx.execute('DELETE FROM t WHERE a = 1') session.execute.assert_called_with('DELETE FROM t WHERE a = 1', {}) def test_transaction_propagates_errors(tx_env): """An error inside the block propagates (rollback is get_session's job).""" with pytest.raises(ValueError): with snowflake.transaction(): raise ValueError('boom') def test_transaction_fetchall_returns_rows_within_transaction(tx_env): """fetchall() runs the query in the transaction and returns all rows.""" session, _ = tx_env session.execute.return_value.cursor.fetchall.return_value = [(1,), (2,)] with snowflake.transaction() as tx: rows = tx.fetchall('SELECT a FROM t WHERE b = :b', {'b': 9}) assert rows == [(1,), (2,)] session.execute.assert_called_with('SELECT a FROM t WHERE b = :b', {'b': 9}) def test_transaction_fetchone_returns_first_row(tx_env): """fetchone() runs the query in the transaction and returns one row.""" session, _ = tx_env session.execute.return_value.cursor.fetchone.return_value = (1,) with snowflake.transaction() as tx: row = tx.fetchone('SELECT a FROM t') assert row == (1,) session.execute.assert_called_with('SELECT a FROM t', {})