From 90b0b564c24e41e0e97f3e1c494a87f11b355fdf Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:06:56 +0000 Subject: [PATCH] feat: add multi-ID delete support with per-ID results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add delete_bounties() method to BountyService that returns individual results per bounty ID (deleted, not_found, permission_denied) - Update cmd_delete to accept multiple IDs and show per-ID messages - Add 3 tests for delete_bounties method Example output: /delete 1 2 3 ✅ Bounty #1 deleted. ✅ Bounty #2 deleted. ⛔ Bounty #3 not found. Fixes #47 --- apps/telegram-bot/commands.py | 22 ++++++++++++------- core/services.py | 22 +++++++++++++++++++ tests/test_services.py | 40 +++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/apps/telegram-bot/commands.py b/apps/telegram-bot/commands.py index 609a921..9635c07 100644 --- a/apps/telegram-bot/commands.py +++ b/apps/telegram-bot/commands.py @@ -318,11 +318,11 @@ cmd_edit = cmd_update async def cmd_delete(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: args = extract_args(update.message.text) if not args: - await update.message.reply_text("Usage: /delete ") + await update.message.reply_text("Usage: /delete [bounty_id ...]") return try: - bounty_id = int(args[0]) + bounty_ids = [int(arg) for arg in args] except ValueError: await update.message.reply_text("Invalid bounty ID.") return @@ -331,19 +331,25 @@ async def cmd_delete(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: room_id = get_room_id(update) try: - success = BOUNTY_SERVICE.delete_bounty( + results = BOUNTY_SERVICE.delete_bounties( room_id=room_id, - bounty_id=bounty_id, + bounty_ids=bounty_ids, user_id=user_id, ) except PermissionError as e: await update.message.reply_text(f"⛔ {e}") return - if success: - await update.message.reply_text(f"✅ Bounty #{bounty_id} deleted.") - else: - await update.message.reply_text("Bounty not found.") + response_lines = [] + for bounty_id, result in results.items(): + if result == "deleted": + response_lines.append(f"✅ Bounty #{bounty_id} deleted.") + elif result == "not_found": + response_lines.append(f"⛔ Bounty #{bounty_id} not found.") + elif result == "permission_denied": + response_lines.append(f"⛔ Bounty #{bounty_id} permission denied.") + + await update.message.reply_text("\n".join(response_lines)) async def cmd_track(update: Update, ctx: ContextTypes.DEFAULT_TYPE) -> None: diff --git a/core/services.py b/core/services.py index 2f727b4..3ddc86f 100644 --- a/core/services.py +++ b/core/services.py @@ -210,6 +210,28 @@ class BountyService: self._storage.update_bounty(room_id, bounty) return True + def delete_bounties( + self, room_id: int, bounty_ids: list[int], user_id: int + ) -> dict[int, str]: + """Soft delete multiple bounties. Returns dict of bounty_id -> result. + + Results can be: 'deleted', 'not_found', 'permission_denied' + """ + results = {} + for bounty_id in bounty_ids: + bounty = self._storage.get_bounty(room_id, bounty_id) + if not bounty: + results[bounty_id] = "not_found" + continue + if not self.is_admin(room_id, user_id): + results[bounty_id] = "permission_denied" + continue + + bounty.deleted_at = int(time.time()) + self._storage.update_bounty(room_id, bounty) + results[bounty_id] = "deleted" + return results + class TrackingService: """Service for tracking bounty operations.""" diff --git a/tests/test_services.py b/tests/test_services.py index 8a92134..3875b47 100644 --- a/tests/test_services.py +++ b/tests/test_services.py @@ -210,6 +210,46 @@ class TestBountyService: result = self.service.delete_bounty(-1001, 999, self.admin_user_id) assert result is False + def test_delete_bounties_multi_id_success(self): + """Test delete_bounties returns individual results for multiple bounties.""" + bounty1 = self.service.add_bounty( + room_id=-1001, user_id=self.admin_user_id, text="To delete 1" + ) + bounty2 = self.service.add_bounty( + room_id=-1001, user_id=self.admin_user_id, text="To delete 2" + ) + results = self.service.delete_bounties( + -1001, [bounty1.id, bounty2.id], self.admin_user_id + ) + assert results == {bounty1.id: "deleted", bounty2.id: "deleted"} + # Verify both are soft deleted + assert self.service.get_bounty(-1001, bounty1.id) is None + assert self.service.get_bounty(-1001, bounty2.id) is None + + def test_delete_bounties_mixed_results(self): + """Test delete_bounties returns not_found for non-existent bounties.""" + bounty = self.service.add_bounty( + room_id=-1001, user_id=self.admin_user_id, text="To delete" + ) + results = self.service.delete_bounties( + -1001, [bounty.id, 999, 888], self.admin_user_id + ) + assert results == {bounty.id: "deleted", 999: "not_found", 888: "not_found"} + + def test_delete_bounties_permission_denied(self): + """Test delete_bounties returns permission_denied for non-admin users.""" + bounty = self.service.add_bounty( + room_id=-1001, user_id=self.admin_user_id, text="To delete" + ) + results = self.service.delete_bounties( + -1001, + [bounty.id], + 999, # non-admin user + ) + assert results == {bounty.id: "permission_denied"} + # Verify bounty was NOT deleted + assert self.service.get_bounty(-1001, bounty.id) is not None + class TestTrackingService: """Unit tests for TrackingService.""" -- 2.49.1