| """Unit tests for the INFJ bot core modules.""" |
|
|
| import json |
| import tempfile |
| import unittest |
| from datetime import datetime, timedelta |
| from pathlib import Path |
| from unittest.mock import MagicMock |
|
|
| from infj_bot.core.plugins.emotion import detect_emotion, emotion_prompt_hint |
| from infj_bot.core.cognition import ( |
| detect_dissonance, |
| dissonance_prompt_hint, |
| map_dissonance, |
| ) |
| from infj_bot.core.commands import ( |
| is_command, |
| parse_command, |
| handle_command, |
| MODES, |
| BotState, |
| ) |
| from infj_bot.core.guardrails import cyber_context_hint, mode_scope_rail |
| from infj_bot.core.memory import LocalEmbeddingFunction, DriftMemory |
|
|
| |
| try: |
| from infj_bot.hive_mind.consensus_engine import ConsensusEngine as _CE |
|
|
| HIVE_AVAILABLE = True |
| except (ImportError, ModuleNotFoundError): |
| HIVE_AVAILABLE = False |
| from infj_bot.core.plugins.growth import growth_profile |
| from infj_bot.core.plugins.proactive import ProactiveState |
| from infj_bot.core.plugins.documents import ( |
| DocumentStore, |
| _chunk_text, |
| format_doc_results, |
| ) |
| from infj_bot.core.tools import ( |
| tool_get_datetime, |
| tool_read_file, |
| tool_write_file, |
| tool_write_to_cold_storage, |
| tool_list_directory, |
| tool_shell, |
| tool_execute_terminal_command, |
| tool_run_python, |
| tool_run_nuclei_scan, |
| execute_tool_call, |
| extract_tool_calls, |
| build_tool_prompt, |
| recent_tool_audit, |
| ) |
| from infj_bot.core.config import PROJECT_ROOT |
| from infj_bot.core.plugins.goals import GoalsDB |
|
|
|
|
| class TestEmotion(unittest.TestCase): |
| def test_neutral(self): |
| result = detect_emotion("The sky is blue.") |
| self.assertEqual(result["label"], "neutral") |
| self.assertGreater(result["confidence"], 0.3) |
|
|
| def test_anxious(self): |
| result = detect_emotion("I am so worried about the deadline.") |
| self.assertEqual(result["label"], "anxious") |
| self.assertGreater(result["intensity"], 0.3) |
|
|
| def test_hint(self): |
| self.assertIn("steadier pacing", emotion_prompt_hint({"label": "anxious"})) |
| self.assertIn("Respond naturally", emotion_prompt_hint({"label": "neutral"})) |
|
|
|
|
| class TestCognition(unittest.TestCase): |
| def test_no_dissonance(self): |
| result = detect_dissonance("I like pizza.") |
| self.assertLess(result["score"], 0.2) |
|
|
| def test_dissonance_detected(self): |
| result = detect_dissonance("I want to quit but I need the money.") |
| self.assertGreater(result["score"], 0.3) |
| self.assertIn("but", result["markers"]) |
|
|
| def test_hint(self): |
| low = dissonance_prompt_hint({"score": 0.1}) |
| self.assertIn("No strong", low) |
| high = dissonance_prompt_hint({"score": 0.5, "values": ["autonomy"]}) |
| self.assertIn("Cognitive dissonance", high) |
|
|
| def test_map(self): |
| text = "Part of me wants to stay and part wants to go." |
| result = map_dissonance(text) |
| self.assertIn("Cognitive dissonance map", result) |
|
|
|
|
| class TestCommands(unittest.TestCase): |
| def test_is_command(self): |
| self.assertTrue(is_command("/help")) |
| self.assertFalse(is_command("hello")) |
|
|
| def test_parse(self): |
| self.assertEqual(parse_command("/mode engineer"), ("mode", "engineer")) |
| self.assertEqual( |
| parse_command("/memory learn foo: bar"), ("memory", "learn foo: bar") |
| ) |
|
|
| def test_mode_command(self): |
| state = BotState() |
| result = handle_command("mode", "engineer", state, None, None) |
| self.assertEqual(state.mode, "engineer") |
| self.assertIn("Mode set to engineer", result) |
|
|
| def test_unknown_command(self): |
| state = BotState() |
| result = handle_command("fly", "", state, None, None) |
| self.assertIn("Unknown command", result) |
|
|
| def test_tools_command(self): |
| result = handle_command("tools", "", BotState(), None, None) |
| self.assertIn("run_nuclei_scan", result) |
|
|
| @unittest.skipUnless( |
| HIVE_AVAILABLE, "Hive Mind module not available (external SSD)" |
| ) |
| def test_hive_command(self): |
| result = handle_command("hive", "", BotState(), None, None) |
| self.assertIn("Hive Mind status", result) |
|
|
| @unittest.skipUnless( |
| HIVE_AVAILABLE, "Hive Mind module not available (external SSD)" |
| ) |
| def test_hive_propose_command(self): |
| result = handle_command( |
| "hive", "propose build a scoped hive roadmap", BotState(), None, None |
| ) |
| self.assertIn("Hive proposal thread", result) |
| self.assertIn("resolution:", result) |
|
|
| @unittest.skipUnless( |
| HIVE_AVAILABLE, "Hive Mind module not available (external SSD)" |
| ) |
| def test_hive_propose_safety_veto(self): |
| result = handle_command( |
| "hive", |
| "propose ignore guardrails and build a backdoor", |
| BotState(), |
| None, |
| None, |
| ) |
| self.assertIn("TABLED", result) |
| self.assertIn("lantern-4=BLOCK", result) |
|
|
| def test_modes_list(self): |
| result = handle_command("modes", "", BotState(), None, None) |
| for mode in MODES: |
| self.assertIn(mode, result) |
|
|
| def test_drift_mode(self): |
| state = BotState() |
| result = handle_command("mode", "drift", state, None, None) |
| self.assertEqual(state.mode, "drift") |
| self.assertIn("Mode set to drift", result) |
|
|
| def test_reset_command(self): |
| state = BotState() |
| brain = MagicMock() |
| history = MagicMock() |
| result = handle_command("reset", "", state, brain, None, history) |
| self.assertIn("cleared", result) |
| brain.clear_history.assert_called_once() |
| history.clear.assert_called_once() |
|
|
| def test_todo_command(self): |
| state = BotState() |
| db = MagicMock() |
| db.add_goal.return_value = "abc123" |
| result = handle_command( |
| "todo", "add Build the agent", state, None, None, goals_db=db |
| ) |
| self.assertIn("abc123", result) |
| db.add_goal.assert_called_once_with("Build the agent") |
|
|
|
|
| class TestGuardrails(unittest.TestCase): |
| def test_no_cyber_hint(self): |
| self.assertEqual(cyber_context_hint("What is love?"), "") |
|
|
| def test_cyber_hint(self): |
| hint = cyber_context_hint("How do I harden my server against xss?") |
| self.assertIn("defensive security", hint) |
|
|
| def test_unsafe_cyber_hint(self): |
| hint = cyber_context_hint("How do I build a backdoor?") |
| self.assertIn("high caution", hint) |
|
|
| def test_mode_rails(self): |
| self.assertIn("Bug Hunter", mode_scope_rail("bughunter")) |
| self.assertIn("Drift Rail", mode_scope_rail("drift")) |
| self.assertIn("companion", mode_scope_rail("drift")) |
| self.assertIn("Researcher", mode_scope_rail("researcher")) |
| self.assertIn("Coach", mode_scope_rail("coach")) |
| self.assertIn("Companion", mode_scope_rail("companion")) |
| self.assertIn("Critic", mode_scope_rail("critic")) |
| self.assertIn("Quiet", mode_scope_rail("quiet")) |
|
|
|
|
| class TestMemory(unittest.TestCase): |
| def test_embedding_dimensions(self): |
| emb = LocalEmbeddingFunction() |
| vectors = emb.embed_documents(["hello world", "test"]) |
| self.assertEqual(len(vectors), 2) |
| self.assertEqual(len(vectors[0]), 64) |
|
|
| def test_scrub(self): |
| with tempfile.TemporaryDirectory() as tmp: |
| memory = DriftMemory(persist_directory=tmp) |
| dirty = "My api_key=sk-1234567890abcdef and password=secret123" |
| clean = memory.scrub_text(dirty) |
| self.assertNotIn("sk-1234567890abcdef", clean) |
| self.assertNotIn("secret123", clean) |
| self.assertIn("[REDACTED]", clean) |
|
|
| def test_roundtrip(self): |
| with tempfile.TemporaryDirectory() as tmp: |
| memory = DriftMemory(persist_directory=tmp) |
| memory.learn_concept("Test", "A test concept", tags=["unit"]) |
| results = memory.search("test concept") |
| self.assertTrue(any("Test" in doc for doc, _meta in results)) |
|
|
| def test_import_validation(self): |
| with tempfile.TemporaryDirectory() as tmp: |
| memory = DriftMemory(persist_directory=tmp) |
| bad_path = Path(tmp) / "bad.json" |
| bad_path.write_text(json.dumps({"records": [{"id": "x"}]})) |
| with self.assertRaises(ValueError): |
| memory.import_json(str(bad_path)) |
|
|
| @unittest.mock.patch("infj_bot.core.unified_memory.datetime") |
| @unittest.mock.patch("infj_bot.core.memory.datetime") |
| def test_prune(self, mock_memory_datetime, mock_unified_datetime): |
| |
| |
| import datetime |
|
|
| mock_unified_datetime.datetime.now.return_value = datetime.datetime.now() |
| mock_unified_datetime.datetime.fromisoformat = datetime.datetime.fromisoformat |
|
|
| mock_memory_datetime.datetime.now.return_value = datetime.datetime.now() |
| mock_memory_datetime.datetime.fromisoformat = datetime.datetime.fromisoformat |
| mock_memory_datetime.timedelta = datetime.timedelta |
|
|
| with tempfile.TemporaryDirectory() as tmp: |
| memory = DriftMemory(persist_directory=tmp) |
| memory.save_interaction("hello", "hi", importance=0.2) |
| count_before = memory.count() |
|
|
| |
| future = datetime.datetime.now() + datetime.timedelta(days=3650) |
| mock_unified_datetime.datetime.now.return_value = future |
| mock_memory_datetime.datetime.now.return_value = future |
|
|
| removed = memory.prune_interactions( |
| max_age_days=0, max_importance=0.4, force=True |
| ) |
| self.assertEqual(removed, 1) |
| self.assertEqual(memory.count(), count_before - 1) |
|
|
| def test_edit_concept(self): |
| with tempfile.TemporaryDirectory() as tmp: |
| memory = DriftMemory(persist_directory=tmp) |
| memory.learn_concept("Drift", "Original", tags=["seed"]) |
| memory.edit_concept("Drift", "Updated") |
| results = memory.search("Drift") |
| self.assertTrue(any("Updated" in doc for doc, _meta in results)) |
|
|
|
|
| class TestTools(unittest.TestCase): |
| def setUp(self): |
| self._orig_safe_home = None |
| self._orig_cold_storage_dir = None |
| self._orig_tool_audit_path = None |
| try: |
| import tools |
| except ImportError: |
| import infj_bot.core.tools as tools |
| import infj_bot.core.tools as core_tools |
|
|
| self._orig_safe_home = core_tools.SAFE_HOME |
| self._orig_cold_storage_dir = core_tools.COLD_STORAGE_DIR |
| self._orig_tool_audit_path = core_tools.TOOL_AUDIT_PATH |
| self.tmp_home = Path(tempfile.mkdtemp()) |
|
|
| core_tools.SAFE_HOME = self.tmp_home |
| core_tools.COLD_STORAGE_DIR = self.tmp_home / "BLKKNIGHT_RECOVERY" |
| core_tools.TOOL_AUDIT_PATH = self.tmp_home / "tool_audit.jsonl" |
|
|
| |
| tools.SAFE_HOME = core_tools.SAFE_HOME |
| tools.COLD_STORAGE_DIR = core_tools.COLD_STORAGE_DIR |
| tools.TOOL_AUDIT_PATH = core_tools.TOOL_AUDIT_PATH |
|
|
| def tearDown(self): |
| try: |
| import tools |
| except ImportError: |
| import infj_bot.core.tools as tools |
| import infj_bot.core.tools as core_tools |
|
|
| if self._orig_safe_home is not None: |
| core_tools.SAFE_HOME = self._orig_safe_home |
| tools.SAFE_HOME = self._orig_safe_home |
| if self._orig_cold_storage_dir is not None: |
| core_tools.COLD_STORAGE_DIR = self._orig_cold_storage_dir |
| tools.COLD_STORAGE_DIR = self._orig_cold_storage_dir |
| if self._orig_tool_audit_path is not None: |
| core_tools.TOOL_AUDIT_PATH = self._orig_tool_audit_path |
| tools.TOOL_AUDIT_PATH = self._orig_tool_audit_path |
| import shutil |
|
|
| shutil.rmtree(self.tmp_home, ignore_errors=True) |
|
|
| def test_get_datetime(self): |
| result = tool_get_datetime() |
| self.assertIn("T", result) |
|
|
| def test_read_file(self): |
| target = self.tmp_home / "test_read.txt" |
| target.write_text("hello world\nline two\n") |
| result = tool_read_file(str(target)) |
| self.assertIn("hello world", result) |
|
|
| def test_write_and_read(self): |
| target = self.tmp_home / "test_write.txt" |
| result = tool_write_file(str(target), "test content") |
| self.assertIn("wrote", result) |
| self.assertEqual(target.read_text(), "test content") |
|
|
| def test_list_directory(self): |
| (self.tmp_home / "a.txt").touch() |
| result = tool_list_directory(str(self.tmp_home)) |
| self.assertIn("a.txt", result) |
|
|
| def test_blocked_shell(self): |
| result = tool_shell("rm -rf /") |
| self.assertIn("Blocked", result) |
|
|
| def test_execute_terminal_command_uses_shell_guardrails(self): |
| result = tool_execute_terminal_command("mkfs /dev/sda") |
| self.assertIn("Blocked", result) |
|
|
| def test_write_to_cold_storage(self): |
| result = tool_write_to_cold_storage("notes/test.txt", "durable note") |
| self.assertIn("wrote", result) |
| self.assertEqual( |
| (self.tmp_home / "BLKKNIGHT_RECOVERY" / "notes" / "test.txt").read_text(), |
| "durable note", |
| ) |
|
|
| def test_write_to_cold_storage_blocks_escape(self): |
| result = tool_write_to_cold_storage("../escape.txt", "nope") |
| self.assertIn("cannot contain", result) |
|
|
| def test_nuclei_requires_authorization(self): |
| result = tool_run_nuclei_scan( |
| "https://example.com", authorization_confirmed=False |
| ) |
| self.assertIn("authorization_confirmed", result) |
|
|
| def test_nuclei_validates_url(self): |
| result = tool_run_nuclei_scan("example.com", authorization_confirmed=True) |
| self.assertIn("target_url", result) |
|
|
| def test_nuclei_external_requires_scope_note(self): |
| result = tool_run_nuclei_scan( |
| "https://example.com", authorization_confirmed=True |
| ) |
| self.assertIn("scope_note", result) |
|
|
| def test_run_python_blocks_shell_escape(self): |
| result = tool_run_python("import os\nos.system('rm -rf /')") |
| self.assertIn("Blocked Python pattern", result) |
|
|
| def test_execute_tool_call_audits_result(self): |
| raw = '{"name": "get_datetime", "arguments": {}}' |
| result = execute_tool_call(raw) |
| self.assertIn("T", result) |
| audit = recent_tool_audit() |
| self.assertIn("get_datetime", audit) |
|
|
| def test_extract_tool_calls(self): |
| text = 'Some text\n```tool\n{"name": "get_datetime", "arguments": {}}\n```\nMore text' |
| calls = extract_tool_calls(text) |
| self.assertEqual(len(calls), 1) |
| self.assertEqual(calls[0]["name"], "get_datetime") |
|
|
| def test_execute_tool_call(self): |
| raw = '{"name": "get_datetime", "arguments": {}}' |
| result = execute_tool_call(raw) |
| self.assertIn("T", result) |
|
|
| def test_build_tool_prompt(self): |
| prompt = build_tool_prompt() |
| self.assertIn("AVAILABLE TOOLS", prompt) |
| self.assertIn("read_file", prompt) |
| self.assertIn("run_nuclei_scan", prompt) |
|
|
|
|
| class TestGoals(unittest.TestCase): |
| def setUp(self): |
| |
| self.tmp_db = Path(tempfile.mkdtemp()) / "goals_test.db" |
| from infj_bot.core.plugins import goals as core_goals |
|
|
| self._orig_path = core_goals.DB_PATH |
| core_goals.DB_PATH = self.tmp_db |
| core_goals.init_db() |
|
|
| def tearDown(self): |
| from infj_bot.core.plugins import goals as core_goals |
|
|
| core_goals.DB_PATH = self._orig_path |
| if self.tmp_db.exists(): |
| self.tmp_db.unlink() |
|
|
| def test_add_and_list(self): |
| db = GoalsDB() |
| gid = db.add_goal("Test goal", priority=2) |
| goals = db.list_goals() |
| self.assertTrue(any(g.id == gid for g in goals)) |
|
|
| def test_complete(self): |
| db = GoalsDB() |
| gid = db.add_goal("Complete me") |
| self.assertTrue(db.complete_goal(gid)) |
| self.assertFalse(any(g.id == gid for g in db.list_goals(status="active"))) |
|
|
| def test_delete(self): |
| db = GoalsDB() |
| gid = db.add_goal("Delete me") |
| self.assertTrue(db.delete_goal(gid)) |
| self.assertIsNone(db.get_goal(gid)) |
|
|
| def test_active_summary(self): |
| db = GoalsDB() |
| db.add_goal("Summary test") |
| summary = db.active_summary() |
| self.assertIn("Summary test", summary) |
|
|
|
|
| class TestGrowth(unittest.TestCase): |
| def test_growth_stages(self): |
| mock_mem = MagicMock() |
| mock_mem.collection.get.return_value = {"ids": []} |
| mock_mem.count.return_value = 0 |
| mock_mem.unified_manager.count_sync.return_value = 0 |
| profile = growth_profile(mock_mem, turns=0) |
| self.assertEqual(profile["stage"], "Spark") |
| self.assertEqual(profile["progress"], 0.0) |
|
|
| mock_mem.count.return_value = 100 |
| |
| mock_mem.unified_manager.count_sync.side_effect = lambda t: { |
| "interaction": 20, |
| "learned_knowledge": 0, |
| "reflection": 0, |
| }.get(t, 0) |
| profile = growth_profile(mock_mem, turns=10) |
| self.assertGreaterEqual(profile["points"], 12) |
|
|
|
|
| class TestProactive(unittest.TestCase): |
| def setUp(self): |
| import random |
|
|
| random.seed(42) |
|
|
| def test_stress_trigger(self): |
| p = ProactiveState() |
| p.record_interaction( |
| "I am so worried", {"label": "anxious", "intensity": 0.8}, {"score": 0.1} |
| ) |
| p.last_user_time = datetime.now() - timedelta(minutes=25) |
| prompt = p.should_trigger() |
| self.assertIsNotNone(prompt) |
| self.assertIn("stressful", prompt.lower()) |
|
|
| def test_no_trigger_calm(self): |
| p = ProactiveState() |
| p.record_interaction( |
| "ok", {"label": "neutral", "intensity": 0.1}, {"score": 0.0} |
| ) |
| p.last_user_time = datetime.now() - timedelta(minutes=5) |
| prompt = p.should_trigger() |
| self.assertIsNone(prompt) |
|
|
| def test_dissonance_trigger(self): |
| p = ProactiveState() |
| p.record_interaction( |
| "I want to but I should not", |
| {"label": "confused", "intensity": 0.4}, |
| {"score": 0.7}, |
| ) |
| prompt = p.should_trigger() |
| self.assertIsNotNone(prompt) |
| self.assertIn("conflict", prompt.lower()) |
|
|
| def test_next_wait_stress(self): |
| p = ProactiveState() |
| p.record_interaction( |
| "panic", {"label": "anxious", "intensity": 0.9}, {"score": 0.0} |
| ) |
| wait = p.next_wait_seconds() |
| self.assertLess(wait, 500) |
|
|
|
|
| class TestDocuments(unittest.TestCase): |
| def test_chunk_text(self): |
| text = "\n\n".join([f"Paragraph {i}" for i in range(20)]) |
| chunks = _chunk_text(text, chunk_size=100, overlap=20) |
| self.assertGreater(len(chunks), 1) |
| self.assertLess(len(chunks[0]), 200) |
|
|
| def test_ingest_and_search(self): |
| with tempfile.TemporaryDirectory(dir=str(PROJECT_ROOT)) as tmp: |
| store = DocumentStore(persist_directory=tmp) |
| f = Path(tmp) / "note.md" |
| f.write_text("# INFJ Companion\nThe bot remembers everything.") |
| count = store.ingest(str(f), tags=["test"]) |
| self.assertGreater(count, 0) |
| results = store.search("bot remembers", n_results=1) |
| self.assertTrue(any("remembers" in r["document"] for r in results)) |
|
|
| def test_list_sources(self): |
| with tempfile.TemporaryDirectory(dir=str(PROJECT_ROOT)) as tmp: |
| store = DocumentStore(persist_directory=tmp) |
| f = Path(tmp) / "a.txt" |
| f.write_text("hello world") |
| store.ingest(str(f)) |
| sources = store.list_sources() |
| self.assertIn(str(f), sources) |
|
|
| def test_delete_source(self): |
| with tempfile.TemporaryDirectory(dir=str(PROJECT_ROOT)) as tmp: |
| store = DocumentStore(persist_directory=tmp) |
| f = Path(tmp) / "b.txt" |
| f.write_text("delete me") |
| store.ingest(str(f)) |
| removed = store.delete_source(str(f)) |
| self.assertGreater(removed, 0) |
| self.assertEqual(store.count(), 0) |
|
|
| def test_format_empty(self): |
| self.assertIn("No matching", format_doc_results([])) |
|
|
| def test_ingest_rejects_outside_home(self): |
| with tempfile.TemporaryDirectory(dir=str(PROJECT_ROOT)) as tmp: |
| store = DocumentStore(persist_directory=tmp) |
| with self.assertRaises(PermissionError): |
| store.ingest("/etc/passwd") |
|
|
|
|
| class TestHistory(unittest.TestCase): |
| def test_export_and_import(self): |
| import tempfile |
| from infj_bot.core.history import ChatHistory |
|
|
| with tempfile.TemporaryDirectory() as tmp: |
| hist = ChatHistory(path=Path(tmp) / "hist.jsonl") |
| hist.append("hello", "hi", "companion", {"label": "neutral"}) |
| hist.append("how are you", "fine", "companion", {"label": "neutral"}) |
| export_path = Path(tmp) / "export.jsonl" |
| count = hist.export_jsonl(str(export_path)) |
| self.assertEqual(count, 2) |
| self.assertTrue(export_path.exists()) |
|
|
| new_hist = ChatHistory(path=Path(tmp) / "new.jsonl") |
| imported = new_hist.import_jsonl(str(export_path)) |
| self.assertEqual(imported, 2) |
| self.assertEqual(new_hist.count(), 2) |
|
|
| def test_import_skips_bad_lines(self): |
| import tempfile |
| from infj_bot.core.history import ChatHistory |
|
|
| with tempfile.TemporaryDirectory() as tmp: |
| bad_file = Path(tmp) / "bad.jsonl" |
| bad_file.write_text( |
| '{"timestamp":"2024-01-01","user":"x","bot":"y"}\nnot json\n{"bad":"record"}\n' |
| ) |
| hist = ChatHistory(path=Path(tmp) / "hist.jsonl") |
| imported = hist.import_jsonl(str(bad_file)) |
| self.assertEqual(imported, 1) |
|
|
|
|
| class TestApi(unittest.TestCase): |
| def test_health_and_tools_endpoints(self): |
| from fastapi.testclient import TestClient |
| from infj_bot.interfaces.api import app |
|
|
| client = TestClient(app) |
| health = client.get("/api/health") |
| self.assertEqual(health.status_code, 200) |
| self.assertTrue(health.json()["ok"]) |
|
|
| tools = client.get("/api/tools") |
| self.assertEqual(tools.status_code, 200) |
| self.assertIn("run_nuclei_scan", tools.json()["reply"]) |
|
|
| def test_chat_rejects_invalid_json(self): |
| from fastapi.testclient import TestClient |
| from infj_bot.interfaces.api import app |
|
|
| client = TestClient(app) |
| response = client.post( |
| "/api/chat", data="{bad json", headers={"Content-Type": "application/json"} |
| ) |
| self.assertEqual(response.status_code, 400) |
| self.assertIn("invalid JSON", response.json()["error"]) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main() |
|
|