@@ -139,3 +139,312 @@ def test_entity_id_conversion(mock_run: MagicMock) -> None:
139139 assert backend ._entity_id_to_bead_id ("bd-a1b2" ) == "bd-a1b2"
140140 assert backend ._entity_id_to_bead_id ("123" ) == "bd-123"
141141 assert backend ._entity_id_to_bead_id ("a1b2" ) == "bd-a1b2"
142+
143+
144+ @patch ("entity_manager.backends.beads.subprocess.run" )
145+ def test_update_issue (mock_run : MagicMock ) -> None :
146+ """Test updating a beads issue."""
147+ # Mock init
148+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
149+ backend = BeadsBackend ()
150+
151+ # Mock read call (for update to fetch current issue)
152+ mock_run .return_value = MagicMock (
153+ returncode = 0 ,
154+ stdout = json .dumps (
155+ {
156+ "id" : "bd-a1b2" ,
157+ "title" : "Updated Title" ,
158+ "description" : "Updated description" ,
159+ "status" : "closed" ,
160+ "labels" : [],
161+ "assignee" : "bob" ,
162+ }
163+ ),
164+ stderr = "" ,
165+ )
166+
167+ entity = backend .update ("bd-a1b2" , title = "Updated Title" , status = "closed" , assignee = "bob" )
168+ assert entity .title == "Updated Title"
169+ assert entity .status == "closed"
170+ assert entity .assignee == "bob"
171+
172+
173+ @patch ("entity_manager.backends.beads.subprocess.run" )
174+ def test_update_issue_with_labels (mock_run : MagicMock ) -> None :
175+ """Test updating a beads issue with labels."""
176+ # Mock init
177+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
178+ backend = BeadsBackend ()
179+
180+ # Mock read call for getting current labels
181+ mock_run .return_value = MagicMock (
182+ returncode = 0 ,
183+ stdout = json .dumps (
184+ {
185+ "id" : "bd-a1b2" ,
186+ "title" : "Test" ,
187+ "description" : "" ,
188+ "status" : "open" ,
189+ "labels" : ["old:label" ],
190+ "assignee" : None ,
191+ }
192+ ),
193+ stderr = "" ,
194+ )
195+
196+ # Update with new labels
197+ entity = backend .update ("bd-a1b2" , labels = {"new" : "label" , "tag" : "" })
198+ assert entity .labels == {"old" : "label" } # Will have old labels from read mock
199+
200+
201+ @patch ("entity_manager.backends.beads.subprocess.run" )
202+ def test_delete_issue (mock_run : MagicMock ) -> None :
203+ """Test deleting (closing) a beads issue."""
204+ # Mock init
205+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
206+ backend = BeadsBackend ()
207+
208+ # Mock close call
209+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"result" : "success" }), stderr = "" )
210+
211+ backend .delete (["bd-a1b2" , "bd-c3d4" ])
212+
213+ # Verify close was called for each ID
214+ assert mock_run .call_count >= 2
215+
216+
217+ @patch ("entity_manager.backends.beads.subprocess.run" )
218+ def test_list_with_filters (mock_run : MagicMock ) -> None :
219+ """Test listing with filters."""
220+ # Mock init
221+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
222+ backend = BeadsBackend ()
223+
224+ # Mock list call
225+ mock_run .return_value = MagicMock (
226+ returncode = 0 ,
227+ stdout = json .dumps (
228+ [
229+ {
230+ "id" : "bd-a1b2" ,
231+ "title" : "Bug Issue" ,
232+ "description" : "" ,
233+ "status" : "open" ,
234+ "labels" : [],
235+ "assignee" : "alice" ,
236+ }
237+ ]
238+ ),
239+ stderr = "" ,
240+ )
241+
242+ entities = backend .list_entities (filters = {"status" : "open" , "assignee" : "alice" })
243+ assert len (entities ) == 1
244+ assert entities [0 ].assignee == "alice"
245+
246+
247+ @patch ("entity_manager.backends.beads.subprocess.run" )
248+ def test_list_with_limit (mock_run : MagicMock ) -> None :
249+ """Test listing with limit."""
250+ # Mock init
251+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
252+ backend = BeadsBackend ()
253+
254+ # Mock list call with multiple items
255+ mock_run .return_value = MagicMock (
256+ returncode = 0 ,
257+ stdout = json .dumps (
258+ [
259+ {"id" : f"bd-{ i } " , "title" : f"Issue { i } " , "description" : "" , "status" : "open" , "labels" : []}
260+ for i in range (10 )
261+ ]
262+ ),
263+ stderr = "" ,
264+ )
265+
266+ entities = backend .list_entities (limit = 5 )
267+ assert len (entities ) == 5
268+
269+
270+ @patch ("entity_manager.backends.beads.subprocess.run" )
271+ def test_remove_link (mock_run : MagicMock ) -> None :
272+ """Test removing dependencies."""
273+ # Mock init
274+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
275+ backend = BeadsBackend ()
276+
277+ # Mock remove link call
278+ mock_run .return_value = MagicMock (returncode = 0 , stdout = "" , stderr = "" )
279+
280+ backend .remove_link ("bd-a1b2" , ["bd-c3d4" ], "blocks" )
281+
282+ # Verify the command was called
283+ assert mock_run .call_count >= 1
284+
285+
286+ @patch ("entity_manager.backends.beads.subprocess.run" )
287+ def test_list_links (mock_run : MagicMock ) -> None :
288+ """Test listing links for an issue."""
289+ # Mock init
290+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
291+ backend = BeadsBackend ()
292+
293+ # Mock show call with dependencies
294+ mock_run .return_value = MagicMock (
295+ returncode = 0 ,
296+ stdout = json .dumps (
297+ {
298+ "id" : "bd-a1b2" ,
299+ "title" : "Test" ,
300+ "description" : "" ,
301+ "status" : "open" ,
302+ "labels" : [],
303+ "dependencies" : [
304+ {"type" : "blocks" , "target_id" : "bd-c3d4" },
305+ {"type" : "related" , "target_id" : "bd-e5f6" },
306+ ],
307+ }
308+ ),
309+ stderr = "" ,
310+ )
311+
312+ links = backend .list_links ("bd-a1b2" )
313+ assert len (links ) == 2
314+ assert links [0 ].link_type == "blocks"
315+ assert links [1 ].link_type == "related"
316+
317+
318+ @patch ("entity_manager.backends.beads.subprocess.run" )
319+ def test_list_links_with_filter (mock_run : MagicMock ) -> None :
320+ """Test listing links with type filter."""
321+ # Mock init
322+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
323+ backend = BeadsBackend ()
324+
325+ # Mock show call with dependencies
326+ mock_run .return_value = MagicMock (
327+ returncode = 0 ,
328+ stdout = json .dumps (
329+ {
330+ "id" : "bd-a1b2" ,
331+ "title" : "Test" ,
332+ "description" : "" ,
333+ "status" : "open" ,
334+ "labels" : [],
335+ "dependencies" : [
336+ {"type" : "blocks" , "target_id" : "bd-c3d4" },
337+ {"type" : "related" , "target_id" : "bd-e5f6" },
338+ ],
339+ }
340+ ),
341+ stderr = "" ,
342+ )
343+
344+ links = backend .list_links ("bd-a1b2" , link_type = "blocks" )
345+ assert len (links ) == 1
346+ assert links [0 ].link_type == "blocks"
347+
348+
349+ @patch ("entity_manager.backends.beads.subprocess.run" )
350+ def test_get_link_tree (mock_run : MagicMock ) -> None :
351+ """Test getting dependency tree."""
352+ # Mock init
353+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
354+ backend = BeadsBackend ()
355+
356+ # Use side_effect to handle multiple calls in sequence
357+ mock_run .side_effect = [
358+ # First call: read (show) command
359+ MagicMock (
360+ returncode = 0 ,
361+ stdout = json .dumps (
362+ {
363+ "id" : "bd-a1b2" ,
364+ "title" : "Test Issue" ,
365+ "description" : "" ,
366+ "status" : "open" ,
367+ "labels" : [],
368+ }
369+ ),
370+ stderr = "" ,
371+ ),
372+ # Second call: tree command
373+ MagicMock (
374+ returncode = 0 ,
375+ stdout = json .dumps (
376+ {
377+ "children" : [{"id" : "bd-c3d4" , "title" : "Child" }],
378+ "blocking" : [],
379+ "blocked_by" : [],
380+ "parent" : {"id" : "bd-e5f6" , "title" : "Parent" },
381+ }
382+ ),
383+ stderr = "" ,
384+ ),
385+ ]
386+
387+ tree = backend .get_link_tree ("bd-a1b2" )
388+ assert tree ["entity" ]["id" ] == "bd-a1b2"
389+ assert len (tree ["links" ]["children" ]) == 1
390+ assert len (tree ["links" ]["parent" ]) == 1
391+
392+
393+ @patch ("entity_manager.backends.beads.subprocess.run" )
394+ def test_find_cycles (mock_run : MagicMock ) -> None :
395+ """Test finding cycles in dependency graph."""
396+ # Mock init
397+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
398+ backend = BeadsBackend ()
399+
400+ # Mock cycles call
401+ mock_run .return_value = MagicMock (
402+ returncode = 0 ,
403+ stdout = json .dumps ([["bd-a1b2" , "bd-c3d4" , "bd-a1b2" ]]),
404+ stderr = "" ,
405+ )
406+
407+ cycles = backend .find_cycles ()
408+ assert len (cycles ) == 1
409+ assert len (cycles [0 ]) == 3
410+
411+
412+ @patch ("entity_manager.backends.beads.subprocess.run" )
413+ def test_create_with_labels (mock_run : MagicMock ) -> None :
414+ """Test creating issue with labels."""
415+ # Mock init
416+ mock_run .return_value = MagicMock (returncode = 0 , stdout = json .dumps ({"status" : "ok" }), stderr = "" )
417+ backend = BeadsBackend ()
418+
419+ # Mock create call
420+ mock_run .return_value = MagicMock (
421+ returncode = 0 ,
422+ stdout = json .dumps (
423+ {
424+ "id" : "bd-a1b2" ,
425+ "title" : "Test" ,
426+ "description" : "" ,
427+ "status" : "open" ,
428+ "labels" : ["bug" , "priority:high" ],
429+ "assignee" : None ,
430+ }
431+ ),
432+ stderr = "" ,
433+ )
434+
435+ entity = backend .create ("Test" , labels = {"bug" : "" , "priority" : "high" })
436+ assert entity .labels == {"bug" : "" , "priority" : "high" }
437+
438+
439+ @patch ("entity_manager.backends.beads.subprocess.run" )
440+ def test_init_failure (mock_run : MagicMock ) -> None :
441+ """Test initialization failure."""
442+ import subprocess
443+
444+ mock_run .side_effect = subprocess .CalledProcessError (1 , ["bd" , "info" ], stderr = "bd not found" )
445+
446+ try :
447+ BeadsBackend ()
448+ assert False , "Should have raised ValueError"
449+ except ValueError as e :
450+ assert "bd command failed" in str (e )
0 commit comments