Skip to content

Commit d784126

Browse files
committed
test(orm): add BaseRelationship contract unit tests
Cover the abstract BaseRelationship contract independent of the concrete relationship query wiring: non-string name rejection, key/attribute defaults, __set_name__ owner recording, __get__ descriptor caching, get_builder registry resolution, __getattr__ builder delegation, joins() clause building, and NotImplementedError for all 11 abstract hooks.
1 parent b62a524 commit d784126

3 files changed

Lines changed: 182 additions & 0 deletions

File tree

fastapi_startkit/tests/masoniteorm/relationships/__init__.py

Whitespace-only changes.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"""Test doubles for the ``BaseRelationship`` contract unit tests.
2+
3+
``make_builder`` returns a chainable mock standing in for masoniteorm's async
4+
``QueryBuilder``. These tests target ``BaseRelationship``'s descriptor and key
5+
handling directly, so the builder is faked: only the join-clause and
6+
``__getattr__`` delegation tests touch it, and both assert on how
7+
``BaseRelationship`` drives the builder rather than on real query execution.
8+
Real-DB behaviour of the concrete relationships is covered by the sqlite
9+
integration suites under ``tests/masoniteorm/sqlite/relationships``.
10+
"""
11+
12+
from unittest.mock import AsyncMock, MagicMock
13+
14+
15+
CHAINABLE = [
16+
"select",
17+
"add_select",
18+
"join",
19+
"table",
20+
"where",
21+
"where_in",
22+
"where_column",
23+
"run_scopes",
24+
"without_global_scopes",
25+
"when",
26+
"new",
27+
"with_",
28+
]
29+
30+
31+
def make_builder(table_name="others", get_result=None, columns=None, connection="sqlite"):
32+
"""A chainable query-builder mock.
33+
34+
Every query-shaping method returns the same mock so call chains resolve,
35+
while ``get`` is awaitable and ``get_table_name`` is fixed.
36+
"""
37+
builder = MagicMock(name=f"builder<{table_name}>")
38+
builder.get_table_name.return_value = table_name
39+
builder._columns = columns
40+
builder.connection = connection
41+
builder.connection_name = connection
42+
for method in CHAINABLE:
43+
getattr(builder, method).return_value = builder
44+
builder.get = AsyncMock(return_value=[] if get_result is None else get_result)
45+
return builder
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""Unit tests for the abstract ``BaseRelationship`` contract.
2+
3+
These assert ``BaseRelationship``'s key handling, descriptor (``__get__`` /
4+
``__set_name__``) behaviour, builder delegation, join-clause construction, and
5+
that every abstract hook raises ``NotImplementedError`` on the base class. They
6+
exercise the descriptor/dunder machinery directly, independent of any concrete
7+
relationship's query wiring; the join-clause and delegation tests use the
8+
chainable builder double from ``conftest.make_builder``.
9+
"""
10+
11+
from unittest.mock import MagicMock
12+
13+
import pytest
14+
15+
from fastapi_startkit.masoniteorm.models import registry
16+
from fastapi_startkit.masoniteorm.relationships.BaseRelationship import BaseRelationship
17+
18+
from .conftest import make_builder
19+
20+
21+
def test_rejects_non_string_relationship_name():
22+
with pytest.raises(TypeError, match="expects a string"):
23+
BaseRelationship(["not", "a", "string"])
24+
25+
26+
def test_stores_keys_and_defaults():
27+
rel = BaseRelationship("Profile", local_key="id", foreign_key="user_id")
28+
29+
assert rel.local_key == "id"
30+
assert rel.foreign_key == "user_id"
31+
assert rel.attribute is None
32+
33+
34+
def test_set_name_records_attribute_and_owner():
35+
rel = BaseRelationship("Profile")
36+
37+
class Owner:
38+
pass
39+
40+
rel.__set_name__(Owner, "profile")
41+
42+
assert rel.attribute == "profile"
43+
assert rel.owner_class is Owner
44+
45+
46+
def test_get_builder_resolves_through_registry(monkeypatch):
47+
related_builder = object()
48+
related_instance = MagicMock()
49+
related_instance.get_builder.return_value = related_builder
50+
related_model = MagicMock(return_value=related_instance)
51+
52+
monkeypatch.setattr(registry.Registry, "resolve", classmethod(lambda cls, name: related_model))
53+
54+
rel = BaseRelationship("Profile")
55+
56+
assert rel.get_builder() is related_builder
57+
58+
59+
def test_get_returns_self_when_accessed_on_class():
60+
rel = BaseRelationship("Profile")
61+
62+
assert rel.__get__(None, object) is rel
63+
64+
65+
def test_get_returns_self_when_owner_not_loaded():
66+
rel = BaseRelationship("Profile")
67+
rel.attribute = "profile"
68+
rel.set_keys = MagicMock()
69+
70+
instance = MagicMock()
71+
instance.is_loaded.return_value = False
72+
73+
assert rel.__get__(instance, object) is rel
74+
rel.set_keys.assert_called_once_with(instance, "profile")
75+
76+
77+
def test_get_returns_cached_relationship():
78+
rel = BaseRelationship("Profile")
79+
rel.attribute = "profile"
80+
rel.set_keys = MagicMock()
81+
82+
instance = MagicMock()
83+
instance.is_loaded.return_value = True
84+
instance._relationships = {"profile": "cached"}
85+
86+
assert rel.__get__(instance, object) == "cached"
87+
88+
89+
def test_getattr_delegates_to_builder():
90+
builder = make_builder()
91+
builder.custom_attr = "delegated"
92+
93+
rel = BaseRelationship("Profile")
94+
rel.get_builder = MagicMock(return_value=builder)
95+
96+
assert rel.custom_attr == "delegated"
97+
98+
99+
def test_joins_builds_join_clause():
100+
other = make_builder(table_name="profiles")
101+
local = make_builder(table_name="users")
102+
103+
rel = BaseRelationship("Profile", local_key="id", foreign_key="user_id")
104+
rel.get_builder = MagicMock(return_value=other)
105+
106+
rel.joins(local, clause="left")
107+
108+
local.join.assert_called_once_with(
109+
"profiles",
110+
"users.id",
111+
"=",
112+
"profiles.user_id",
113+
clause="left",
114+
)
115+
116+
117+
@pytest.mark.parametrize(
118+
"method,args",
119+
[
120+
("apply_query", (MagicMock(), MagicMock())),
121+
("query_where_exists", (MagicMock(), MagicMock())),
122+
("get_with_count_query", (MagicMock(), MagicMock())),
123+
("attach", (MagicMock(), MagicMock())),
124+
("get_related", (MagicMock(), MagicMock())),
125+
("relate", (MagicMock(),)),
126+
("detach", (MagicMock(), MagicMock())),
127+
("attach_related", (MagicMock(), MagicMock())),
128+
("detach_related", (MagicMock(), MagicMock())),
129+
("query_has", (MagicMock(),)),
130+
("map_related", (MagicMock(),)),
131+
],
132+
)
133+
def test_abstract_methods_raise_not_implemented(method, args):
134+
rel = BaseRelationship("Profile")
135+
136+
with pytest.raises(NotImplementedError):
137+
getattr(rel, method)(*args)

0 commit comments

Comments
 (0)