from __future__ import annotations import unittest from datetime import datetime, timezone from sqlalchemy import DateTime, String, create_engine from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from govoplan_core.core.sqlalchemy_change_tracking import ( ensure_object_id, operation_for_object, operation_for_soft_deletable, previous_value, ) class Base(DeclarativeBase): pass class ExampleRecord(Base): __tablename__ = "example_records" id: Mapped[str] = mapped_column(String, primary_key=True) name: Mapped[str] = mapped_column(String) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class SqlalchemyChangeTrackingTests(unittest.TestCase): def setUp(self) -> None: self.engine = create_engine("sqlite:///:memory:") Base.metadata.create_all(self.engine) self.session = Session(self.engine, expire_on_commit=False) def tearDown(self) -> None: self.session.close() self.engine.dispose() def test_pending_object_is_created_and_can_receive_id(self) -> None: record = ExampleRecord(id="", name="Draft", deleted_at=None) self.session.add(record) self.assertEqual(ensure_object_id(record, lambda: "record-1"), "record-1") self.assertEqual(record.id, "record-1") self.assertEqual(operation_for_object(record, changed_attrs=("name",)), "created") def test_changed_object_reports_update_and_previous_value(self) -> None: record = ExampleRecord(id="record-1", name="Before", deleted_at=None) self.session.add(record) self.session.flush() record.name = "After" self.assertEqual(operation_for_object(record, changed_attrs=("name",)), "updated") self.assertEqual(previous_value(record, "name"), "Before") def test_soft_delete_and_restore_are_classified(self) -> None: deleted_at = datetime(2026, 7, 13, tzinfo=timezone.utc) record = ExampleRecord(id="record-1", name="Record", deleted_at=None) self.session.add(record) self.session.flush() record.deleted_at = deleted_at self.assertEqual(operation_for_soft_deletable(record, changed_attrs=("deleted_at",)), "deleted") self.session.flush() record.deleted_at = None self.assertEqual(operation_for_soft_deletable(record, changed_attrs=("deleted_at",)), "created") if __name__ == "__main__": unittest.main()