Skip to content

Commit 2f728d8

Browse files
refactor(api): remove test-only model re-export from workspaces (#118)
* refactor(api): remove test-only model re-export from workspaces * chore(test): remove unused logging import in wired-at-read-sites test
1 parent c97a37c commit 2f728d8

2 files changed

Lines changed: 23 additions & 24 deletions

File tree

api/workspaces.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@
2424
warn_workspace_json_read,
2525
)
2626
from utils.workspace_descriptor import read_json_file
27-
from services.workspace_resolver import (
28-
infer_workspace_name_from_context,
29-
lookup_workspace_display_name,
30-
)
27+
from services.workspace_resolver import infer_workspace_name_from_context
3128
from services.cli_tabs import get_cli_workspace_tabs
3229
from services.workspace_listing import list_workspace_projects
3330
from services.workspace_tabs import (
@@ -36,13 +33,6 @@
3633
list_workspace_tab_summaries,
3734
)
3835

39-
# Re-exported for tests/test_models_wired_at_read_sites.py — the typed-model
40-
# spy harness patches `workspaces_mod.Bubble` / `.Composer` / `.Workspace` to
41-
# verify that production read paths actually call from_dict. The classes
42-
# themselves are wired inside the services modules now (post-#25 split);
43-
# importing them here keeps the spy resolution stable.
44-
from models import Bubble, Composer, Workspace # noqa: F401
45-
4636
bp = Blueprint("workspaces", __name__)
4737
_logger = logging.getLogger(__name__)
4838

tests/test_models_wired_at_read_sites.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,14 @@ def test_search_endpoint_calls_bubble_from_dict(self):
117117

118118
def test_workspace_tabs_endpoint_calls_bubble_from_dict(self):
119119
from app import create_app
120-
import api.workspaces as workspaces_mod
120+
import services.workspace_db as workspace_db_mod
121+
from models import Bubble
121122
app = create_app()
122123
app.config["TESTING"] = True
123124
app.config["EXCLUSION_RULES"] = []
124-
with patch.object(workspaces_mod.Bubble, "from_dict", wraps=workspaces_mod.Bubble.from_dict) as spy:
125+
with patch.object(
126+
workspace_db_mod.Bubble, "from_dict", wraps=Bubble.from_dict
127+
) as spy:
125128
client = app.test_client()
126129
response = client.get(f"/api/workspaces/{WORKSPACE_ID}/tabs")
127130
self.assertEqual(response.status_code, 200)
@@ -136,8 +139,6 @@ def test_bubble_schema_drift_is_logged_not_swallowed_silently(self):
136139
# ValueError and skipped silently. Schema drift must now log a
137140
# `Schema drift in bubble <bid>` line so disappearing bubbles can be
138141
# traced. The well-formed row still loads alongside.
139-
import logging
140-
141142
from app import create_app
142143
# Seed a deliberately-malformed bubble row that will trip
143144
# Bubble.from_dict's "expected non-empty str" gate on the bubble_id by
@@ -163,17 +164,20 @@ def test_bubble_schema_drift_is_logged_not_swallowed_silently(self):
163164
msg="drift log must include the offending bubble id")
164165

165166
def test_workspace_tabs_endpoint_calls_composer_from_dict(self):
166-
# Brad's most-important finding: list_workspaces() at api/workspaces.py:605
167-
# validates each composer with Composer.from_dict, but get_workspace_tabs()
168-
# in the same file used raw json.loads. Schema drift would have been hidden
169-
# from one of the two primary conversation-browsing paths. This test pins
170-
# that BOTH paths must now validate via the model.
167+
# Brad's most-important finding: list_workspaces() validates each composer
168+
# with Composer.from_dict, but get_workspace_tabs() used raw json.loads.
169+
# Schema drift would have been hidden from one of the two primary
170+
# conversation-browsing paths. This test pins that BOTH paths must now
171+
# validate via the model.
171172
from app import create_app
172-
import api.workspaces as workspaces_mod
173+
import services.workspace_tabs as workspace_tabs_mod
174+
from models import Composer
173175
app = create_app()
174176
app.config["TESTING"] = True
175177
app.config["EXCLUSION_RULES"] = []
176-
with patch.object(workspaces_mod.Composer, "from_dict", wraps=workspaces_mod.Composer.from_dict) as spy:
178+
with patch.object(
179+
workspace_tabs_mod.Composer, "from_dict", wraps=Composer.from_dict
180+
) as spy:
177181
client = app.test_client()
178182
response = client.get(f"/api/workspaces/{WORKSPACE_ID}/tabs")
179183
self.assertEqual(response.status_code, 200)
@@ -220,8 +224,13 @@ def test_composers_endpoint_calls_workspace_from_dict(self):
220224

221225
def test_workspace_display_name_calls_workspace_from_dict(self):
222226
from services.workspace_resolver import lookup_workspace_display_name
223-
import api.workspaces as workspaces_mod
224-
with patch.object(workspaces_mod.Workspace, "from_dict", wraps=workspaces_mod.Workspace.from_dict) as spy:
227+
import services.workspace_resolver as workspace_resolver_mod
228+
from models import Workspace
229+
with patch.object(
230+
workspace_resolver_mod.Workspace,
231+
"from_dict",
232+
wraps=Workspace.from_dict,
233+
) as spy:
225234
name = lookup_workspace_display_name(self.workspace_path, WORKSPACE_ID)
226235
self.assertIsInstance(name, str)
227236
self.assertGreaterEqual(

0 commit comments

Comments
 (0)