refactor(tests): one definition each for the copied fixtures and fakes (#2825, milestone 296 area 1)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 24s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 18s

The shape ledger showed the same test scaffolding defined over and over:
_bind_user x12 (byte-identical), _dispose_engine x10 in three wordings,
_no_supersession x3, _make_mock_session x7 in three subsets, a get-or-create
User helper x2 (+3 inlined), and fifteen hand-rolled MagicMock note factories
each re-explaining the same "an auto-MagicMock attribute is truthy" hazard
(note 2109).

Now: conftest.py carries _bind_user / _dispose_engine / _no_supersession as
opt-in fixtures (pytestmark = usefixtures(...) per module, so unit tests pay
nothing), and tests/helpers.py carries make_mock_session(), ensure_user() and
fake_note(**attrs) — the hazard documented once, real values on every
attribute the product reads. Call sites were rewritten by AST so titles with
dashes and commas survived; the three SimpleNamespace _note stand-ins that
only feed a single function stay local.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 11:03:48 -04:00
co-authored by Claude Fable 5
parent dad56a51bd
commit bbee0d0db1
38 changed files with 316 additions and 609 deletions
+12 -21
View File
@@ -8,16 +8,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from scribe.services.design_systems import DesignSystemCycle
def _make_mock_session():
s = AsyncMock()
s.__aenter__ = AsyncMock(return_value=s)
s.__aexit__ = AsyncMock(return_value=False)
s.add = MagicMock()
s.commit = AsyncMock()
s.refresh = AsyncMock()
return s
from tests.helpers import make_mock_session
# --- creating ---------------------------------------------------------------
@@ -38,7 +29,7 @@ async def test_create_with_a_parent_is_denied_without_write_on_that_parent():
async def test_create_without_a_parent_never_consults_the_parent_acl():
"""A family system has no parent, and creating one must not be gated on a
permission check for a system that does not exist."""
mock_session = _make_mock_session()
mock_session = make_mock_session()
captured = {}
mock_session.add = MagicMock(
side_effect=lambda obj: captured.update(
@@ -63,7 +54,7 @@ async def test_reparenting_into_a_loop_raises_rather_than_returning_none():
"""None already means "not found, or not yours". A caller that conflated the
two would report "no such design system" for what is really "that parent is
one of its own descendants", so the cycle gets its own exception type."""
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=MagicMock(deleted_at=None, parent_id=None))
with patch("scribe.services.design_systems.async_session") as mock_cls, \
@@ -85,7 +76,7 @@ async def test_clearing_the_parent_is_allowed_and_is_not_read_as_no_change():
value rather than "leave alone", which is why it is handled apart from the
others."""
system = MagicMock(deleted_at=None, parent_id=5)
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=system)
with patch("scribe.services.design_systems.async_session") as mock_cls, \
@@ -116,7 +107,7 @@ async def test_token_values_default_to_an_empty_map_not_json_null():
"""The column is NOT NULL so that absence has exactly ONE spelling. Passing
None straight through would store JSON null and hand every reader back the
second empty state the schema was shaped to remove."""
mock_session = _make_mock_session()
mock_session = make_mock_session()
captured = {}
mock_session.add = MagicMock(
side_effect=lambda obj: captured.update(value_by_mode=obj.value_by_mode)
@@ -159,7 +150,7 @@ async def test_pointing_a_project_needs_only_READ_on_the_system():
through another project is a legitimate choice here. Requiring write would
make a shared family style unusable by the people it was shared with."""
project = MagicMock(deleted_at=None, design_system_id=None)
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=project)
with patch("scribe.services.design_systems.async_session") as mock_cls, \
@@ -179,7 +170,7 @@ async def test_clearing_a_projects_design_system_skips_the_system_acl():
"""Un-styling a project must not require permission on the system it is
letting go of — including one that has since been deleted."""
project = MagicMock(deleted_at=None, design_system_id=3)
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=project)
with patch("scribe.services.design_systems.async_session") as mock_cls, \
@@ -219,7 +210,7 @@ async def test_resolve_scopes_the_hierarchy_to_the_systems_OWNER_not_the_caller(
"""
owner, caller = 42, 7
system = MagicMock(deleted_at=None, owner_user_id=owner)
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=system)
mock_session.execute = AsyncMock(
return_value=MagicMock(scalars=MagicMock(return_value=MagicMock(all=lambda: [])))
@@ -243,7 +234,7 @@ async def test_resolve_scopes_the_hierarchy_to_the_systems_OWNER_not_the_caller(
@pytest.mark.asyncio
async def test_create_token_supersedes_defaults_to_an_empty_list_not_json_null():
"""Same NOT NULL reasoning as value_by_mode: absence gets one spelling."""
mock_session = _make_mock_session()
mock_session = make_mock_session()
captured = {}
mock_session.add = MagicMock(
side_effect=lambda obj: captured.update(supersedes=obj.supersedes)
@@ -259,7 +250,7 @@ async def test_create_token_supersedes_defaults_to_an_empty_list_not_json_null()
@pytest.mark.asyncio
async def test_create_token_records_the_literals_it_replaces():
mock_session = _make_mock_session()
mock_session = make_mock_session()
captured = {}
mock_session.add = MagicMock(
side_effect=lambda obj: captured.update(supersedes=obj.supersedes)
@@ -348,7 +339,7 @@ async def test_design_context_merges_guidance_ANCESTOR_FIRST():
description="one app", guidance="Accent on the wordmark.")
app.title = "App"
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=app)
mock_session.execute = AsyncMock(return_value=MagicMock(
scalars=MagicMock(return_value=MagicMock(all=lambda: [app, family]))
@@ -395,7 +386,7 @@ async def test_design_context_omits_systems_with_no_guidance():
description="", guidance=" ")
app.title = "App"
mock_session = _make_mock_session()
mock_session = make_mock_session()
mock_session.get = AsyncMock(return_value=app)
mock_session.execute = AsyncMock(return_value=MagicMock(
scalars=MagicMock(return_value=MagicMock(all=lambda: [app, family]))