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
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:
@@ -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]))
|
||||
|
||||
Reference in New Issue
Block a user