"""
Phase 21: Memory Skill Engine — Comprehensive Tests

Tests:
  - Skill data model
  - Skill extraction (from observations + from patterns/playbooks)
  - Skill registry (store, retrieve, rank, filter)
  - Skill execution (suggest + auto modes)
  - Skill feedback & success tracking
  - Skill evolution (merge, deprecate, promote)
  - SDK integration
  - REST API endpoints
"""

import os
import time
import tempfile
import unittest

from openmemo.storage.sqlite_store import SQLiteStore
from openmemo.config import OpenMemoConfig
from openmemo.skill.skill_engine import (
    Skill, SkillEngine, DefaultRelevanceScorer,
    compute_skill_score,
    SKILL_STATUS_ACTIVE, SKILL_STATUS_VERIFIED, SKILL_STATUS_DEPRECATED,
    EXEC_MODE_SUGGEST, EXEC_MODE_AUTO,
)


class TestSkillDataModel(unittest.TestCase):
    def test_skill_creation(self):
        skill = Skill(
            name="debug_docker", scene="deployment",
            trigger="docker container crashed",
            steps=["check logs", "inspect env", "restart"],
            tools=["docker logs", "docker inspect"],
            confidence=0.8,
        )
        self.assertEqual(skill.name, "debug_docker")
        self.assertEqual(skill.scene, "deployment")
        self.assertEqual(len(skill.steps), 3)
        self.assertEqual(len(skill.tools), 2)
        self.assertEqual(skill.status, SKILL_STATUS_ACTIVE)
        self.assertEqual(skill.skill_version, 1)

    def test_skill_to_dict(self):
        skill = Skill(name="test_skill", scene="coding")
        d = skill.to_dict()
        self.assertEqual(d["name"], "test_skill")
        self.assertEqual(d["scene"], "coding")
        self.assertIn("steps", d)
        self.assertIn("tools", d)
        self.assertIn("confidence", d)
        self.assertIn("status", d)
        self.assertIn("skill_version", d)

    def test_skill_from_dict(self):
        data = {
            "id": "s1", "name": "test", "scene": "dev",
            "trigger": "error occurs", "steps": ["fix it"],
            "tools": ["debugger"], "confidence": 0.9,
        }
        skill = Skill.from_dict(data)
        self.assertEqual(skill.id, "s1")
        self.assertEqual(skill.name, "test")
        self.assertEqual(skill.steps, ["fix it"])
        self.assertEqual(skill.confidence, 0.9)

    def test_record_usage_success(self):
        skill = Skill(name="test")
        skill.record_usage(True)
        self.assertEqual(skill.usage_count, 1)
        self.assertEqual(skill.success_rate, 1.0)
        skill.record_usage(False)
        self.assertEqual(skill.usage_count, 2)
        self.assertEqual(skill.success_rate, 0.5)

    def test_record_usage_multiple(self):
        skill = Skill(name="test")
        for _ in range(8):
            skill.record_usage(True)
        for _ in range(2):
            skill.record_usage(False)
        self.assertEqual(skill.usage_count, 10)
        self.assertAlmostEqual(skill.success_rate, 0.8, places=2)

    def test_compute_skill_score(self):
        skill_data = {
            "confidence": 0.8, "usage_count": 20,
            "success_rate": 0.9, "updated_at": time.time(),
        }
        score = compute_skill_score(skill_data)
        self.assertGreater(score, 0.5)

    def test_compute_skill_score_zero(self):
        score = compute_skill_score({})
        self.assertEqual(score, 0.0)


