"""Tests for db_utils.py update path in upsert function. The upsert function has two paths: 1. Row exists -> either return existing (do_update=False) or update it (do_update=True) 2. Row doesn't exist -> insert new row These tests cover the update path (lines 25-26). NOTE: MYPY errors suppressed for test fixtures - see AGENTS.md for type checking notes. """ from sqlalchemy.orm import Session from src.db_utils import upsert class TestUpsertUpdatePath: """Tests for the update path in db_utils.upsert.""" def test_upsert_updates_existing_row(self, db_session: Session): """Test that upsert updates an existing row when do_update=True.""" from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() class TestModel(Base): __tablename__ = "test_model" id: int = Column(Integer, primary_key=True, autoincrement=True) name: str = Column(String(100), nullable=False) value: str = Column(String(50), nullable=False) Base.metadata.create_all(db_session.get_bind()) # Create and insert a row initial = TestModel(name="initial", value="old_value") db_session.add(initial) db_session.commit() # Verify it was inserted result = db_session.query(TestModel).filter_by(name="initial").one() assert result.value == "old_value" def test_upsert_skips_update_when_do_update_false(self, db_session: Session): """Test that upsert doesn't modify existing row when do_update=False.""" from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() class TestModel(Base): __tablename__ = "test_model_2" id: int = Column(Integer, primary_key=True, autoincrement=True) name: str = Column(String(100), nullable=False) value: str = Column(String(50), nullable=False) Base.metadata.create_all(db_session.get_bind()) # Create and insert a row initial = TestModel(name="skip_update", value="original") db_session.add(initial) db_session.commit() # Upsert with do_update=False should not modify existing row same_row = TestModel(name="skip_update", value="should_not_change") result = upsert( db_session, TestModel, {"name": "skip_update"}, same_row.__dict__, do_update=False, do_commit=True, ) # The row should still have original values assert result.value == "original" def test_upsert_updates_and_returns_existing(self, db_session: Session): """Test that upsert returns the existing (now updated) row when do_update=True.""" from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() class TestModel(Base): __tablename__ = "test_model_3" id: int = Column(Integer, primary_key=True, autoincrement=True) name: str = Column(String(100), nullable=False) value: str = Column(String(50), nullable=False) Base.metadata.create_all(db_session.get_bind()) # Create and insert a row initial = TestModel(name="update_test", value="old") db_session.add(initial) db_session.commit() # Upsert with do_update=True should update and return the same object new_values = TestModel(name="update_test", value="new_value") result = upsert( db_session, TestModel, {"name": "update_test"}, new_values.__dict__, do_update=True, do_commit=True, ) # Result should be the same row (not a new one) assert result is not None # Should have updated values assert result.value == "new_value" def test_upsert_returns_updated_row_by_value_when_not_same_object( self, db_session: Session ): """Test update path when the returned row is same by identity (session cache).""" from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() class TestModel(Base): __tablename__ = "test_model_4" id: int = Column(Integer, primary_key=True, autoincrement=True) name: str = Column(String(100), nullable=False) value: str = Column(String(50), nullable=False) Base.metadata.create_all(db_session.get_bind()) # Create and insert a row initial = TestModel(name="identity_test", value="initial") db_session.add(initial) db_session.commit() # Get the row from DB again (fresh load) before_update = ( db_session.query(TestModel).filter_by(name="identity_test").one() ) # Now upsert should return same object due to session cache + identity map new_values = TestModel(name="identity_test", value="updated") result = upsert( db_session, TestModel, {"name": "identity_test"}, new_values.__dict__, do_update=True, do_commit=False, ) # don't commit yet # Should be same identity due to session cache assert result is before_update assert result.value == "updated" class TestUpsertUpdateEdgeCases: """Test edge cases for the update path.""" def test_upsert_partial_update(self, db_session: Session): """Test that only specified fields are updated.""" from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() class TestModel(Base): __tablename__ = "test_model_5" id: int = Column(Integer, primary_key=True, autoincrement=True) name: str = Column(String(100), nullable=False) value: str = Column(String(50), nullable=False) extra: str = Column(String(50), nullable=False) Base.metadata.create_all(db_session.get_bind()) # Create and insert a row with all fields initial = TestModel( name="partial", value="original_value", extra="extra_original" ) db_session.add(initial) db_session.commit() # Create new values with only some fields set (note: we skip 'extra' field) partial_update = TestModel(name="partial", value="new_value", extra=None) result = upsert( db_session, TestModel, {"name": "partial"}, partial_update.__dict__, do_update=True, do_commit=True, ) # value should be updated since it was in the dict assert result.value == "new_value" # extra field handling depends on what's in the __dict__ class TestUpsertUpdateEmptyDict: """Test edge case of empty update dictionary.""" def test_upsert_with_empty_update_dict(self, db_session: Session): """Test upsert with empty parameters_new dict - should still work if existing found.""" from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base Base = declarative_base() class TestModel(Base): __tablename__ = "test_model_6" id: int = Column(Integer, primary_key=True, autoincrement=True) name: str = Column(String(100), nullable=False) value: str = Column(String(50), nullable=False) Base.metadata.create_all(db_session.get_bind()) # Create and insert a row initial = TestModel(name="empty_dict", value="test") db_session.add(initial) db_session.commit() # Try to upsert with empty new parameters (edge case) result = upsert( db_session, TestModel, {"name": "empty_dict"}, {}, do_update=True, do_commit=False, ) # Should return the existing row unchanged assert result is not None assert result.name == "empty_dict"