"""Tests for scratchpad.py. Refs: Epic #367, Sub-issue #372 """ from __future__ import annotations import json import os import sys from pathlib import Path from unittest.mock import patch import pytest sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from mempalace.scratchpad import ( write_scratch, read_scratch, delete_scratch, list_sessions, clear_session, _scratch_path, ) @pytest.fixture def scratch_dir(tmp_path): """Provide a temporary scratchpad directory.""" with patch("mempalace.scratchpad.SCRATCHPAD_DIR", tmp_path): yield tmp_path class TestScratchPath: def test_sanitizes_session_id(self): path = _scratch_path("safe-id_123") assert "safe-id_123.json" in str(path) def test_strips_dangerous_chars(self): path = _scratch_path("../../etc/passwd") assert ".." not in path.name assert "/" not in path.name # Dots are stripped, so only alphanumeric chars remain assert path.name == "etcpasswd.json" class TestWriteAndRead: def test_write_then_read(self, scratch_dir): write_scratch("sess1", "note", "hello world") result = read_scratch("sess1", "note") assert "note" in result assert result["note"]["value"] == "hello world" def test_read_all_keys(self, scratch_dir): write_scratch("sess1", "a", 1) write_scratch("sess1", "b", 2) result = read_scratch("sess1") assert "a" in result assert "b" in result def test_read_missing_key(self, scratch_dir): write_scratch("sess1", "exists", "yes") result = read_scratch("sess1", "missing") assert result == {} def test_read_missing_session(self, scratch_dir): result = read_scratch("nonexistent") assert result == {} def test_overwrite_key(self, scratch_dir): write_scratch("sess1", "key", "v1") write_scratch("sess1", "key", "v2") result = read_scratch("sess1", "key") assert result["key"]["value"] == "v2" class TestDelete: def test_delete_existing_key(self, scratch_dir): write_scratch("sess1", "key", "val") assert delete_scratch("sess1", "key") is True assert read_scratch("sess1", "key") == {} def test_delete_missing_key(self, scratch_dir): write_scratch("sess1", "other", "val") assert delete_scratch("sess1", "missing") is False class TestListSessions: def test_lists_sessions(self, scratch_dir): write_scratch("alpha", "k", "v") write_scratch("beta", "k", "v") sessions = list_sessions() assert "alpha" in sessions assert "beta" in sessions def test_empty_directory(self, scratch_dir): assert list_sessions() == [] class TestClearSession: def test_clears_existing(self, scratch_dir): write_scratch("sess1", "k", "v") assert clear_session("sess1") is True assert read_scratch("sess1") == {} def test_clear_nonexistent(self, scratch_dir): assert clear_session("ghost") is False