"""Tests for the mempalace skill. Validates PalaceRoom, Mempalace class, factory constructors, and the analyse_issues entry-point. Refs: Epic #367, Sub-issue #368 """ from __future__ import annotations import json import sys import os import time import pytest # Ensure the package is importable from the repo layout sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from mempalace.mempalace import Mempalace, PalaceRoom, analyse_issues # ── PalaceRoom unit tests ───────────────────────────────────────────────── class TestPalaceRoom: def test_store_and_retrieve(self): room = PalaceRoom(name="test", label="Test Room") room.store("key1", 42) assert room.retrieve("key1") == 42 def test_retrieve_default(self): room = PalaceRoom(name="test", label="Test Room") assert room.retrieve("missing") is None assert room.retrieve("missing", "fallback") == "fallback" def test_summary_format(self): room = PalaceRoom(name="test", label="Test Room") room.store("repos", 5) summary = room.summary() assert "## Test Room" in summary assert "repos: 5" in summary def test_contents_default_factory_isolation(self): """Each room gets its own dict — no shared mutable default.""" r1 = PalaceRoom(name="a", label="A") r2 = PalaceRoom(name="b", label="B") r1.store("x", 1) assert r2.retrieve("x") is None def test_entered_at_is_recent(self): before = time.time() room = PalaceRoom(name="t", label="T") after = time.time() assert before <= room.entered_at <= after # ── Mempalace core tests ────────────────────────────────────────────────── class TestMempalace: def test_add_and_enter_room(self): p = Mempalace(domain="test") p.add_room("r1", "Room 1") room = p.enter("r1") assert room.name == "r1" def test_enter_nonexistent_room_raises(self): p = Mempalace() with pytest.raises(KeyError, match="No room"): p.enter("ghost") def test_store_without_enter_raises(self): p = Mempalace() p.add_room("r", "R") with pytest.raises(RuntimeError, match="Enter a room"): p.store("k", "v") def test_store_and_retrieve_via_palace(self): p = Mempalace() p.add_room("r", "R") p.enter("r") p.store("count", 10) assert p.retrieve("r", "count") == 10 def test_retrieve_missing_room_returns_default(self): p = Mempalace() assert p.retrieve("nope", "key") is None assert p.retrieve("nope", "key", 99) == 99 def test_render_includes_domain(self): p = Mempalace(domain="audit") p.add_room("r", "Room") p.enter("r") p.store("item", "value") output = p.render() assert "audit" in output assert "Room" in output def test_to_dict_structure(self): p = Mempalace(domain="test") p.add_room("r", "R") p.enter("r") p.store("a", 1) d = p.to_dict() assert d["domain"] == "test" assert "elapsed_seconds" in d assert d["rooms"]["r"] == {"a": 1} def test_to_json_is_valid(self): p = Mempalace(domain="j") p.add_room("x", "X") p.enter("x") p.store("v", [1, 2, 3]) parsed = json.loads(p.to_json()) assert parsed["rooms"]["x"]["v"] == [1, 2, 3] # ── Factory constructor tests ───────────────────────────────────────────── class TestFactories: def test_for_issue_analysis_rooms(self): p = Mempalace.for_issue_analysis() assert p.domain == "issue_analysis" for key in ("repo_architecture", "assignment_status", "triage_priority", "resolution_patterns"): p.enter(key) # should not raise def test_for_health_check_rooms(self): p = Mempalace.for_health_check() assert p.domain == "health_check" for key in ("service_topology", "failure_signals", "recovery_history"): p.enter(key) def test_for_code_review_rooms(self): p = Mempalace.for_code_review() assert p.domain == "code_review" for key in ("change_scope", "risk_surface", "test_coverage", "reviewer_context"): p.enter(key) # ── analyse_issues entry-point tests ────────────────────────────────────── class TestAnalyseIssues: SAMPLE_DATA = [ {"repo": "the-nexus", "open_issues": 40, "assigned": 30, "unassigned": 10}, {"repo": "timmy-home", "open_issues": 30, "assigned": 25, "unassigned": 5}, {"repo": "hermes-agent", "open_issues": 20, "assigned": 15, "unassigned": 5}, {"repo": "empty-repo", "open_issues": 0, "assigned": 0, "unassigned": 0}, ] def test_returns_string(self): result = analyse_issues(self.SAMPLE_DATA) assert isinstance(result, str) assert len(result) > 0 def test_contains_room_headers(self): result = analyse_issues(self.SAMPLE_DATA) assert "Repository Architecture" in result assert "Assignment Status" in result def test_coverage_below_target(self): result = analyse_issues(self.SAMPLE_DATA, target_assignee_rate=0.90) assert "BELOW TARGET" in result def test_coverage_meets_target(self): good_data = [ {"repo": "a", "open_issues": 10, "assigned": 10, "unassigned": 0}, ] result = analyse_issues(good_data, target_assignee_rate=0.80) assert "OK" in result def test_empty_repos_list(self): result = analyse_issues([]) assert isinstance(result, str) def test_single_repo(self): data = [{"repo": "solo", "open_issues": 5, "assigned": 3, "unassigned": 2}] result = analyse_issues(data) assert "solo" in result or "issue_analysis" in result