|
6 | 6 | import sys |
7 | 7 | import tempfile |
8 | 8 | import unittest |
| 9 | +from unittest.mock import patch |
9 | 10 |
|
10 | 11 | from flask import Flask |
11 | 12 |
|
@@ -149,5 +150,66 @@ def test_synthetic_falls_back_to_max_bubble_timestamp(self) -> None: |
149 | 150 | self.assertLess(synthetic["timestamp"], now_ms - 10_000_000_000) |
150 | 151 |
|
151 | 152 |
|
| 153 | +def _seed_workspace_with_tool_former(parent: str) -> str: |
| 154 | + ws_root = os.path.join(parent, "workspaceStorage") |
| 155 | + global_root = os.path.join(parent, "globalStorage") |
| 156 | + os.makedirs(ws_root, exist_ok=True) |
| 157 | + os.makedirs(global_root, exist_ok=True) |
| 158 | + ws_dir = os.path.join(ws_root, "ws-a") |
| 159 | + os.makedirs(ws_dir, exist_ok=True) |
| 160 | + with open(os.path.join(ws_dir, "workspace.json"), "w") as f: |
| 161 | + json.dump({"folder": "/tmp/proj"}, f) |
| 162 | + sqlite3.connect(os.path.join(ws_dir, "state.vscdb")).close() |
| 163 | + |
| 164 | + conn = sqlite3.connect(os.path.join(global_root, "state.vscdb")) |
| 165 | + conn.execute("CREATE TABLE cursorDiskKV ([key] TEXT PRIMARY KEY, value TEXT)") |
| 166 | + conn.execute( |
| 167 | + "INSERT INTO cursorDiskKV VALUES (?, ?)", |
| 168 | + ( |
| 169 | + "composerData:cmp-t", |
| 170 | + json.dumps({ |
| 171 | + "name": "Tab with toolFormerData", |
| 172 | + "createdAt": 1_715_000_000_000, |
| 173 | + "lastUpdatedAt": 1_715_000_500_000, |
| 174 | + "fullConversationHeadersOnly": [{"bubbleId": "b-t", "type": 2}], |
| 175 | + }), |
| 176 | + ), |
| 177 | + ) |
| 178 | + conn.execute( |
| 179 | + "INSERT INTO cursorDiskKV VALUES (?, ?)", |
| 180 | + ( |
| 181 | + "bubbleId:cmp-t:b-t", |
| 182 | + json.dumps({ |
| 183 | + "text": "assistant message", |
| 184 | + "createdAt": 1_715_000_400_000, |
| 185 | + "toolFormerData": {"name": "tool-x"}, |
| 186 | + }), |
| 187 | + ), |
| 188 | + ) |
| 189 | + conn.commit() |
| 190 | + conn.close() |
| 191 | + return ws_root |
| 192 | + |
| 193 | + |
| 194 | +class TestParseToolCallNonDictReturn(unittest.TestCase): |
| 195 | + def test_non_dict_parse_result_does_not_drop_composer(self) -> None: |
| 196 | + app = Flask(__name__) |
| 197 | + app.config["TESTING"] = True |
| 198 | + app.config["EXCLUSION_RULES"] = [] |
| 199 | + |
| 200 | + with tempfile.TemporaryDirectory() as tmp: |
| 201 | + ws_root = _seed_workspace_with_tool_former(tmp) |
| 202 | + # Force _parse_tool_call to return None — the previous code |
| 203 | + # would have stored ``tool_calls = [None]`` and crashed in the |
| 204 | + # display-text fallback with ``NoneType.get``. |
| 205 | + with patch("services.workspace_tabs._parse_tool_call", return_value=None): |
| 206 | + with app.test_request_context("/api/workspaces/global/tabs"): |
| 207 | + payload, status = assemble_workspace_tabs("global", ws_root, rules=[]) |
| 208 | + |
| 209 | + self.assertEqual(status, 200) |
| 210 | + ids = [t["id"] for t in payload.get("tabs", [])] |
| 211 | + self.assertIn("cmp-t", ids) |
| 212 | + |
| 213 | + |
152 | 214 | if __name__ == "__main__": |
153 | 215 | unittest.main() |
0 commit comments