Skip to content

Commit 25a6385

Browse files
follow up optional PRs
1 parent 28f8466 commit 25a6385

3 files changed

Lines changed: 78 additions & 6 deletions

File tree

models/raw_access.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,36 +271,40 @@ def composer_code_block_data(
271271
def bubble_relevant_files(
272272
bubble: Bubble | dict[str, Any], bubble_id: str = ""
273273
) -> list[str]:
274-
from models.conversation import Bubble
274+
from models.conversation import Bubble, _filter_str_list_elements
275275

276276
if isinstance(bubble, Bubble):
277277
return bubble.relevant_files
278-
return cast(
279-
list[str],
278+
return _filter_str_list_elements(
280279
_optional_list_absent_ok(
281280
bubble,
282281
"relevantFiles",
283282
model="Bubble",
284283
entity_id=bubble_id,
285284
),
285+
model="Bubble",
286+
record_id=bubble_id,
287+
field="relevantFiles",
286288
)
287289

288290

289291
def bubble_attached_file_uris(
290292
bubble: Bubble | dict[str, Any], bubble_id: str = ""
291293
) -> list[FileUriDict]:
292-
from models.conversation import Bubble
294+
from models.conversation import Bubble, _filter_dict_list_elements
293295

294296
if isinstance(bubble, Bubble):
295297
return bubble.attached_file_code_chunks_uris
296-
return cast(
297-
list[FileUriDict],
298+
return _filter_dict_list_elements(
298299
_optional_list_absent_ok(
299300
bubble,
300301
"attachedFileCodeChunksUris",
301302
model="Bubble",
302303
entity_id=bubble_id,
303304
),
305+
model="Bubble",
306+
record_id=bubble_id,
307+
field="attachedFileCodeChunksUris",
304308
)
305309

306310

tests/test_models_wired_at_read_sites.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,38 @@ def swapped_from_dict(raw): # type: ignore[no-redef]
292292
"is back to being just a filter.",
293293
)
294294

295+
def test_list_composers_response_fields_do_not_mutate_storage_raw(self):
296+
from app import create_app
297+
from models import WorkspaceLocalComposer
298+
import api.composers as composers_mod
299+
300+
captured: list[WorkspaceLocalComposer] = []
301+
original_from_dict = WorkspaceLocalComposer.from_dict
302+
303+
def capturing_from_dict(raw): # type: ignore[no-untyped-def]
304+
local = original_from_dict(raw)
305+
captured.append(local)
306+
return local
307+
308+
app = create_app()
309+
app.config["TESTING"] = True
310+
app.config["EXCLUSION_RULES"] = []
311+
with patch.object(
312+
composers_mod.WorkspaceLocalComposer,
313+
"from_dict",
314+
side_effect=capturing_from_dict,
315+
):
316+
client = app.test_client()
317+
response = client.get("/api/composers")
318+
self.assertEqual(response.status_code, 200)
319+
rows = response.get_json()
320+
row = next(c for c in rows if c["composerId"] == COMPOSER_ID)
321+
self.assertEqual(row["workspaceId"], WORKSPACE_ID)
322+
323+
local = next(c for c in captured if c.composer_id == COMPOSER_ID)
324+
self.assertNotIn("workspaceId", local._raw)
325+
self.assertNotIn("workspaceFolder", local._raw)
326+
295327

296328
class TestGetComposerValidatesSchema(unittest.TestCase):
297329
# Brad's follow-up finding: list_composers() validates each row via

tests/test_raw_accessors.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from models.conversation import Bubble, Composer
1616
from utils.display_bubble import bubble_display_timestamp_ms
1717
from models.raw_access import (
18+
bubble_attached_file_uris,
19+
bubble_relevant_files,
1820
composer_newly_created_files,
1921
conversation_header_bubble_id,
2022
message_request_context_project_layouts,
@@ -163,6 +165,40 @@ def test_dict_bridge_newly_created_files_matches_composer_property(self) -> None
163165
composer_newly_created_files(composer, "cid-bridge"),
164166
)
165167

168+
def test_dict_bridge_relevant_files_skips_non_str_elements(self) -> None:
169+
raw = {"relevantFiles": ["/good.py", 123, "/also.py"]}
170+
bubble = Bubble.from_dict(raw, bubble_id="b-bridge")
171+
with self.assertLogs("models.conversation", level="WARNING") as logs:
172+
self.assertEqual(
173+
bubble_relevant_files(raw, "b-bridge"),
174+
["/good.py", "/also.py"],
175+
)
176+
self.assertEqual(bubble.relevant_files, bubble_relevant_files(bubble, "b-bridge"))
177+
self.assertTrue(any("relevantFiles" in m for m in logs.output), logs.output)
178+
179+
def test_dict_bridge_attached_file_uris_skips_non_dict_elements(self) -> None:
180+
raw = {
181+
"attachedFileCodeChunksUris": [
182+
{"path": "/a.py"},
183+
"bad",
184+
{"path": "/b.py"},
185+
]
186+
}
187+
bubble = Bubble.from_dict(raw, bubble_id="b-bridge-uri")
188+
with self.assertLogs("models.conversation", level="WARNING") as logs:
189+
self.assertEqual(
190+
bubble_attached_file_uris(raw, "b-bridge-uri"),
191+
[{"path": "/a.py"}, {"path": "/b.py"}],
192+
)
193+
self.assertEqual(
194+
bubble.attached_file_code_chunks_uris,
195+
bubble_attached_file_uris(bubble, "b-bridge-uri"),
196+
)
197+
self.assertTrue(
198+
any("attachedFileCodeChunksUris" in m for m in logs.output),
199+
logs.output,
200+
)
201+
166202
def test_optional_raw_list_no_warning_when_present(self) -> None:
167203
with self.assertNoLogs("models.raw_access", level="WARNING"):
168204
value = optional_raw_list(

0 commit comments

Comments
 (0)