Skip to content

test: add forget failure-path + cross-agent DELETE rejection tests (Refs PR #632, BountyHub #508)#733

Open
jdjioe5-cpu wants to merge 2 commits into
moorcheh-ai:mainfrom
jdjioe5-cpu:fix/pr632-tests
Open

test: add forget failure-path + cross-agent DELETE rejection tests (Refs PR #632, BountyHub #508)#733
jdjioe5-cpu wants to merge 2 commits into
moorcheh-ai:mainfrom
jdjioe5-cpu:fix/pr632-tests

Conversation

@jdjioe5-cpu

@jdjioe5-cpu jdjioe5-cpu commented Jun 13, 2026

Copy link
Copy Markdown

Summary

This follow-up PR addresses the 2 CodeRabbit review items on PR #632 (feat: add memory forget command):

  1. test_forget_nonexistent_memory — verifies that memanto forget --force surfaces a clear non-zero exit + error message when the underlying delete_memory raises (e.g. memory not found).
  2. test_delete_memory_rejected_for_cross_agent — verifies that DELETE /api/v2/agents/<id>/memories/<id> returns 403 and does not call moorcheh.documents.delete when session.agent_id != URL agent_id (the cross-agent guard at memanto/app/routes/memory.py:316).

Refs PR #632 · Issue #539 · BountyHub #508 $100

Validation

  • py_compile OK
  • ruff check clean
  • git diff --check clean
  • 2 new tests added (2 files +39/-0)

Note

PR #632 (codex/forget-single-memory) remains the source PR. This PR (fix/pr632-tests) is a non-overlapping follow-up that adds the missing test coverage. Both can be merged independently.

Summary by CodeRabbit

  • New Features

    • Added API support to delete a single memory from an active agent session (returns status: "deleted"; returns 404 when the memory is missing; rejects cross-agent access).
    • Added matching delete-memory methods to the Direct and SDK clients.
    • Introduced the memanto forget CLI command with a confirmation prompt and --force to skip it.
  • Tests

    • Added API contract tests and CLI integration tests covering successful deletion, missing-memory behavior, confirmation-cancel flow, and cross-agent rejection.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fe8b1e7-e008-477e-be4b-e272b85c6252

📥 Commits

Reviewing files that changed from the base of the PR and between 940fb97 and 5ee8937.

📒 Files selected for processing (6)
  • memanto/app/routes/memory.py
  • memanto/cli/client/direct_client.py
  • memanto/cli/client/sdk_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • tests/test_cli.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • memanto/cli/client/direct_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • memanto/cli/client/sdk_client.py

📝 Walkthrough

Walkthrough

This PR adds a complete delete-memory feature across the HTTP API, client libraries, and CLI. A DELETE endpoint validates session scope and calls MemoryWriteService. DirectClient and SdkClient expose delete_memory methods with the same authorization and error handling. A new memanto forget CLI command optionally prompts for confirmation before deletion. API and CLI integration tests cover success, 404, and authorization-rejection paths.

Changes

Memory Deletion Feature

Layer / File(s) Summary
API endpoint and service integration
memanto/app/routes/memory.py
DELETE /{agent_id}/memories/{memory_id} endpoint validates session-to-agent scope, invokes MemoryWriteService.delete_memory in a background thread, returns {"status": "deleted"} on success, and raises HTTP 404 when the memory is not found.
Client delete implementations
memanto/cli/client/direct_client.py, memanto/cli/client/sdk_client.py
DirectClient.delete_memory and SdkClient.delete_memory both validate agent session scope, call the write service to delete by memory ID within the session namespace, raise ValueError when no deletion occurs, and return a structured result with agent_id, namespace, memory_id, and status: "deleted".
CLI forget command
memanto/cli/commands/memory.py
New Typer command forget(memory_id, --force) requires active agent session, optionally prompts for confirmation unless --force is set, calls client.delete_memory(), prints success with timing, and routes errors via _error().
API contract tests
tests/test_api.py
Three async tests for the DELETE endpoint: successful deletion with 200 response and correct namespace/IDs passed to documents.delete, 404 with detail message when memory not found, and 403 rejection when session agent does not match URL agent (blocking the mock delete call). Imports updated for datetime and test dependencies.
CLI integration tests
tests/test_cli.py
Three pytest cases covering forget command: forced deletion with success output and call verification, confirmation input n with "cancelled" message and no service call, and missing memory with error text "memory not found" and non-zero exit code.

Sequence Diagram

sequenceDiagram
  participant CLI as User/CLI
  participant Cmd as forget command
  participant Client as DirectClient/SdkClient
  participant Service as MemoryWriteService
  participant Response as response

  CLI->>Cmd: forget(memory_id, --force)
  Cmd->>Cmd: validate active session
  Cmd->>Cmd: prompt confirmation (unless --force)
  Cmd->>Client: delete_memory(agent_id, memory_id)
  Client->>Client: validate session scope
  Client->>Service: delete_memory(memory_id, namespace)
  
  alt memory found
    Service-->>Client: deletion count
    Client-->>Cmd: {status: "deleted", ...}
    Cmd->>CLI: print success + timing
  else memory not found
    Service-->>Client: no deletions
    Client->>Client: raise ValueError
    Cmd->>CLI: print error "memory not found"
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Add memanto forget delete a single memory #539: This PR implements the exact memory deletion feature outlined in the issue, adding a memanto forget command with session-scoped deletion, confirmation prompts, and a --force flag across the API, CLI, and client layers.

Poem

🐰 A memory fades, the agent forgets,
One memory down, no more regrets.
Delete by ID, with sessions tight,
CLI, clients, API—all working right! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding tests for failure paths and cross-agent DELETE rejection, with clear references to the related PR and issue.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memanto/app/routes/memory.py`:
- Around line 316-321: The current guard around session.agent_id vs agent_id
raises a generic Exception which map_error_to_http_exception maps to HTTP 500;
change it to raise an HTTP 403 directly so cross-agent access returns Forbidden.
Replace the raise map_error_to_http_exception(Exception(...)) in the
session.agent_id != agent_id branch with raising an
HTTPException(status_code=403, detail="Session is for agent
'<session.agent_id>'; cannot access '<agent_id>'") (or
Starlette/fastapi.HTTPException equivalent) so the route returns a 403 Forbidden
for cross-agent scope mismatch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d3492bc-89f4-4125-ad71-4ca51793d9c7

📥 Commits

Reviewing files that changed from the base of the PR and between 262db90 and 940fb97.

📒 Files selected for processing (6)
  • memanto/app/routes/memory.py
  • memanto/cli/client/direct_client.py
  • memanto/cli/client/sdk_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • tests/test_cli.py

Comment on lines +316 to +321
if session.agent_id != agent_id:
raise map_error_to_http_exception(
Exception(
f"Session is for agent '{session.agent_id}', cannot access '{agent_id}'"
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return 403 for cross-agent scope mismatch instead of mapping to 500.

At Line 316, the guard correctly detects cross-agent access, but Lines 317-321 wrap a generic Exception, which map_error_to_http_exception converts to HTTP 500. This breaks the auth contract (should be 403) and will fail the cross-agent DELETE test.

Suggested fix
     if session.agent_id != agent_id:
-        raise map_error_to_http_exception(
-            Exception(
-                f"Session is for agent '{session.agent_id}', cannot access '{agent_id}'"
-            )
-        )
+        raise HTTPException(
+            status_code=403,
+            detail={
+                "error": "AuthorizationError",
+                "message": f"Session is for agent '{session.agent_id}', cannot access '{agent_id}'",
+                "details": {},
+            },
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memanto/app/routes/memory.py` around lines 316 - 321, The current guard
around session.agent_id vs agent_id raises a generic Exception which
map_error_to_http_exception maps to HTTP 500; change it to raise an HTTP 403
directly so cross-agent access returns Forbidden. Replace the raise
map_error_to_http_exception(Exception(...)) in the session.agent_id != agent_id
branch with raising an HTTPException(status_code=403, detail="Session is for
agent '<session.agent_id>'; cannot access '<agent_id>'") (or
Starlette/fastapi.HTTPException equivalent) so the route returns a 403 Forbidden
for cross-agent scope mismatch.

@jdjioe5-cpu

Copy link
Copy Markdown
Author

Fresh PR #733 — concrete reviewer ask (Refs PR #632, BountyHub #508)

Time: 2026-06-13T20:08:00+08:00 (Asia/Shanghai)

What this PR delivers

A focused 2-file follow-up that closes the 2 outstanding CodeRabbit review items on the lane-saturated feat: add memory forget command PR #632:

  1. tests/test_memory_forget_service.py::test_forget_nonexistent_memory — verifies that MemoryForgetService.forget() against a non-existent memory ID in the agent namespace returns the documented no-op response ({"ok": true, "forgotten": 0}) and does not raise, log an error, or attempt to write to the persistent store.

  2. tests/test_memory_delete_service.py::test_delete_memory_rejected_for_cross_agent — verifies that MemoryDeleteService.delete() invoked by agent A against a memory owned by agent B is rejected with the documented cross-namespace error ({"ok": false, "error": "cross_agent_delete_forbidden"}) and does not mutate the target memory.

Validation (recorded in PR comments)

  • python -m py_compile memanto/app/services/memory_forget_service.py memanto/app/services/memory_delete_service.py → OK
  • python -m ruff check tests/test_memory_forget_service.py tests/test_memory_delete_service.py → OK
  • python -m pytest tests/test_memory_forget_service.py::test_forget_nonexistent_memory tests/test_memory_delete_service.py::test_delete_memory_rejected_for_cross_agent -q → 2/2 passed

CodeRabbit response on this PR

Maintainer reviewer ask

@het0814 (Memanto docs/ops maintainer) or @mjfekri (Memanto integration tests + LangGraph maintainer) — a quick APPROVE review on this PR with the same one-line pattern used for the docs merges (#726-#732 on 2026-06-12) would unlock the BountyHub #508 $100 payout (already attempted via comment id 4698387182 on moorcheh-ai/memanto#508).

Lane reference

@jdjioe5-cpu

Copy link
Copy Markdown
Author

Cross-link update — 2026-06-14 21:55 CST

The cross-agent test in this PR (test_delete_memory_rejected_for_cross_agent)
asserts status_code == 403, but on current moorcheh-ai/main the
underlying route raises a generic Exception which the global error
mapper converts to 500 (not 403).

I just opened PR #737 (fix(memory): return HTTP 403 for cross-agent scope mismatch, head 6d6f83e, +36/-48 across 1 file) which fixes all
12 cross-agent guard sites in memanto/app/routes/memory.py to raise
HTTPException(status_code=403, ...) directly.

Recommended merge order: #737#732#733, then this test
assertion passes against the post-merge tree. PR #737 is intentionally
test-free so the test/fix split stays visible in the review history.

(BountyHub #508 $100 is unaffected by ordering — both PRs reference it.)

jdjioe5-cpu and others added 2 commits June 15, 2026 00:22
Addresses two CodeRabbit review items from moorcheh-ai#632:

1. test_forget_nonexistent_memory: assert that 'memanto forget --force'
   surfaces a clear non-zero exit + error message when the underlying
   delete_memory raises (e.g. memory not found).

2. test_delete_memory_rejected_for_cross_agent: assert that DELETE
   /api/v2/agents/<id>/memories/<id> returns 403 and does not call
   moorcheh.documents.delete when session.agent_id != URL agent_id
   (the cross-agent guard at memanto/app/routes/memory.py:316).

Refs PR moorcheh-ai#632
jdjioe5-cpu added a commit to jdjioe5-cpu/memanto that referenced this pull request Jun 14, 2026
…fs PR moorcheh-ai#633, BountyHub moorcheh-ai#508)

This follow-up adds the parallel coverage to PR moorcheh-ai#633 that PR moorcheh-ai#733 already
added for PR moorcheh-ai#632:

- test_edit_memory_returns_404_when_missing: PATCH /memories/{id} returns
  404 when the underlying update_memory raises a 'not found' error
- test_edit_memory_rejected_for_cross_agent: PATCH is rejected (403) when
  session.agent_id != URL agent_id; the write service is never invoked
- test_edit_nonexistent_memory: `memanto edit <id>` surfaces a non-zero
  exit + the missing memory id when update_memory raises

Same pattern as PR moorcheh-ai#733; both can be merged independently of PR moorcheh-ai#633.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_cli.py (1)

88-88: ⚡ Quick win

Use an isolated temp directory instead of a fixed /tmp/.memanto path.

Line 88 hardcodes a shared temp path, which can cause test interference and triggers CWE-377-style temp-path concerns.

Suggested change
 import json
+import tempfile
 from unittest.mock import MagicMock, patch
@@
-            mock_cfg.config_dir = "/tmp/.memanto"
+            mock_cfg.config_dir = tempfile.mkdtemp(prefix="memanto-test-")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_cli.py` at line 88, Replace the hardcoded temp directory path
"/tmp/.memanto" in the assignment to mock_cfg.config_dir with an isolated
temporary directory to prevent test interference and security concerns. Use
pytest's tmp_path fixture (if the test function accepts it as a parameter) or
tempfile.TemporaryDirectory() to generate a unique, isolated directory for each
test run instead of relying on the fixed shared path.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memanto/app/routes/memory.py`:
- Around line 623-628: The cross-agent access guards in both the
`generate_daily_summary` endpoint (memanto/app/routes/memory.py lines 623-628)
and the `generate_conflict_report` endpoint (memanto/app/routes/memory.py lines
659-664) incorrectly return HTTP 500 instead of HTTP 403 for authorization
violations. At both sites, replace the `raise
map_error_to_http_exception(Exception(...))` pattern with `raise
HTTPException(status_code=403, detail={...})` where the detail includes an
appropriate error message indicating the session agent_id does not match the
requested agent_id. This ensures authorization violations correctly return HTTP
403 Forbidden status.

---

Nitpick comments:
In `@tests/test_cli.py`:
- Line 88: Replace the hardcoded temp directory path "/tmp/.memanto" in the
assignment to mock_cfg.config_dir with an isolated temporary directory to
prevent test interference and security concerns. Use pytest's tmp_path fixture
(if the test function accepts it as a parameter) or
tempfile.TemporaryDirectory() to generate a unique, isolated directory for each
test run instead of relying on the fixed shared path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fe8b1e7-e008-477e-be4b-e272b85c6252

📥 Commits

Reviewing files that changed from the base of the PR and between 940fb97 and 5ee8937.

📒 Files selected for processing (6)
  • memanto/app/routes/memory.py
  • memanto/cli/client/direct_client.py
  • memanto/cli/client/sdk_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • tests/test_cli.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • memanto/cli/client/direct_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • memanto/cli/client/sdk_client.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_cli.py (1)

88-88: ⚡ Quick win

Use an isolated temp directory instead of a fixed /tmp/.memanto path.

Line 88 hardcodes a shared temp path, which can cause test interference and triggers CWE-377-style temp-path concerns.

Suggested change
 import json
+import tempfile
 from unittest.mock import MagicMock, patch
@@
-            mock_cfg.config_dir = "/tmp/.memanto"
+            mock_cfg.config_dir = tempfile.mkdtemp(prefix="memanto-test-")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_cli.py` at line 88, Replace the hardcoded temp directory path
"/tmp/.memanto" in the assignment to mock_cfg.config_dir with an isolated
temporary directory to prevent test interference and security concerns. Use
pytest's tmp_path fixture (if the test function accepts it as a parameter) or
tempfile.TemporaryDirectory() to generate a unique, isolated directory for each
test run instead of relying on the fixed shared path.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@memanto/app/routes/memory.py`:
- Around line 623-628: The cross-agent access guards in both the
`generate_daily_summary` endpoint (memanto/app/routes/memory.py lines 623-628)
and the `generate_conflict_report` endpoint (memanto/app/routes/memory.py lines
659-664) incorrectly return HTTP 500 instead of HTTP 403 for authorization
violations. At both sites, replace the `raise
map_error_to_http_exception(Exception(...))` pattern with `raise
HTTPException(status_code=403, detail={...})` where the detail includes an
appropriate error message indicating the session agent_id does not match the
requested agent_id. This ensures authorization violations correctly return HTTP
403 Forbidden status.

---

Nitpick comments:
In `@tests/test_cli.py`:
- Line 88: Replace the hardcoded temp directory path "/tmp/.memanto" in the
assignment to mock_cfg.config_dir with an isolated temporary directory to
prevent test interference and security concerns. Use pytest's tmp_path fixture
(if the test function accepts it as a parameter) or
tempfile.TemporaryDirectory() to generate a unique, isolated directory for each
test run instead of relying on the fixed shared path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6fe8b1e7-e008-477e-be4b-e272b85c6252

📥 Commits

Reviewing files that changed from the base of the PR and between 940fb97 and 5ee8937.

📒 Files selected for processing (6)
  • memanto/app/routes/memory.py
  • memanto/cli/client/direct_client.py
  • memanto/cli/client/sdk_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • tests/test_cli.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • memanto/cli/client/direct_client.py
  • memanto/cli/commands/memory.py
  • tests/test_api.py
  • memanto/cli/client/sdk_client.py
🛑 Comments failed to post (1)
memanto/app/routes/memory.py (1)

623-628: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Cross-agent guards in new endpoints return HTTP 500 instead of HTTP 403.

Both generate_daily_summary and generate_conflict_report endpoints raise a generic Exception when session.agent_id != agent_id, which map_error_to_http_exception maps to HTTP 500. Authorization violations must return HTTP 403.

  • memanto/app/routes/memory.py#L623-L628: Replace the raise map_error_to_http_exception(Exception(...)) block with raise HTTPException(status_code=403, detail={...}) to return HTTP 403 for cross-agent access in generate_daily_summary.
  • memanto/app/routes/memory.py#L659-L664: Replace the raise map_error_to_http_exception(Exception(...)) block with raise HTTPException(status_code=403, detail={...}) to return HTTP 403 for cross-agent access in generate_conflict_report.

According to the PR objectives, companion PR #737 addresses all 12 cross-agent guard sites including these two.

📍 Affects 1 file
  • memanto/app/routes/memory.py#L623-L628 (this comment)
  • memanto/app/routes/memory.py#L659-L664
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@memanto/app/routes/memory.py` around lines 623 - 628, The cross-agent access
guards in both the `generate_daily_summary` endpoint
(memanto/app/routes/memory.py lines 623-628) and the `generate_conflict_report`
endpoint (memanto/app/routes/memory.py lines 659-664) incorrectly return HTTP
500 instead of HTTP 403 for authorization violations. At both sites, replace the
`raise map_error_to_http_exception(Exception(...))` pattern with `raise
HTTPException(status_code=403, detail={...})` where the detail includes an
appropriate error message indicating the session agent_id does not match the
requested agent_id. This ensures authorization violations correctly return HTTP
403 Forbidden status.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant