From b0fdaaca515cdb6b687a32bc2b574a41505936af Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Thu, 9 Jul 2026 11:25:17 -0700 Subject: [PATCH 1/3] Add tests for masoniteorm Collection.load() and raise coverage to 100% Cover the Collection subclass's post-query eager loading (load) and with_relationship_autoloading no-op, which previously had ~28% coverage: - has-one and has-many relations registered after load() - loading multiple relations in one call - empty-collection short-circuit and no-matching-records path - non-Collection get_related result routed through add_relation --- .../masoniteorm/collection/test_collection.py | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/fastapi_startkit/tests/masoniteorm/collection/test_collection.py b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py index e44a4841..8c80101e 100644 --- a/fastapi_startkit/tests/masoniteorm/collection/test_collection.py +++ b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py @@ -1,7 +1,9 @@ +from unittest import IsolatedAsyncioTestCase + from fastapi_startkit.masoniteorm.collection import Collection from fastapi_startkit.masoniteorm.models.model import Model -from ..fixtures.model import User +from ..fixtures.model import Articles, Profile, User from ..sqlite.test_case import TestCase @@ -17,6 +19,54 @@ async def test_serialize_with_on_the_fly_appends(self): self.assertTrue(isinstance(serialized, list)) self.assertTrue(len(serialized) > 0) + async def test_load_has_one_relationship(self): + users = await User.all() + + result = await users.load("profile") + + self.assertIs(result, users) + admin = users.where("email", "admin@admin.com").first() + self.assertIn("profile", admin._relationships) + self.assertIsInstance(admin.profile, Profile) + + async def test_load_has_many_relationship(self): + users = await User.all() + + result = await users.load("articles") + + self.assertIs(result, users) + admin = users.where("email", "admin@admin.com").first() + self.assertIn("articles", admin._relationships) + self.assertEqual(len(admin.articles), 1) + self.assertIsInstance(admin.articles[0], Articles) + + async def test_load_multiple_relationships_at_once(self): + users = await User.all() + + await users.load("profile", "articles") + + admin = users.where("email", "admin@admin.com").first() + self.assertIn("profile", admin._relationships) + self.assertIn("articles", admin._relationships) + + async def test_load_without_matching_related_records(self): + # Jane (id 2) has no articles, so get_related returns an empty + # collection and no relation is registered. + users = await User.where("id", 2).get() + + result = await users.load("articles") + + self.assertIs(result, users) + self.assertNotIn("articles", users.first()._relationships) + + async def test_load_on_empty_collection_returns_self(self): + users = await User.where("id", 9999).get() + self.assertTrue(users.is_empty()) + + result = await users.load("profile") + + self.assertIs(result, users) + def test_take(self): collection = Collection([1, 2, 3, 4]) self.assertEqual(collection.take(2), [1, 2]) @@ -694,3 +744,43 @@ def test_eq(self): self.assertTrue(collection == other) different = Collection([1, 2, 3]) self.assertFalse(collection == different) + + +class _StubRelationship: + """Relationship whose get_related returns a non-Collection result.""" + + async def get_related(self, query, relation): + return {"id": 99} + + def map_related(self, related_result): + return related_result + + +class _StubModel: + stub = _StubRelationship() + + def __init__(self): + self.relations = {} + + def add_relation(self, data): + self.relations.update(data) + + +class TestCollectionLoad(IsolatedAsyncioTestCase): + """Covers the load() paths that don't require a database.""" + + async def test_load_on_empty_collection_is_noop(self): + collection = Collection([]) + self.assertIs(await collection.load("stub"), collection) + + async def test_load_registers_non_collection_result_via_add_relation(self): + collection = Collection([_StubModel(), _StubModel()]) + + result = await collection.load("stub") + + self.assertIs(result, collection) + for model in collection: + self.assertEqual(model.relations["stub"], {"id": 99}) + + def test_with_relationship_autoloading_is_noop(self): + self.assertIsNone(Collection([]).with_relationship_autoloading()) From 7591518f4f625f97b7d21bbdf1d519bceaf6046f Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Thu, 9 Jul 2026 11:56:36 -0700 Subject: [PATCH 2/3] Drive Collection.load() non-Collection branch with real Model/HasOne Replace the hand-rolled _StubModel/_StubRelationship with a real Model subclass and a real HasOne whose get_related is overridden to return a single row. This keeps map_related, add_relation, and _relationships real, and documents why the non-Collection branch is unreachable by any real relationship driven through load() (get_related always returns a Collection when handed the whole Collection). --- .../masoniteorm/collection/test_collection.py | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/fastapi_startkit/tests/masoniteorm/collection/test_collection.py b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py index 8c80101e..5e8284f2 100644 --- a/fastapi_startkit/tests/masoniteorm/collection/test_collection.py +++ b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py @@ -2,6 +2,7 @@ from fastapi_startkit.masoniteorm.collection import Collection from fastapi_startkit.masoniteorm.models.model import Model +from fastapi_startkit.masoniteorm.relationships import HasOne from ..fixtures.model import Articles, Profile, User from ..sqlite.test_case import TestCase @@ -746,41 +747,53 @@ def test_eq(self): self.assertFalse(collection == different) -class _StubRelationship: - """Relationship whose get_related returns a non-Collection result.""" +class _SingleResultHasOne(HasOne): + """A real HasOne whose get_related is forced to return a single (non-Collection) row. - async def get_related(self, query, relation): - return {"id": 99} + Collection.load() always hands the *whole* Collection to get_related, and every + real relationship branches on ``isinstance(relation, Collection)`` to return a + Collection in that case (see get_related in HasOne, HasMany, BelongsTo, the Morph* + and *Through relationships). So the non-Collection branch of load() — the + ``model.add_relation(...)`` fallback — is unreachable by any real relationship + driven through load(); it is defensive code. - def map_related(self, related_result): - return related_result + Overriding only get_related is therefore the single narrowest way to exercise that + branch while keeping the rest of the path real: a real Model, the real HasOne + map_related, and the real Model.add_relation / _relationships all run unchanged. + """ + async def get_related(self, query, relation, eagers=(), callback=None): + return {"id": 99} -class _StubModel: - stub = _StubRelationship() - def __init__(self): - self.relations = {} +class _Widget(Model): + __table__ = "widgets" + __timestamps__ = False - def add_relation(self, data): - self.relations.update(data) + gadget: "_Widget" = _SingleResultHasOne("Widget", "widget_id", "id") class TestCollectionLoad(IsolatedAsyncioTestCase): - """Covers the load() paths that don't require a database.""" + """Covers the load() branches that a live database cannot reach. + + Genuine relationship loading against real HasOne/HasMany relationships and a live + SQLite database is covered by TestCollection.test_load_* above. These tests cover + the empty-collection short-circuit and the defensive non-Collection fallback, which + no DB-backed relationship produces (see _SingleResultHasOne for why). + """ async def test_load_on_empty_collection_is_noop(self): collection = Collection([]) - self.assertIs(await collection.load("stub"), collection) + self.assertIs(await collection.load("gadget"), collection) async def test_load_registers_non_collection_result_via_add_relation(self): - collection = Collection([_StubModel(), _StubModel()]) + collection = Collection([_Widget(), _Widget()]) - result = await collection.load("stub") + result = await collection.load("gadget") self.assertIs(result, collection) for model in collection: - self.assertEqual(model.relations["stub"], {"id": 99}) + self.assertEqual(model._relationships["gadget"], {"id": 99}) def test_with_relationship_autoloading_is_noop(self): self.assertIsNone(Collection([]).with_relationship_autoloading()) From 2ae0d55c2529828690413e0f850623cc4dc4de67 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Thu, 9 Jul 2026 12:13:59 -0700 Subject: [PATCH 3/3] Replace stub-based Collection.load() tests with real sqlite relationships The load() tests now exercise real HasOne/HasMany relationships against the sqlite test harness (User has-one Profile, User has-many Articles) end-to-end, asserting related records attach via load(). Removes the _Widget / _SingleResultHasOne stubs and the duplicated empty-collection test. The non-Collection else branch of load() is unreachable by real relationships (get_related always returns a Collection), so it is now uncovered by design rather than driven by a synthetic stub. --- .../masoniteorm/collection/test_collection.py | 58 +------------------ 1 file changed, 3 insertions(+), 55 deletions(-) diff --git a/fastapi_startkit/tests/masoniteorm/collection/test_collection.py b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py index 5e8284f2..873b4837 100644 --- a/fastapi_startkit/tests/masoniteorm/collection/test_collection.py +++ b/fastapi_startkit/tests/masoniteorm/collection/test_collection.py @@ -1,8 +1,5 @@ -from unittest import IsolatedAsyncioTestCase - from fastapi_startkit.masoniteorm.collection import Collection from fastapi_startkit.masoniteorm.models.model import Model -from fastapi_startkit.masoniteorm.relationships import HasOne from ..fixtures.model import Articles, Profile, User from ..sqlite.test_case import TestCase @@ -68,6 +65,9 @@ async def test_load_on_empty_collection_returns_self(self): self.assertIs(result, users) + def test_with_relationship_autoloading_is_noop(self): + self.assertIsNone(Collection([]).with_relationship_autoloading()) + def test_take(self): collection = Collection([1, 2, 3, 4]) self.assertEqual(collection.take(2), [1, 2]) @@ -745,55 +745,3 @@ def test_eq(self): self.assertTrue(collection == other) different = Collection([1, 2, 3]) self.assertFalse(collection == different) - - -class _SingleResultHasOne(HasOne): - """A real HasOne whose get_related is forced to return a single (non-Collection) row. - - Collection.load() always hands the *whole* Collection to get_related, and every - real relationship branches on ``isinstance(relation, Collection)`` to return a - Collection in that case (see get_related in HasOne, HasMany, BelongsTo, the Morph* - and *Through relationships). So the non-Collection branch of load() — the - ``model.add_relation(...)`` fallback — is unreachable by any real relationship - driven through load(); it is defensive code. - - Overriding only get_related is therefore the single narrowest way to exercise that - branch while keeping the rest of the path real: a real Model, the real HasOne - map_related, and the real Model.add_relation / _relationships all run unchanged. - """ - - async def get_related(self, query, relation, eagers=(), callback=None): - return {"id": 99} - - -class _Widget(Model): - __table__ = "widgets" - __timestamps__ = False - - gadget: "_Widget" = _SingleResultHasOne("Widget", "widget_id", "id") - - -class TestCollectionLoad(IsolatedAsyncioTestCase): - """Covers the load() branches that a live database cannot reach. - - Genuine relationship loading against real HasOne/HasMany relationships and a live - SQLite database is covered by TestCollection.test_load_* above. These tests cover - the empty-collection short-circuit and the defensive non-Collection fallback, which - no DB-backed relationship produces (see _SingleResultHasOne for why). - """ - - async def test_load_on_empty_collection_is_noop(self): - collection = Collection([]) - self.assertIs(await collection.load("gadget"), collection) - - async def test_load_registers_non_collection_result_via_add_relation(self): - collection = Collection([_Widget(), _Widget()]) - - result = await collection.load("gadget") - - self.assertIs(result, collection) - for model in collection: - self.assertEqual(model._relationships["gadget"], {"id": 99}) - - def test_with_relationship_autoloading_is_noop(self): - self.assertIsNone(Collection([]).with_relationship_autoloading())