"""Tests for UpdateMixin.""" import pytest from abacus_models.core import constants from abacus_models.core.contexts import ctx_user_id, set_user_context from abacus_models.core.mixins.update_mixin import ( disable_on_update, enable_on_update, ) from abacus_models.core.utils import current_timestamp from tests.conftest import TestModelFull, TestModelUpdateOnly class TestUpdateMixinAutomatic: """Tests for automatic update field tracking.""" def test_update_sets_last_modified(self, session, user_context, sample_full_model): """Test updating a model automatically sets last_modified.""" original_modified = sample_full_model.last_modified before = current_timestamp() sample_full_model.name = 'Updated Name' session.flush() after = current_timestamp() assert sample_full_model.last_modified is not None assert ( before <= sample_full_model.last_modified <= after or sample_full_model.last_modified == before ) assert sample_full_model.last_modified != original_modified def test_update_sets_last_modified_by( self, session, user_context, sample_full_model ): """Test updating a model automatically sets last_modified_by.""" sample_full_model.name = 'Updated Name' session.flush() assert sample_full_model.last_modified_by == 'test_user_123' def test_update_sets_both_fields(self, session, user_context, sample_full_model): """Test updating a model sets both last_modified fields.""" sample_full_model.name = 'Updated Name' session.flush() assert sample_full_model.last_modified is not None assert sample_full_model.last_modified_by == 'test_user_123' def test_update_multiple_times(self, session, user_context, sample_full_model): """Test multiple updates continue to update last_modified.""" sample_full_model.name = 'Update 1' session.flush() time1 = sample_full_model.last_modified sample_full_model.name = 'Update 2' session.flush() time2 = sample_full_model.last_modified assert time2 >= time1 def test_update_with_different_users(self, session, sample_full_model): """Test update tracking with different users.""" with set_user_context('user_1'): sample_full_model.name = 'Update 1' session.flush() user1_modified_by = sample_full_model.last_modified_by with set_user_context('user_2'): sample_full_model.name = 'Update 2' session.flush() user2_modified_by = sample_full_model.last_modified_by assert user1_modified_by == 'user_1' assert user2_modified_by == 'user_2' def test_update_only_model(self, session, user_context): """Test UpdateMixin works with model having only update fields.""" model = TestModelUpdateOnly(name='Test') session.add(model) session.flush() model.name = 'Updated' session.flush() assert model.last_modified is not None assert model.last_modified_by == 'test_user_123' def test_create_triggers_update(self, session, user_context): """Test creating a model also sets last_modified fields.""" model = TestModelFull(name='Test') session.add(model) session.flush() # last_modified fields should be set on create (same as created_at) assert model.last_modified is not None assert model.last_modified_by == user_context assert model.created_at is not None assert model.created_by == user_context class TestUpdateMixinTouch: """Tests for the touch() method.""" def test_touch_updates_last_modified( self, session, user_context, sample_full_model ): """Test touch() updates last_modified field.""" original_modified = sample_full_model.last_modified before = current_timestamp() sample_full_model.touch() after = current_timestamp() assert sample_full_model.last_modified is not None assert ( before <= sample_full_model.last_modified <= after or sample_full_model.last_modified == before ) assert sample_full_model.last_modified != original_modified def test_touch_updates_last_modified_by( self, session, user_context, sample_full_model ): """Test touch() updates last_modified_by field.""" sample_full_model.touch() assert sample_full_model.last_modified_by == 'test_user_123' def test_touch_without_actual_changes( self, session, user_context, sample_full_model ): """Test touch() updates fields even without other changes.""" original_name = sample_full_model.name original_modified = sample_full_model.last_modified sample_full_model.touch() assert sample_full_model.name == original_name assert sample_full_model.last_modified != original_modified def test_touch_with_different_users(self, session, sample_full_model): """Test touch() respects current user context.""" with set_user_context('user_1'): sample_full_model.touch() user1_by = sample_full_model.last_modified_by with set_user_context('user_2'): sample_full_model.touch() user2_by = sample_full_model.last_modified_by assert user1_by == 'user_1' assert user2_by == 'user_2' class TestUpdateMixinEnableDisable: """Tests for enable/disable functionality.""" def test_disable_on_update_prevents_auto_tracking( self, session, user_context, sample_full_model ): """Test disable_on_update prevents automatic field population.""" original_modified = sample_full_model.last_modified disable_on_update() try: sample_full_model.name = 'Updated' session.flush() # Fields should not be updated automatically assert sample_full_model.last_modified == original_modified finally: # Re-enable for other tests enable_on_update() def test_enable_on_update_restores_auto_tracking( self, session, user_context, sample_full_model ): """Test enable_on_update restores automatic field population.""" try: disable_on_update() enable_on_update() before = current_timestamp() sample_full_model.name = 'Updated' session.flush() after = current_timestamp() # Fields should be updated automatically again assert sample_full_model.last_modified is not None assert ( before <= sample_full_model.last_modified <= after or sample_full_model.last_modified == before ) finally: # Ensure enabled for other tests enable_on_update() def test_disable_enable_cycle(self, session, user_context): """Test multiple disable/enable cycles.""" try: # Create model model = TestModelFull(name='Test') session.add(model) session.flush() # First update - should work model.name = 'Update 1' session.flush() assert model.last_modified is not None time1 = model.last_modified # Disable disable_on_update() model.name = 'Update 2' session.flush() assert model.last_modified == time1 # Should not change # Re-enable enable_on_update() model.name = 'Update 3' session.flush() assert model.last_modified > time1 # Should change again finally: # Ensure enabled for other tests enable_on_update() class TestUpdateMixinEdgeCases: """Tests for edge cases and error handling.""" def test_update_without_user_context(self, session): """Test updating a model without user context.""" # Create model with user context first with set_user_context('creator'): model = TestModelFull(name='Test') session.add(model) session.flush() # Ensure strict validation is enabled original_strict = constants.STRICT_CONTEXT_VALIDATION constants.STRICT_CONTEXT_VALIDATION = True try: ctx_user_id.set(None) with pytest.raises(ValueError, match='context user_id is not set'): model.name = 'Updated' session.flush() finally: constants.STRICT_CONTEXT_VALIDATION = original_strict def test_update_without_user_context_non_strict(self, session): """Test updating with non-strict context allows None user.""" # Create model with user context first with set_user_context('creator'): model = TestModelFull(name='Test') session.add(model) session.commit() session.refresh(model) assert model.last_modified is not None assert model.last_modified_by is not None # Temporarily disable strict validation original_strict = constants.STRICT_CONTEXT_VALIDATION try: # Clear context AFTER setting non-strict constants.STRICT_CONTEXT_VALIDATION = False original_modified = model.last_modified with set_user_context(None): model.name = 'Updated' session.commit() session.refresh(model) # last_modified should be updated, last_modified_by should be None assert model.last_modified is not None assert model.last_modified != original_modified assert model.last_modified_by is None finally: constants.STRICT_CONTEXT_VALIDATION = original_strict def test_touch_without_user_context(self, session): """Test touch() without user context.""" # Create model with user context first with set_user_context('creator'): model = TestModelFull(name='Test') session.add(model) session.flush() ctx_user_id.set(None) with pytest.raises(ValueError, match='context user_id is not set'): model.touch() def test_update_does_not_affect_created_fields( self, session, user_context, sample_full_model ): """Test updating does not change created_at or created_by.""" original_created_at = sample_full_model.created_at original_created_by = sample_full_model.created_by with set_user_context('different_user'): sample_full_model.name = 'Updated' session.flush() # created fields should not change assert sample_full_model.created_at == original_created_at assert sample_full_model.created_by == original_created_by