@@ -139,18 +139,19 @@ async def test_read_content_allows_safe_paths_with_mocked_api(self, client):
139139 # Mock the API call to simulate a successful response
140140 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
141141 mock_response = MagicMock ()
142- mock_response .headers = {
143- "content-type" : "text/markdown" ,
144- "content-length" : "100"
145- }
142+ mock_response .headers = {"content-type" : "text/markdown" , "content-length" : "100" }
146143 mock_response .text = f"# Content for { safe_path } \n This is test content."
147144 mock_call_get .return_value = mock_response
148-
145+
149146 result = await read_content .fn (path = safe_path )
150147
151148 # Should succeed (not a security error)
152149 assert isinstance (result , dict )
153- assert result ["type" ] != "error" or "paths must stay within project boundaries" not in result .get ("error" , "" )
150+ assert result [
151+ "type"
152+ ] != "error" or "paths must stay within project boundaries" not in result .get (
153+ "error" , ""
154+ )
154155
155156 @pytest .mark .asyncio
156157 async def test_read_content_memory_url_processing (self , client ):
@@ -178,7 +179,7 @@ async def test_read_content_security_logging(self, client, caplog):
178179
179180 assert result ["type" ] == "error"
180181 assert "paths must stay within project boundaries" in result ["error" ]
181-
182+
182183 # Check that security violation was logged
183184 # Note: This test may need adjustment based on the actual logging setup
184185 # The security validation should generate a warning log entry
@@ -189,45 +190,47 @@ async def test_read_content_empty_path_security(self, client):
189190 # Mock the API call since empty path should be allowed (resolves to project root)
190191 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
191192 mock_response = MagicMock ()
192- mock_response .headers = {
193- "content-type" : "text/markdown" ,
194- "content-length" : "50"
195- }
193+ mock_response .headers = {"content-type" : "text/markdown" , "content-length" : "50" }
196194 mock_response .text = "# Root content"
197195 mock_call_get .return_value = mock_response
198-
196+
199197 result = await read_content .fn (path = "" )
200198
201199 assert isinstance (result , dict )
202200 # Empty path should not trigger security error (it's handled as project root)
203- assert result ["type" ] != "error" or "paths must stay within project boundaries" not in result .get ("error" , "" )
201+ assert result [
202+ "type"
203+ ] != "error" or "paths must stay within project boundaries" not in result .get (
204+ "error" , ""
205+ )
204206
205207 @pytest .mark .asyncio
206208 async def test_read_content_current_directory_references_security (self , client ):
207209 """Test that current directory references are handled securely."""
208210 # Test current directory references (should be safe)
209211 safe_paths = [
210212 "./notes/file.md" ,
211- "folder/./file.md" ,
213+ "folder/./file.md" ,
212214 "./folder/subfolder/file.md" ,
213215 ]
214216
215217 for safe_path in safe_paths :
216218 # Mock the API call for these safe paths
217219 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
218220 mock_response = MagicMock ()
219- mock_response .headers = {
220- "content-type" : "text/markdown" ,
221- "content-length" : "100"
222- }
221+ mock_response .headers = {"content-type" : "text/markdown" , "content-length" : "100" }
223222 mock_response .text = f"# Content for { safe_path } "
224223 mock_call_get .return_value = mock_response
225-
224+
226225 result = await read_content .fn (path = safe_path )
227226
228227 assert isinstance (result , dict )
229228 # Should NOT contain security error message
230- assert result ["type" ] != "error" or "paths must stay within project boundaries" not in result .get ("error" , "" )
229+ assert result [
230+ "type"
231+ ] != "error" or "paths must stay within project boundaries" not in result .get (
232+ "error" , ""
233+ )
231234
232235
233236class TestReadContentFunctionality :
@@ -246,13 +249,10 @@ async def test_read_content_text_file_success(self, client):
246249 # Mock the API call to simulate reading the file
247250 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
248251 mock_response = MagicMock ()
249- mock_response .headers = {
250- "content-type" : "text/markdown" ,
251- "content-length" : "100"
252- }
252+ mock_response .headers = {"content-type" : "text/markdown" , "content-length" : "100" }
253253 mock_response .text = "# Test Document\n This is test content for reading."
254254 mock_call_get .return_value = mock_response
255-
255+
256256 result = await read_content .fn (path = "docs/test-document.md" )
257257
258258 assert isinstance (result , dict )
@@ -267,16 +267,16 @@ async def test_read_content_image_file_handling(self, client):
267267 # Mock the API call to simulate reading an image
268268 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
269269 # Create a simple fake image data
270- fake_image_data = b' \x89 PNG\r \n \x1a \n \x00 \x00 \x00 \r IHDR\x00 \x00 \x00 \x01 \x00 \x00 \x00 \x01 \x08 \x06 \x00 \x00 \x00 \x1f \x15 \xc4 \x89 \x00 \x00 \x00 \r IDATx\x9c c\x00 \x01 \x00 \x00 \x05 \x00 \x01 \r \n -\xdb \x00 \x00 \x00 \x00 IEND\xae B`\x82 '
271-
270+ fake_image_data = b" \x89 PNG\r \n \x1a \n \x00 \x00 \x00 \r IHDR\x00 \x00 \x00 \x01 \x00 \x00 \x00 \x01 \x08 \x06 \x00 \x00 \x00 \x1f \x15 \xc4 \x89 \x00 \x00 \x00 \r IDATx\x9c c\x00 \x01 \x00 \x00 \x05 \x00 \x01 \r \n -\xdb \x00 \x00 \x00 \x00 IEND\xae B`\x82 "
271+
272272 mock_response = MagicMock ()
273273 mock_response .headers = {
274274 "content-type" : "image/png" ,
275- "content-length" : str (len (fake_image_data ))
275+ "content-length" : str (len (fake_image_data )),
276276 }
277277 mock_response .content = fake_image_data
278278 mock_call_get .return_value = mock_response
279-
279+
280280 # Mock PIL Image processing
281281 with patch ("basic_memory.mcp.tools.read_content.PILImage" ) as mock_pil :
282282 mock_img = MagicMock ()
@@ -285,10 +285,10 @@ async def test_read_content_image_file_handling(self, client):
285285 mock_img .mode = "RGB"
286286 mock_img .getbands .return_value = ["R" , "G" , "B" ]
287287 mock_pil .open .return_value = mock_img
288-
288+
289289 with patch ("basic_memory.mcp.tools.read_content.optimize_image" ) as mock_optimize :
290290 mock_optimize .return_value = b"optimized_image_data"
291-
291+
292292 result = await read_content .fn (path = "assets/safe-image.png" )
293293
294294 assert isinstance (result , dict )
@@ -302,24 +302,22 @@ async def test_read_content_with_project_parameter(self, client):
302302 """Test reading content with explicit project parameter."""
303303 # Mock the API call and project configuration
304304 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
305- with patch ("basic_memory.mcp.tools.read_content.get_active_project" ) as mock_get_project :
305+ with patch (
306+ "basic_memory.mcp.tools.read_content.get_active_project"
307+ ) as mock_get_project :
306308 # Mock project configuration
307309 mock_project = MagicMock ()
308310 mock_project .project_url = "http://test"
309311 mock_project .home = Path ("/test/project" )
310312 mock_get_project .return_value = mock_project
311-
313+
312314 mock_response = MagicMock ()
313- mock_response .headers = {
314- "content-type" : "text/plain" ,
315- "content-length" : "50"
316- }
315+ mock_response .headers = {"content-type" : "text/plain" , "content-length" : "50" }
317316 mock_response .text = "Project-specific content"
318317 mock_call_get .return_value = mock_response
319-
318+
320319 result = await read_content .fn (
321- path = "notes/project-file.txt" ,
322- project = "specific-project"
320+ path = "notes/project-file.txt" , project = "specific-project"
323321 )
324322
325323 assert isinstance (result , dict )
@@ -332,7 +330,7 @@ async def test_read_content_nonexistent_file_handling(self, client):
332330 # Mock API call to return 404
333331 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
334332 mock_call_get .side_effect = Exception ("File not found" )
335-
333+
336334 # This should pass security validation but fail on API call
337335 try :
338336 result = await read_content .fn (path = "docs/nonexistent-file.md" )
@@ -348,15 +346,15 @@ async def test_read_content_binary_file_handling(self, client):
348346 # Mock the API call to simulate reading a binary file
349347 with patch ("basic_memory.mcp.tools.read_content.call_get" ) as mock_call_get :
350348 binary_data = b"Binary file content with special bytes: \x00 \x01 \x02 \x03 "
351-
349+
352350 mock_response = MagicMock ()
353351 mock_response .headers = {
354352 "content-type" : "application/octet-stream" ,
355- "content-length" : str (len (binary_data ))
353+ "content-length" : str (len (binary_data )),
356354 }
357355 mock_response .content = binary_data
358356 mock_call_get .return_value = mock_response
359-
357+
360358 result = await read_content .fn (path = "files/safe-binary.bin" )
361359
362360 assert isinstance (result , dict )
@@ -399,14 +397,14 @@ async def test_read_content_url_encoded_attacks(self, client):
399397 for attack_path in encoded_attacks :
400398 try :
401399 result = await read_content .fn (path = attack_path )
402-
400+
403401 # These may or may not be blocked depending on URL decoding,
404402 # but should not cause security issues
405403 assert isinstance (result , dict )
406-
404+
407405 # If not blocked by security validation, may fail at API level
408406 # which is also acceptable
409-
407+
410408 except Exception :
411409 # Exception due to API failure or other issues is acceptable
412410 # as long as no actual traversal occurs
@@ -435,7 +433,7 @@ async def test_read_content_very_long_attack_path(self, client):
435433 """Test handling of very long attack paths."""
436434 # Create a very long path traversal attack
437435 long_attack = "../" * 1000 + "etc/passwd"
438-
436+
439437 result = await read_content .fn (path = long_attack )
440438
441439 assert isinstance (result , dict )
@@ -458,4 +456,4 @@ async def test_read_content_case_variations_attacks(self, client):
458456
459457 assert isinstance (result , dict )
460458 assert result ["type" ] == "error"
461- assert "paths must stay within project boundaries" in result ["error" ]
459+ assert "paths must stay within project boundaries" in result ["error" ]
0 commit comments