diff --git a/server/main.py b/server/main.py index a11b9bd53..3be534dcd 100644 --- a/server/main.py +++ b/server/main.py @@ -124,6 +124,11 @@ class MemoryCreate(BaseModel): prompt: Optional[str] = Field(None, description="Custom prompt to use for fact extraction.") +class MemoryUpdate(BaseModel): + text: str = Field(..., description="New content to update the memory with.") + metadata: Optional[Dict[str, Any]] = Field(None, description="Metadata to update.") + + class SearchRequest(BaseModel): query: str = Field(..., description="Search query.") user_id: Optional[str] = None @@ -199,18 +204,18 @@ def search_memories(search_req: SearchRequest, _api_key: Optional[str] = Depends @app.put("/memories/{memory_id}", summary="Update a memory") -def update_memory(memory_id: str, updated_memory: Dict[str, Any], _api_key: Optional[str] = Depends(verify_api_key)): +def update_memory(memory_id: str, updated_memory: MemoryUpdate, _api_key: Optional[str] = Depends(verify_api_key)): """Update an existing memory with new content. - + Args: memory_id (str): ID of the memory to update - updated_memory (str): New content to update the memory with - + updated_memory (MemoryUpdate): New content and optional metadata to update the memory with + Returns: dict: Success message indicating the memory was updated """ try: - return MEMORY_INSTANCE.update(memory_id=memory_id, data=updated_memory) + return MEMORY_INSTANCE.update(memory_id=memory_id, data=updated_memory.text, metadata=updated_memory.metadata) except Exception as e: logging.exception("Error in update_memory:") raise HTTPException(status_code=500, detail=str(e)) diff --git a/tests/test_server_auth.py b/tests/test_server_auth.py index d72a6789a..4feb60d38 100644 --- a/tests/test_server_auth.py +++ b/tests/test_server_auth.py @@ -94,7 +94,7 @@ class TestAuthDisabled: assert resp.status_code == 200 def test_update_memory_without_key(self): - resp = self.client.put("/memories/mem-1", json={"data": "updated"}) + resp = self.client.put("/memories/mem-1", json={"text": "updated"}) assert resp.status_code == 200 def test_history_without_key(self): @@ -276,7 +276,7 @@ class TestAuthEnabled: assert resp.status_code == 200 def test_update_memory_with_key(self): - resp = self._authed("PUT", "/memories/mem-1", json={"data": "updated"}) + resp = self._authed("PUT", "/memories/mem-1", json={"text": "updated"}) assert resp.status_code == 200 def test_history_with_key(self): @@ -346,7 +346,7 @@ class TestAuthenticatedCRUDFlow: self.mock.search.assert_called_once() # 5. Update - resp = self._authed("PUT", "/memories/mem-1", json={"data": "updated content"}) + resp = self._authed("PUT", "/memories/mem-1", json={"text": "updated content"}) assert resp.status_code == 200 self.mock.update.assert_called_once() diff --git a/tests/test_server_params.py b/tests/test_server_params.py index ae569fc00..b614e571e 100644 --- a/tests/test_server_params.py +++ b/tests/test_server_params.py @@ -505,3 +505,59 @@ class TestCallSignatureMatch: assert resp.status_code == 200 _, kwargs = mock_memory.search.call_args assert kwargs["query"] == "food" + + +# =========================================================================== +# MemoryUpdate: text and metadata forwarding (fix for #3933) +# =========================================================================== + +class TestUpdateMemory: + """Verify that PUT /memories/{id} extracts text and metadata from the + request body and forwards them correctly to Memory.update().""" + + def test_text_forwarded_as_data(self, client, mock_memory): + resp = client.put("/memories/mem-1", json={"text": "Likes tennis"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.update.call_args + assert kwargs["data"] == "Likes tennis" + + def test_metadata_forwarded(self, client, mock_memory): + resp = client.put("/memories/mem-1", json={ + "text": "Likes tennis", + "metadata": {"category": "sports"}, + }) + assert resp.status_code == 200 + _, kwargs = mock_memory.update.call_args + assert kwargs["metadata"] == {"category": "sports"} + + def test_metadata_omitted_passes_none(self, client, mock_memory): + resp = client.put("/memories/mem-1", json={"text": "Likes tennis"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.update.call_args + assert kwargs["metadata"] is None + + def test_missing_text_returns_422(self, client): + """text is required — omitting it should fail validation.""" + resp = client.put("/memories/mem-1", json={"metadata": {"k": "v"}}) + assert resp.status_code == 422 + + def test_dict_not_passed_as_data(self, client, mock_memory): + """Regression test for #3933: the entire dict must NOT be passed as data.""" + resp = client.put("/memories/mem-1", json={"text": "updated content"}) + assert resp.status_code == 200 + _, kwargs = mock_memory.update.call_args + assert isinstance(kwargs["data"], str) + + +class TestUpdateOpenAPISchema: + """Verify the MemoryUpdate schema appears in the OpenAPI docs.""" + + def test_update_schema_includes_text(self, client): + schema = client.get("/openapi.json").json() + update_props = schema["components"]["schemas"]["MemoryUpdate"]["properties"] + assert "text" in update_props + + def test_update_schema_includes_metadata(self, client): + schema = client.get("/openapi.json").json() + update_props = schema["components"]["schemas"]["MemoryUpdate"]["properties"] + assert "metadata" in update_props