class TestSkillExtraction(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.store = SQLiteStore(self.tmp.name)
        self.engine = SkillEngine(store=self.store)

    def tearDown(self):
        self.store.close()
        os.unlink(self.tmp.name)

    def test_observe_and_extract(self):
        for _ in range(5):
            self.engine.observe("docker debug", context="deployment", success=True)
        skills = self.engine.extract_skills()
        self.assertGreaterEqual(len(skills), 1)
        self.assertEqual(skills[0].name, "docker debug")

    def test_extract_from_patterns(self):
        patterns = [
            {"content": "When docker crashes, check env variables", "scene": "deployment", "confidence": 0.7},
            {"content": "Python imports fail due to path issues", "scene": "coding", "confidence": 0.6},
        ]
        skills = self.engine.extract_from_patterns(patterns)
        self.assertEqual(len(skills), 2)
        self.assertTrue(any(s.scene == "deployment" for s in skills))
        self.assertTrue(any(s.scene == "coding" for s in skills))

    def test_extract_from_playbooks(self):
        playbooks = [
            {
                "content": "Docker debug playbook",
                "scene": "deployment",
                "confidence": 0.8,
                "metadata": {"steps": ["check logs", "verify env", "restart"]},
            },
        ]
        skills = self.engine.extract_from_patterns([], playbooks)
        self.assertEqual(len(skills), 1)
        self.assertEqual(skills[0].scene, "deployment")
        self.assertEqual(len(skills[0].steps), 3)

    def test_extract_with_llm_fn(self):
        def mock_llm(prompt):
            return {
                "skill_name": "docker_debug",
                "description": "Debug docker containers",
                "scene": "deployment",
                "trigger": "docker crash",
                "steps": ["inspect", "fix", "restart"],
                "tools": ["docker"],
            }

        engine = SkillEngine(store=self.store, llm_fn=mock_llm)
        patterns = [{"content": "Docker debug pattern", "scene": "ops", "confidence": 0.9}]
        skills = engine.extract_from_patterns(patterns)
        self.assertEqual(len(skills), 1)
        self.assertEqual(skills[0].name, "docker_debug")
        self.assertEqual(skills[0].trigger, "docker crash")

    def test_extract_stores_skills(self):
        patterns = [{"content": "Test pattern", "scene": "test", "confidence": 0.5}]
        self.engine.extract_from_patterns(patterns)
        stored = self.store.list_skills()
        self.assertEqual(len(stored), 1)
        self.assertEqual(stored[0]["scene"], "test")


class TestSkillRegistry(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.store = SQLiteStore(self.tmp.name)
        self.engine = SkillEngine(store=self.store)

        self.store.put_skill(Skill(
            id="s1", name="debug_docker", scene="deployment",
            trigger="docker container crashed",
            steps=["check logs", "inspect env"],
            confidence=0.8, usage_count=10, success_rate=0.9,
        ).to_dict())
        self.store.put_skill(Skill(
            id="s2", name="fix_imports", scene="coding",
            trigger="import error python",
            steps=["check sys.path", "verify package"],
            confidence=0.7, usage_count=5, success_rate=0.8,
        ).to_dict())
        self.store.put_skill(Skill(
            id="s3", name="old_skill", scene="testing",
            trigger="old test", status=SKILL_STATUS_DEPRECATED,
            confidence=0.2, usage_count=20, success_rate=0.1,
        ).to_dict())

    def tearDown(self):
        self.store.close()
        os.unlink(self.tmp.name)

    def test_get_skill(self):
        skill = self.store.get_skill("s1")
        self.assertIsNotNone(skill)
        self.assertEqual(skill["name"], "debug_docker")
        self.assertEqual(skill["scene"], "deployment")

    def test_get_skill_not_found(self):
        skill = self.store.get_skill("nonexistent")
        self.assertIsNone(skill)

    def test_list_all_skills(self):
        skills = self.store.list_skills()
        self.assertEqual(len(skills), 3)

    def test_list_skills_by_scene(self):
        skills = self.store.list_skills(scene="deployment")
        self.assertEqual(len(skills), 1)
        self.assertEqual(skills[0]["name"], "debug_docker")

    def test_list_skills_by_status(self):
        active = self.store.list_skills(status="active")
        self.assertEqual(len(active), 2)
        deprecated = self.store.list_skills(status="deprecated")
        self.assertEqual(len(deprecated), 1)

    def test_delete_skill(self):
        result = self.store.delete_skill("s3")
        self.assertTrue(result)
        skills = self.store.list_skills()
        self.assertEqual(len(skills), 2)

    def test_retrieve_relevant_skills(self):
        skills = self.engine.get_relevant_skills("docker container crashed")
        self.assertGreater(len(skills), 0)
        self.assertEqual(skills[0].name, "debug_docker")

    def test_retrieve_excludes_deprecated(self):
        skills = self.engine.get_relevant_skills("old test")
        for s in skills:
            self.assertNotEqual(s.status, SKILL_STATUS_DEPRECATED)

    def test_recall_skills_returns_dicts(self):
        results = self.engine.recall_skills("docker container crashed")
        self.assertIsInstance(results, list)
        if results:
            self.assertIsInstance(results[0], dict)
            self.assertIn("name", results[0])

    def test_recall_with_scene_filter(self):
        results = self.engine.recall_skills("error", scene="coding")
        for r in results:
            self.assertEqual(r["scene"], "coding")


class TestSkillExecution(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.store = SQLiteStore(self.tmp.name)
        self.engine = SkillEngine(store=self.store)

        self.store.put_skill(Skill(
            id="exec1", name="debug_docker",
            steps=["check logs", "inspect env", "restart container"],
            tools=["docker logs", "docker inspect"],
            confidence=0.85,
        ).to_dict())

    def tearDown(self):
        self.store.close()
        os.unlink(self.tmp.name)

    def test_execute_suggest_mode(self):
        result = self.engine.execute_skill("exec1", mode=EXEC_MODE_SUGGEST)
        self.assertEqual(result["mode"], "suggest")
        self.assertEqual(result["skill_name"], "debug_docker")
        self.assertEqual(len(result["suggested_steps"]), 3)
        self.assertEqual(len(result["tools"]), 2)
        self.assertAlmostEqual(result["confidence"], 0.85)

    def test_execute_auto_mode(self):
        result = self.engine.execute_skill("exec1", mode=EXEC_MODE_AUTO)
        self.assertEqual(result["mode"], "auto")
        self.assertEqual(result["steps_executed"], 3)
        self.assertEqual(len(result["results"]), 3)
        for r in result["results"]:
            self.assertEqual(r["status"], "executed")

    def test_execute_not_found(self):
        result = self.engine.execute_skill("nonexistent")
        self.assertIn("error", result)

    def test_execute_unknown_mode(self):
        result = self.engine.execute_skill("exec1", mode="invalid")
        self.assertIn("error", result)


class TestSkillFeedback(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.store = SQLiteStore(self.tmp.name)
        self.engine = SkillEngine(store=self.store)

        self.store.put_skill(Skill(
            id="fb1", name="debug_skill",
            confidence=0.7, usage_count=0, success_rate=0.0,
        ).to_dict())

    def tearDown(self):
        self.store.close()
        os.unlink(self.tmp.name)

    def test_record_feedback_success(self):
        result = self.engine.record_feedback("fb1", success=True, result="fixed the bug")
        self.assertEqual(result["status"], "recorded")
        self.assertTrue(result["feedback"]["success"])

        skill = self.store.get_skill("fb1")
        self.assertEqual(skill["usage_count"], 1)
        self.assertEqual(skill["success_rate"], 1.0)

    def test_record_feedback_failure(self):
        self.engine.record_feedback("fb1", success=True)
        self.engine.record_feedback("fb1", success=False)
        skill = self.store.get_skill("fb1")
        self.assertEqual(skill["usage_count"], 2)
        self.assertAlmostEqual(skill["success_rate"], 0.5)

    def test_feedback_stored_in_db(self):
        self.engine.record_feedback("fb1", success=True, result="worked")
        feedbacks = self.store.list_skill_feedback("fb1")
        self.assertEqual(len(feedbacks), 1)
        self.assertTrue(feedbacks[0]["success"])
        self.assertEqual(feedbacks[0]["result"], "worked")

    def test_feedback_not_found(self):
        result = self.engine.record_feedback("nonexistent", success=True)
        self.assertIn("error", result)

    def test_multiple_feedbacks(self):
        for i in range(5):
            self.engine.record_feedback("fb1", success=(i % 2 == 0), result=f"attempt {i}")
        feedbacks = self.store.list_skill_feedback("fb1")
        self.assertEqual(len(feedbacks), 5)


class TestSkillEvolution(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        self.store = SQLiteStore(self.tmp.name)
        self.engine = SkillEngine(store=self.store)

    def tearDown(self):
        self.store.close()
        os.unlink(self.tmp.name)

    def test_deprecate_weak_skills(self):
        self.store.put_skill(Skill(
            id="weak1", name="bad_skill",
            usage_count=10, success_rate=0.1,
        ).to_dict())
        result = self.engine.evolve_skills()
        self.assertGreaterEqual(result["deprecated"], 1)

        skill = self.store.get_skill("weak1")
        self.assertEqual(skill["status"], SKILL_STATUS_DEPRECATED)

    def test_promote_strong_skills(self):
        self.store.put_skill(Skill(
            id="strong1", name="great_skill",
            usage_count=10, success_rate=0.95, confidence=0.9,
        ).to_dict())
        result = self.engine.evolve_skills()
        self.assertGreaterEqual(result["promoted"], 1)

        skill = self.store.get_skill("strong1")
        self.assertEqual(skill["status"], SKILL_STATUS_VERIFIED)

    def test_merge_similar_skills(self):
        self.store.put_skill(Skill(
            id="m1", name="debug docker env",
            scene="deployment",
            steps=["check logs", "restart container"],
            usage_count=5, success_rate=0.8, confidence=0.7,
        ).to_dict())
        self.store.put_skill(Skill(
            id="m2", name="debug docker env",
            scene="deployment",
            steps=["check logs", "restart container"],
            usage_count=3, success_rate=0.7, confidence=0.6,
        ).to_dict())

        result = self.engine.evolve_skills()
        self.assertGreaterEqual(result["merged"], 1)

        active = self.store.list_skills(status="active")
        deprecated = self.store.list_skills(status="deprecated")
        verified = self.store.list_skills(status="verified")
        total_remaining = len(active) + len(verified)
        self.assertEqual(total_remaining, 1)
        self.assertEqual(len(deprecated), 1)

    def test_no_evolution_needed(self):
        self.store.put_skill(Skill(
            id="normal1", name="normal_skill",
            usage_count=3, success_rate=0.6,
        ).to_dict())
        result = self.engine.evolve_skills()
        self.assertEqual(result["merged"], 0)
        self.assertEqual(result["deprecated"], 0)
        self.assertEqual(result["promoted"], 0)

    def test_already_verified_not_promoted_again(self):
        self.store.put_skill(Skill(
            id="v1", name="verified_skill",
            usage_count=10, success_rate=0.95,
            status=SKILL_STATUS_VERIFIED,
        ).to_dict())
        result = self.engine.evolve_skills()
        self.assertEqual(result["promoted"], 0)

    def test_version_increment_on_merge(self):
        self.store.put_skill(Skill(
            id="vm1", name="merge version test",
            scene="test",
            steps=["step1"],
            usage_count=5, success_rate=0.8, confidence=0.7,
            skill_version=1,
        ).to_dict())
        self.store.put_skill(Skill(
            id="vm2", name="merge version test",
            scene="test",
            steps=["step1"],
            usage_count=3, success_rate=0.7, confidence=0.5,
            skill_version=1,
        ).to_dict())

        self.engine.evolve_skills()

        active_skills = [s for s in self.store.list_skills()
                         if s["status"] != SKILL_STATUS_DEPRECATED]
        if active_skills:
            winner = active_skills[0]
            self.assertGreater(winner["skill_version"], 1)


class TestRelevanceScorer(unittest.TestCase):
    def setUp(self):
        self.scorer = DefaultRelevanceScorer()

    def test_trigger_match(self):
        skill = {"trigger": "docker crash", "name": "debug", "pattern": ""}
        score = self.scorer.score(skill, "docker crash happened")
        self.assertGreater(score, 0)

    def test_pattern_match(self):
        skill = {"trigger": "", "name": "test", "pattern": "docker error"}
        score = self.scorer.score(skill, "got docker error in prod")
        self.assertGreater(score, 0)

    def test_no_match(self):
        skill = {"trigger": "python import", "name": "fix_imports", "pattern": "import error"}
        score = self.scorer.score(skill, "database connection timeout")
        self.assertEqual(score, 0.0)

    def test_scene_match(self):
        skill = {"trigger": "error", "name": "test", "pattern": "",
                 "scene": "deployment"}
        score = self.scorer.score(skill, "deployment error occurred")
        self.assertGreater(score, 0)

    def test_score_capped_at_1(self):
        skill = {
            "trigger": "docker crash", "name": "docker crash",
            "pattern": "docker crash", "scene": "docker crash",
            "confidence": 1.0, "success_rate": 1.0, "usage_count": 1000,
        }
        score = self.scorer.score(skill, "docker crash")
        self.assertLessEqual(score, 1.0)


class TestSDKSkillEngine(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        from openmemo.api.sdk import Memory
        self.memo = Memory(db_path=self.tmp.name)
        self.memo._auto_graph = False

    def tearDown(self):
        self.memo.close()
        os.unlink(self.tmp.name)

    def test_list_skills_empty(self):
        skills = self.memo.list_skills()
        self.assertEqual(len(skills), 0)

    def test_extract_skills_from_memory(self):
        self.memo.store.put_cell({
            "id": "p1", "note_id": "p1", "content": "When docker crashes, always check env",
            "cell_type": "pattern", "scene": "deployment",
            "confidence": 0.7, "created_at": time.time(),
        })
        skills = self.memo.extract_skills_from_memory()
        self.assertEqual(len(skills), 1)
        self.assertEqual(skills[0]["scene"], "deployment")

    def test_get_skill(self):
        self.memo.store.put_skill(Skill(
            id="sdk1", name="test_skill", scene="test",
        ).to_dict())
        skill = self.memo.get_skill("sdk1")
        self.assertEqual(skill["name"], "test_skill")

    def test_get_skill_not_found(self):
        skill = self.memo.get_skill("nonexistent")
        self.assertEqual(skill, {})

    def test_recall_skills(self):
        self.memo.store.put_skill(Skill(
            id="recall1", name="fix_docker",
            trigger="docker container crashed",
            scene="deployment", confidence=0.8,
        ).to_dict())
        results = self.memo.recall_skills("docker container crashed")
        self.assertGreater(len(results), 0)

    def test_execute_skill(self):
        self.memo.store.put_skill(Skill(
            id="exe1", name="debug",
            steps=["step1", "step2"],
        ).to_dict())
        result = self.memo.execute_skill("exe1", mode="suggest")
        self.assertEqual(result["mode"], "suggest")
        self.assertEqual(len(result["suggested_steps"]), 2)

    def test_record_feedback(self):
        self.memo.store.put_skill(Skill(
            id="fb_sdk1", name="test_fb",
        ).to_dict())
        result = self.memo.record_skill_feedback("fb_sdk1", success=True, result="good")
        self.assertEqual(result["status"], "recorded")

    def test_evolve_skills(self):
        result = self.memo.evolve_skills()
        self.assertIn("merged", result)
        self.assertIn("deprecated", result)
        self.assertIn("promoted", result)

    def test_list_skills_by_scene(self):
        self.memo.store.put_skill(Skill(
            id="ls1", name="skill_a", scene="coding",
        ).to_dict())
        self.memo.store.put_skill(Skill(
            id="ls2", name="skill_b", scene="deployment",
        ).to_dict())
        coding = self.memo.list_skills(scene="coding")
        self.assertEqual(len(coding), 1)
        all_skills = self.memo.list_skills()
        self.assertEqual(len(all_skills), 2)

    def test_full_lifecycle(self):
        self.memo.store.put_cell({
            "id": "lc1", "note_id": "lc1",
            "content": "Debug docker by checking logs then restarting",
            "cell_type": "pattern", "scene": "deployment",
            "confidence": 0.8, "created_at": time.time(),
        })

        skills = self.memo.extract_skills_from_memory()
        self.assertEqual(len(skills), 1)
        skill_id = skills[0]["id"]

        recalled = self.memo.recall_skills("docker logs restart")
        self.assertGreater(len(recalled), 0)

        result = self.memo.execute_skill(skill_id, mode="suggest")
        self.assertEqual(result["mode"], "suggest")

        self.memo.record_skill_feedback(skill_id, success=True)
        self.memo.record_skill_feedback(skill_id, success=True)

        skill = self.memo.get_skill(skill_id)
        self.assertEqual(skill["usage_count"], 2)


class TestRESTSkillEndpoints(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
        from openmemo.api.rest_server import create_app
        self.app = create_app(db_path=self.tmp.name)
        self.client = self.app.test_client()

    def tearDown(self):
        os.unlink(self.tmp.name)

    def _create_skill(self):
        from openmemo.storage.sqlite_store import SQLiteStore
        store = SQLiteStore(self.tmp.name)
        skill = Skill(
            id="rest1", name="debug_docker",
            scene="deployment", trigger="docker container crashed",
            steps=["check logs", "restart"],
            tools=["docker logs"],
            confidence=0.8, usage_count=3, success_rate=0.9,
        )
        store.put_skill(skill.to_dict())
        store.close()

    def test_list_skills_empty(self):
        resp = self.client.get("/skills")
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertEqual(data["total"], 0)

    def test_list_skills(self):
        self._create_skill()
        resp = self.client.get("/skills")
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertGreaterEqual(data["total"], 1)

    def test_list_skills_by_scene(self):
        self._create_skill()
        resp = self.client.get("/skills?scene=deployment")
        data = resp.get_json()
        self.assertGreaterEqual(data["total"], 1)

        resp2 = self.client.get("/skills?scene=nonexistent")
        data2 = resp2.get_json()
        self.assertEqual(data2["total"], 0)

    def test_get_skill(self):
        self._create_skill()
        resp = self.client.get("/skills/rest1")
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertEqual(data["name"], "debug_docker")

    def test_get_skill_not_found(self):
        resp = self.client.get("/skills/nonexistent")
        self.assertEqual(resp.status_code, 404)

    def test_recall_skills(self):
        self._create_skill()
        resp = self.client.post("/skills/recall", json={
            "query": "docker container crashed",
            "scene": "deployment",
        })
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertIn("skills", data)

    def test_recall_skills_no_query(self):
        resp = self.client.post("/skills/recall", json={})
        self.assertEqual(resp.status_code, 400)

    def test_execute_skill_suggest(self):
        self._create_skill()
        resp = self.client.post("/skills/rest1/execute", json={"mode": "suggest"})
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertEqual(data["mode"], "suggest")
        self.assertIn("suggested_steps", data)

    def test_execute_skill_auto(self):
        self._create_skill()
        resp = self.client.post("/skills/rest1/execute", json={"mode": "auto"})
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertEqual(data["mode"], "auto")

    def test_execute_skill_not_found(self):
        resp = self.client.post("/skills/nonexistent/execute", json={})
        self.assertEqual(resp.status_code, 404)

    def test_skill_feedback(self):
        self._create_skill()
        resp = self.client.post("/skills/rest1/feedback", json={
            "success": True, "result": "fixed the issue",
        })
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertEqual(data["status"], "recorded")

    def test_skill_feedback_not_found(self):
        resp = self.client.post("/skills/nonexistent/feedback", json={
            "success": True,
        })
        self.assertEqual(resp.status_code, 404)

    def test_extract_skills(self):
        resp = self.client.post("/skills/extract")
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertIn("extracted", data)

    def test_evolve_skills(self):
        resp = self.client.post("/skills/evolve")
        self.assertEqual(resp.status_code, 200)
        data = resp.get_json()
        self.assertIn("merged", data)
        self.assertIn("deprecated", data)
        self.assertIn("promoted", data)

    def test_endpoints_listed(self):
        resp = self.client.get("/")
        data = resp.get_json()
        endpoints = data.get("endpoints", {})
        self.assertIn("skills_list", endpoints)
        self.assertIn("skill_recall", endpoints)
        self.assertIn("skill_extract", endpoints)
        self.assertIn("skill_evolve", endpoints)


if __name__ == "__main__":
    unittest.main()
