MP-1 (#368): Port PalaceRoom + Mempalace classes with 22 unit tests MP-2 (#369): L0-L5 retrieval order enforcer with recall-query detection MP-5 (#372): Wake-up protocol (300-900 token context), session scratchpad Modules: - mempalace.py: PalaceRoom + Mempalace dataclasses, factory constructors - retrieval_enforcer.py: Layered memory retrieval (identity → palace → scratch → gitea → skills) - wakeup.py: Session wake-up with caching (5min TTL) - scratchpad.py: JSON-based session notes with palace promotion All 65 tests pass. Pure stdlib + graceful degradation for ONNX issues (#373).
101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
"""Tests for wakeup.py.
|
|
|
|
Refs: Epic #367, Sub-issue #372
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
|
|
|
from mempalace.wakeup import (
|
|
palace_wakeup,
|
|
fleet_status_summary,
|
|
_load_identity,
|
|
_palace_context,
|
|
)
|
|
|
|
|
|
class TestLoadIdentity:
|
|
def test_loads_identity(self, tmp_path):
|
|
f = tmp_path / "identity.txt"
|
|
f.write_text("I am Timmy. A sovereign AI.")
|
|
with patch("mempalace.wakeup.IDENTITY_PATH", f):
|
|
result = _load_identity()
|
|
assert "Timmy" in result
|
|
|
|
def test_missing_identity(self, tmp_path):
|
|
f = tmp_path / "nope.txt"
|
|
with patch("mempalace.wakeup.IDENTITY_PATH", f):
|
|
assert _load_identity() == ""
|
|
|
|
|
|
class TestFleetStatus:
|
|
def test_reads_fleet_json(self, tmp_path):
|
|
f = tmp_path / "fleet_status.json"
|
|
f.write_text(json.dumps({
|
|
"Groq": {"state": "active", "last_seen": "2026-04-07"},
|
|
"Ezra": {"state": "idle", "last_seen": "2026-04-06"},
|
|
}))
|
|
with patch("mempalace.wakeup.FLEET_STATUS_PATH", f):
|
|
result = fleet_status_summary()
|
|
assert "Fleet Status" in result
|
|
assert "Groq" in result
|
|
assert "active" in result
|
|
|
|
def test_missing_fleet_file(self, tmp_path):
|
|
f = tmp_path / "nope.json"
|
|
with patch("mempalace.wakeup.FLEET_STATUS_PATH", f):
|
|
assert fleet_status_summary() == ""
|
|
|
|
def test_invalid_json(self, tmp_path):
|
|
f = tmp_path / "bad.json"
|
|
f.write_text("not json")
|
|
with patch("mempalace.wakeup.FLEET_STATUS_PATH", f):
|
|
assert fleet_status_summary() == ""
|
|
|
|
|
|
class TestPalaceWakeup:
|
|
def test_generates_context_with_identity(self, tmp_path):
|
|
identity = tmp_path / "identity.txt"
|
|
identity.write_text("I am Timmy.")
|
|
cache = tmp_path / "cache.txt"
|
|
with patch("mempalace.wakeup.IDENTITY_PATH", identity), \
|
|
patch("mempalace.wakeup.WAKEUP_CACHE_PATH", cache), \
|
|
patch("mempalace.wakeup._palace_context", return_value=""), \
|
|
patch("mempalace.wakeup.fleet_status_summary", return_value=""):
|
|
result = palace_wakeup(force=True)
|
|
assert "Identity" in result
|
|
assert "Timmy" in result
|
|
assert "Session" in result
|
|
|
|
def test_uses_cache_when_fresh(self, tmp_path):
|
|
cache = tmp_path / "cache.txt"
|
|
cache.write_text("cached wake-up content")
|
|
# Touch the file so it's fresh
|
|
with patch("mempalace.wakeup.WAKEUP_CACHE_PATH", cache), \
|
|
patch("mempalace.wakeup.WAKEUP_CACHE_TTL", 9999):
|
|
result = palace_wakeup(force=False)
|
|
assert result == "cached wake-up content"
|
|
|
|
def test_force_bypasses_cache(self, tmp_path):
|
|
cache = tmp_path / "cache.txt"
|
|
cache.write_text("stale content")
|
|
identity = tmp_path / "identity.txt"
|
|
identity.write_text("I am Timmy.")
|
|
with patch("mempalace.wakeup.WAKEUP_CACHE_PATH", cache), \
|
|
patch("mempalace.wakeup.IDENTITY_PATH", identity), \
|
|
patch("mempalace.wakeup._palace_context", return_value=""), \
|
|
patch("mempalace.wakeup.fleet_status_summary", return_value=""):
|
|
result = palace_wakeup(force=True)
|
|
assert "Identity" in result
|
|
assert "stale content" not in result
|