from unittest import mock import pytest from test_fixtures.crud import ( clone_row, delete_entity, get_entity, insert_entity, update_entity, ) from test_fixtures.models import ExecuteResult def _conn(): conn = mock.Mock() conn.execute.return_value = ExecuteResult(rowcount=1, rows=[]) conn.fetchone.return_value = None return conn def test_get_entity_builds_where_clause(): conn = _conn() conn.fetchone.return_value = {'id': 1, 'name': 'a'} result = get_entity(conn, 'widget', {'vendor_id': 7, 'status': 'new'}) assert result == {'id': 1, 'name': 'a'} conn.fetchone.assert_called_once_with( 'SELECT * FROM `widget` WHERE `vendor_id` = %s AND `status` = %s', (7, 'new'), ) def test_get_entity_no_conditions_selects_all(): conn = _conn() get_entity(conn, 'widget', {}) conn.fetchone.assert_called_once_with('SELECT * FROM `widget`', ()) def test_get_entity_none_condition_uses_is_null(): conn = _conn() get_entity(conn, 'widget', {'deleted_at': None}) conn.fetchone.assert_called_once_with( 'SELECT * FROM `widget` WHERE `deleted_at` IS NULL', () ) def test_insert_entity_without_id_column_returns_none(): conn = _conn() result = insert_entity(conn, 'widget', {'name': 'a', 'vendor_id': 7}) assert result is None conn.execute.assert_called_once_with( 'INSERT INTO `widget` (`name`, `vendor_id`) VALUES (%s, %s)', ('a', 7), ) conn.fetchone.assert_not_called() def test_insert_entity_fetches_by_last_insert_id_for_autoincrement(): conn = _conn() conn.fetchone.return_value = {'widget_id': 42, 'name': 'a'} result = insert_entity(conn, 'widget', {'name': 'a'}, id_column='widget_id') assert result == {'widget_id': 42, 'name': 'a'} conn.fetchone.assert_called_once_with( 'SELECT * FROM `widget` WHERE `widget_id` = LAST_INSERT_ID()' ) def test_insert_entity_fetches_by_supplied_id(): conn = _conn() conn.fetchone.return_value = {'widget_id': 'abc', 'name': 'a'} result = insert_entity( conn, 'widget', {'widget_id': 'abc', 'name': 'a'}, id_column='widget_id' ) assert result == {'widget_id': 'abc', 'name': 'a'} conn.fetchone.assert_called_once_with( 'SELECT * FROM `widget` WHERE `widget_id` = %s', ('abc',) ) def test_insert_entity_requires_values(): conn = _conn() with pytest.raises(ValueError, match='at least one column'): insert_entity(conn, 'widget', {}) def test_update_entity_returns_rowcount(): conn = _conn() conn.execute.return_value = ExecuteResult(rowcount=2, rows=[]) affected = update_entity( conn, 'widget', {'vendor_id': 7}, {'status': 'done', 'name': 'b'} ) assert affected == 2 conn.execute.assert_called_once_with( 'UPDATE `widget` SET `status` = %s, `name` = %s WHERE `vendor_id` = %s', ('done', 'b', 7), ) def test_update_entity_requires_conditions(): conn = _conn() with pytest.raises(ValueError, match='conditions'): update_entity(conn, 'widget', {}, {'status': 'done'}) def test_update_entity_requires_values(): conn = _conn() with pytest.raises(ValueError, match='at least one column'): update_entity(conn, 'widget', {'vendor_id': 7}, {}) def test_delete_entity_returns_rowcount(): conn = _conn() conn.execute.return_value = ExecuteResult(rowcount=3, rows=[]) affected = delete_entity(conn, 'widget', {'vendor_id': 7}) assert affected == 3 conn.execute.assert_called_once_with( 'DELETE FROM `widget` WHERE `vendor_id` = %s', (7,) ) def test_delete_entity_requires_conditions(): conn = _conn() with pytest.raises(ValueError, match='conditions'): delete_entity(conn, 'widget', {}) conn.execute.assert_not_called() def test_clone_row_drops_id_and_applies_overrides(): conn = _conn() # First fetchone: source row. Second fetchone: inserted clone. conn.fetchone.side_effect = [ {'widget_id': 1, 'name': 'a', 'vendor_id': 7}, {'widget_id': 2, 'name': 'a', 'vendor_id': 8}, ] result = clone_row( conn, 'widget', {'widget_id': 1}, overrides={'vendor_id': 8}, id_column='widget_id', ) assert result == {'widget_id': 2, 'name': 'a', 'vendor_id': 8} # The INSERT omits widget_id (auto-generated) and carries the override. conn.execute.assert_called_once_with( 'INSERT INTO `widget` (`name`, `vendor_id`) VALUES (%s, %s)', ('a', 8), ) def test_clone_row_raises_when_source_missing(): conn = _conn() conn.fetchone.return_value = None with pytest.raises(LookupError, match='to clone'): clone_row(conn, 'widget', {'widget_id': 999}, id_column='widget_id') def test_identifier_injection_is_rejected(): conn = _conn() with pytest.raises(ValueError, match='Invalid SQL identifier'): get_entity(conn, 'widget`; DROP TABLE users; --', {'id': 1}) def test_empty_identifier_is_rejected(): conn = _conn() with pytest.raises(ValueError, match='Invalid SQL identifier'): get_entity(conn, '', {'id': 1}) with pytest.raises(ValueError, match='Invalid SQL identifier'): insert_entity(conn, 'widget', {'': 1})