Skip to content

Commit 40cd512

Browse files
committed
fix: reviewer's findings
1 parent 0c914e4 commit 40cd512

1 file changed

Lines changed: 88 additions & 1 deletion

File tree

tests/test_workspace_assignment_fallback.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,34 @@ def test_invalid_definitive_mapping_with_no_fallback_returns_none(self) -> None:
112112
self.assertIsNone(assigned)
113113

114114

115+
class TestDetermineProjectStageOrdering(unittest.TestCase):
116+
"""Non-definitive stages are tried in fixed order; earlier stage wins on conflict."""
117+
118+
def test_newly_created_files_wins_over_conflicting_code_block_data(self) -> None:
119+
with tempfile.TemporaryDirectory() as tmp:
120+
ws_a_root = os.path.join(tmp, "project-a")
121+
ws_b_root = os.path.join(tmp, "project-b")
122+
os.makedirs(ws_a_root, exist_ok=True)
123+
os.makedirs(ws_b_root, exist_ok=True)
124+
entries = [
125+
_write_workspace_json(tmp, "ws-a", ws_a_root),
126+
_write_workspace_json(tmp, "ws-b", ws_b_root),
127+
]
128+
file_a = os.path.join(ws_a_root, "src", "a.py")
129+
file_b = os.path.join(ws_b_root, "src", "b.py")
130+
os.makedirs(os.path.dirname(file_a), exist_ok=True)
131+
os.makedirs(os.path.dirname(file_b), exist_ok=True)
132+
133+
assigned = _resolve(
134+
{
135+
"newlyCreatedFiles": [{"uri": {"path": file_a}}],
136+
"codeBlockData": {f"file://{file_b}": {"language": "python"}},
137+
},
138+
workspace_entries=entries,
139+
)
140+
self.assertEqual(assigned, "ws-a")
141+
142+
115143
class TestDetermineProjectLayoutsStage(unittest.TestCase):
116144
def test_project_layouts_resolves_via_workspace_path_to_id(self) -> None:
117145
root = normalize_file_path("/work/repos/myapp")
@@ -133,7 +161,8 @@ def test_project_layouts_resolves_via_project_name_fallback(self) -> None:
133161
)
134162
self.assertEqual(assigned, "ws-from-name")
135163

136-
def test_missing_project_layouts_key_falls_through(self) -> None:
164+
def test_newly_created_files_resolves_without_project_layouts_entry(self) -> None:
165+
"""No projectLayouts row: newlyCreatedFiles still resolves via workspace_entries."""
137166
with tempfile.TemporaryDirectory() as tmp:
138167
ws_root = os.path.join(tmp, "proj")
139168
os.makedirs(ws_root, exist_ok=True)
@@ -148,6 +177,24 @@ def test_missing_project_layouts_key_falls_through(self) -> None:
148177
)
149178
self.assertEqual(assigned, "ws-entries")
150179

180+
def test_unresolvable_project_layouts_falls_through_to_file_paths(self) -> None:
181+
"""projectLayouts roots that do not map still allow later file-path stages."""
182+
with tempfile.TemporaryDirectory() as tmp:
183+
ws_root = os.path.join(tmp, "fallback-proj")
184+
os.makedirs(ws_root, exist_ok=True)
185+
entries = [_write_workspace_json(tmp, "ws-fallback", ws_root)]
186+
inside = os.path.join(ws_root, "lib", "mod.py")
187+
os.makedirs(os.path.dirname(inside), exist_ok=True)
188+
unmapped_root = normalize_file_path("/no/such/workspace/root")
189+
190+
assigned = _resolve(
191+
{"newlyCreatedFiles": [{"uri": {"path": inside}}]},
192+
composer_id="cmp-unmapped-layout",
193+
project_layouts_map={"cmp-unmapped-layout": [unmapped_root]},
194+
workspace_entries=entries,
195+
)
196+
self.assertEqual(assigned, "ws-fallback")
197+
151198

152199
class TestDetermineProjectFilePathStages(unittest.TestCase):
153200
def test_newly_created_files_resolves_workspace(self) -> None:
@@ -290,6 +337,23 @@ def test_longer_folder_name_wins_path_segment_tie(self) -> None:
290337
)
291338
self.assertEqual(assigned, "ws-long")
292339

340+
def test_path_segment_matching_from_bubble_relevant_files(self) -> None:
341+
"""Path-segment last resort can use bubble file refs when composer has no file keys."""
342+
with tempfile.TemporaryDirectory() as tmp:
343+
ws_root = os.path.join(tmp, "storage", "bubbleseg")
344+
os.makedirs(ws_root, exist_ok=True)
345+
entries = [_write_workspace_json(tmp, "ws-bubble-seg", ws_root)]
346+
347+
orphan = os.path.join(tmp, "external", "bubbleseg", "orphan.py")
348+
os.makedirs(os.path.dirname(orphan), exist_ok=True)
349+
350+
assigned = _resolve(
351+
{"fullConversationHeadersOnly": [{"bubbleId": "b-seg"}]},
352+
bubble_map={"b-seg": {"relevantFiles": [orphan]}},
353+
workspace_entries=entries,
354+
)
355+
self.assertEqual(assigned, "ws-bubble-seg")
356+
293357

294358
class TestDetermineProjectTerminalNone(unittest.TestCase):
295359
def test_all_stages_fail_returns_none(self) -> None:
@@ -398,6 +462,29 @@ def test_valid_definitive_mapping_always_wins(
398462
)
399463
self.assertEqual(result, workspace_id)
400464

465+
@given(
466+
composer_id=st.text(min_size=1, max_size=40),
467+
invalid_id=st.text(min_size=1, max_size=40),
468+
)
469+
@settings(max_examples=60, deadline=None)
470+
def test_invalid_definitive_mapping_never_returned_without_fallback(
471+
self, composer_id: str, invalid_id: str
472+
) -> None:
473+
"""Ignored invalid mapping must not leak through when no heuristic stage matches."""
474+
result = determine_project_for_conversation(
475+
composer_data=dict(_EMPTY_COMPOSER),
476+
composer_id=composer_id,
477+
project_layouts_map={},
478+
project_name_to_workspace_id={},
479+
workspace_path_to_id={},
480+
workspace_entries=[],
481+
bubble_map={},
482+
composer_id_to_workspace_id={composer_id: invalid_id},
483+
invalid_workspace_ids={invalid_id},
484+
)
485+
self.assertNotEqual(result, invalid_id)
486+
self.assertIsNone(result)
487+
401488

402489
if __name__ == "__main__":
403490
unittest.main()

0 commit comments

Comments
 (0)