Skip to content

Commit 3ad0b92

Browse files
committed
test: add repository integration tests for SQLModel to improve patch coverage
Add tests for update_many, upsert, and upsert fallback-match-by-all-fields using an in-memory SQLite database with SQLModel table models.
1 parent d3410b6 commit 3ad0b92

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

tests/unit/test_sqlmodel_compat.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
Validates that SQLModel table=True models can be used with AA repositories and services.
44
"""
55

6+
from collections.abc import Generator
67
from typing import Any, Optional, cast
78
from unittest.mock import MagicMock
89

910
import pytest
11+
from sqlalchemy import create_engine
12+
from sqlalchemy.orm import Session, sessionmaker
1013

1114
sqlmodel = pytest.importorskip("sqlmodel")
1215

1316
from sqlmodel import Field as SQLModelField # noqa: E402
1417
from sqlmodel import SQLModel # noqa: E402
1518

1619
from advanced_alchemy.base import ModelProtocol, model_to_dict # noqa: E402
20+
from advanced_alchemy.repository import SQLAlchemySyncRepository # noqa: E402
1721
from advanced_alchemy.repository._util import get_instrumented_attr, get_primary_key_info, model_from_dict # noqa: E402
1822
from advanced_alchemy.service.typing import ( # noqa: E402
1923
is_pydantic_model,
@@ -284,3 +288,73 @@ def test_model_to_dict_roundtrip_via_model_from_dict() -> None:
284288
assert rebuilt.name == hero.name
285289
assert rebuilt.secret_name == hero.secret_name
286290
assert rebuilt.age == hero.age
291+
292+
293+
# ---------------------------------------------------------------------------
294+
# Chapter 4: Repository integration with SQLModel (in-memory SQLite)
295+
# ---------------------------------------------------------------------------
296+
297+
298+
class HeroRepository(SQLAlchemySyncRepository[HeroModel]):
299+
"""Repository for HeroModel."""
300+
301+
model_type = HeroModel
302+
303+
304+
@pytest.fixture()
305+
def hero_session() -> "Generator[Session, None, None]":
306+
"""Create an in-memory SQLite session with the HeroModel table."""
307+
engine = create_engine("sqlite:///:memory:")
308+
SQLModel.metadata.create_all(engine)
309+
session_factory = sessionmaker(engine, expire_on_commit=False)
310+
with session_factory() as session:
311+
yield session # type: ignore[misc]
312+
313+
314+
def test_repo_update_many_with_sqlmodel(hero_session: "Session") -> None:
315+
"""Repository.update_many should handle SQLModel model instances via model_to_dict."""
316+
repo = HeroRepository(session=hero_session)
317+
hero = repo.add(HeroModel(name="Spider-Boy", secret_name="Pedro", age=10))
318+
hero_session.commit()
319+
320+
hero.age = 20
321+
updated = repo.update_many([hero])
322+
hero_session.commit()
323+
assert updated[0].age == 20
324+
325+
326+
def test_repo_upsert_creates_with_sqlmodel(hero_session: "Session") -> None:
327+
"""Repository.upsert should create a new SQLModel instance when not found."""
328+
repo = HeroRepository(session=hero_session)
329+
hero = HeroModel(name="Spider-Boy", secret_name="Pedro", age=10)
330+
result = repo.upsert(hero)
331+
hero_session.commit()
332+
assert result.name == "Spider-Boy"
333+
assert result.id is not None
334+
335+
336+
def test_repo_upsert_updates_with_sqlmodel(hero_session: "Session") -> None:
337+
"""Repository.upsert should update existing SQLModel instance when found by match_fields."""
338+
repo = HeroRepository(session=hero_session)
339+
existing = repo.add(HeroModel(name="Spider-Boy", secret_name="Pedro", age=10))
340+
hero_session.commit()
341+
342+
updated_hero = HeroModel(name="Spider-Boy", secret_name="Pedro P", age=15)
343+
result = repo.upsert(updated_hero, match_fields=["name"])
344+
hero_session.commit()
345+
assert result.id == existing.id
346+
assert result.secret_name == "Pedro P"
347+
348+
349+
def test_repo_upsert_fallback_match_by_all_fields(hero_session: "Session") -> None:
350+
"""Repository.upsert should match by all non-PK fields when no id and no match_fields."""
351+
repo = HeroRepository(session=hero_session)
352+
repo.add(HeroModel(name="Spider-Boy", secret_name="Pedro", age=10))
353+
hero_session.commit()
354+
355+
# No id set, no match_fields — triggers model_to_dict(data, exclude=exclude_cols) fallback
356+
lookup = HeroModel(name="Spider-Boy", secret_name="Pedro", age=10)
357+
result = repo.upsert(lookup)
358+
hero_session.commit()
359+
assert result.name == "Spider-Boy"
360+
assert result.id is not None

0 commit comments

Comments
 (0)