2cab53bc94
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Waiting to run
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers MCP Server - 25 tools for AI assistants dockerfile:Dockerfile.mcp name:skill-seekers-mcp]) (push) Waiting to run
Docker Publish / Test Docker Images (push) Blocked by required conditions
Test Vector Database Adaptors / Test chroma Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test faiss Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test qdrant Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test weaviate Adaptor (push) Waiting to run
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Waiting to run
Tests / Code Quality (Ruff & Mypy) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Waiting to run
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Waiting to run
Tests / Tests (push) Blocked by required conditions
Tests / Serial / Integration / E2E Tests (push) Blocked by required conditions
Tests / MCP Server Tests (push) Blocked by required conditions
653 lines
23 KiB
Python
653 lines
23 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Comprehensive tests for GuideEnhancer (C3.3 AI Enhancement)
|
|
|
|
Tests dual-mode AI enhancement for how-to guides:
|
|
- API mode (Claude API)
|
|
- LOCAL mode (Claude Code CLI)
|
|
- Auto mode detection
|
|
- All 5 enhancement methods
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from unittest.mock import Mock, patch
|
|
|
|
import pytest
|
|
|
|
from skill_seekers.cli.guide_enhancer import (
|
|
GuideEnhancer,
|
|
PrerequisiteItem,
|
|
StepEnhancement,
|
|
TroubleshootingItem,
|
|
)
|
|
|
|
|
|
class TestGuideEnhancerModeDetection:
|
|
"""Test mode detection logic"""
|
|
|
|
def test_auto_mode_with_api_key(self):
|
|
"""Test auto mode detects API when key present and library available"""
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="auto")
|
|
# Will be 'api' if library available, otherwise 'local' or 'none'
|
|
assert enhancer.mode in ["api", "local", "none"]
|
|
|
|
def test_auto_mode_without_api_key(self):
|
|
"""Test auto mode falls back to LOCAL when no API key"""
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
if "ANTHROPIC_API_KEY" in os.environ:
|
|
del os.environ["ANTHROPIC_API_KEY"]
|
|
|
|
enhancer = GuideEnhancer(mode="auto")
|
|
assert enhancer.mode in ["local", "none"]
|
|
|
|
def test_explicit_api_mode(self):
|
|
"""Test explicit API mode"""
|
|
enhancer = GuideEnhancer(mode="api")
|
|
assert enhancer.mode in ["api", "none"] # none if no API key
|
|
|
|
def test_explicit_local_mode(self):
|
|
"""Test explicit LOCAL mode"""
|
|
enhancer = GuideEnhancer(mode="local")
|
|
assert enhancer.mode in ["local", "none"] # none if no claude CLI
|
|
|
|
def test_explicit_none_mode(self):
|
|
"""Test explicit none mode"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
assert enhancer.mode == "none"
|
|
|
|
def test_claude_cli_check(self):
|
|
"""Test Claude CLI availability check"""
|
|
enhancer = GuideEnhancer(mode="local")
|
|
# Should either detect claude or fall back to api/none
|
|
assert enhancer.mode in ["local", "api", "none"]
|
|
|
|
|
|
class TestGuideEnhancerStepDescriptions:
|
|
"""Test step description enhancement"""
|
|
|
|
def test_enhance_step_descriptions_empty_list(self):
|
|
"""Test with empty steps list"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
steps = []
|
|
result = enhancer.enhance_step_descriptions(steps)
|
|
assert result == []
|
|
|
|
def test_enhance_step_descriptions_none_mode(self):
|
|
"""Test step descriptions in none mode returns empty"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
steps = [
|
|
{
|
|
"description": "scraper.scrape(url)",
|
|
"code": "result = scraper.scrape(url)",
|
|
}
|
|
]
|
|
result = enhancer.enhance_step_descriptions(steps)
|
|
assert result == []
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_enhance_step_descriptions_api_mode(self, mock_call):
|
|
"""Test step descriptions with API mode"""
|
|
mock_call.return_value = json.dumps(
|
|
{
|
|
"step_descriptions": [
|
|
{
|
|
"step_index": 0,
|
|
"explanation": "Initialize the scraper with the target URL",
|
|
"variations": ["Use async scraper for better performance"],
|
|
}
|
|
]
|
|
}
|
|
)
|
|
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="api")
|
|
if enhancer.mode != "api":
|
|
pytest.skip("API mode not available")
|
|
|
|
enhancer.client = Mock() # Mock the client
|
|
|
|
steps = [
|
|
{
|
|
"description": "scraper.scrape(url)",
|
|
"code": "result = scraper.scrape(url)",
|
|
}
|
|
]
|
|
result = enhancer.enhance_step_descriptions(steps)
|
|
|
|
assert len(result) == 1
|
|
assert isinstance(result[0], StepEnhancement)
|
|
assert result[0].step_index == 0
|
|
assert "Initialize" in result[0].explanation
|
|
assert len(result[0].variations) == 1
|
|
|
|
def test_enhance_step_descriptions_malformed_json(self):
|
|
"""Test handling of malformed JSON response"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
with patch.object(enhancer, "_call_ai", return_value="invalid json"):
|
|
steps = [{"description": "test", "code": "code"}]
|
|
result = enhancer.enhance_step_descriptions(steps)
|
|
assert result == []
|
|
|
|
|
|
class TestGuideEnhancerTroubleshooting:
|
|
"""Test troubleshooting enhancement"""
|
|
|
|
def test_enhance_troubleshooting_none_mode(self):
|
|
"""Test troubleshooting in none mode"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
guide_data = {
|
|
"title": "Test Guide",
|
|
"steps": [{"description": "test", "code": "code"}],
|
|
"language": "python",
|
|
}
|
|
result = enhancer.enhance_troubleshooting(guide_data)
|
|
assert result == []
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_enhance_troubleshooting_api_mode(self, mock_call):
|
|
"""Test troubleshooting with API mode"""
|
|
mock_call.return_value = json.dumps(
|
|
{
|
|
"troubleshooting": [
|
|
{
|
|
"problem": "ImportError: No module named requests",
|
|
"symptoms": ["Import fails", "Module not found error"],
|
|
"diagnostic_steps": ["Check pip list", "Verify virtual env"],
|
|
"solution": "Run: pip install requests",
|
|
}
|
|
]
|
|
}
|
|
)
|
|
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="api")
|
|
if enhancer.mode != "api":
|
|
pytest.skip("API mode not available")
|
|
|
|
enhancer.client = Mock()
|
|
|
|
guide_data = {
|
|
"title": "Test Guide",
|
|
"steps": [{"description": "import requests", "code": "import requests"}],
|
|
"language": "python",
|
|
}
|
|
result = enhancer.enhance_troubleshooting(guide_data)
|
|
|
|
assert len(result) == 1
|
|
assert isinstance(result[0], TroubleshootingItem)
|
|
assert "ImportError" in result[0].problem
|
|
assert len(result[0].symptoms) == 2
|
|
assert len(result[0].diagnostic_steps) == 2
|
|
assert "pip install" in result[0].solution
|
|
|
|
|
|
class TestGuideEnhancerPrerequisites:
|
|
"""Test prerequisite enhancement"""
|
|
|
|
def test_enhance_prerequisites_empty_list(self):
|
|
"""Test with empty prerequisites"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
result = enhancer.enhance_prerequisites([])
|
|
assert result == []
|
|
|
|
def test_enhance_prerequisites_none_mode(self):
|
|
"""Test prerequisites in none mode"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
prereqs = ["requests", "beautifulsoup4"]
|
|
result = enhancer.enhance_prerequisites(prereqs)
|
|
assert result == []
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_enhance_prerequisites_api_mode(self, mock_call):
|
|
"""Test prerequisites with API mode"""
|
|
mock_call.return_value = json.dumps(
|
|
{
|
|
"prerequisites_detailed": [
|
|
{
|
|
"name": "requests",
|
|
"why": "HTTP client for making web requests",
|
|
"setup": "pip install requests",
|
|
},
|
|
{
|
|
"name": "beautifulsoup4",
|
|
"why": "HTML/XML parser for web scraping",
|
|
"setup": "pip install beautifulsoup4",
|
|
},
|
|
]
|
|
}
|
|
)
|
|
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="api")
|
|
if enhancer.mode != "api":
|
|
pytest.skip("API mode not available")
|
|
|
|
enhancer.client = Mock()
|
|
|
|
prereqs = ["requests", "beautifulsoup4"]
|
|
result = enhancer.enhance_prerequisites(prereqs)
|
|
|
|
assert len(result) == 2
|
|
assert isinstance(result[0], PrerequisiteItem)
|
|
assert result[0].name == "requests"
|
|
assert "HTTP client" in result[0].why
|
|
assert "pip install" in result[0].setup
|
|
|
|
|
|
class TestGuideEnhancerNextSteps:
|
|
"""Test next steps enhancement"""
|
|
|
|
def test_enhance_next_steps_none_mode(self):
|
|
"""Test next steps in none mode"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
guide_data = {"title": "Test Guide", "description": "Test"}
|
|
result = enhancer.enhance_next_steps(guide_data)
|
|
assert result == []
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_enhance_next_steps_api_mode(self, mock_call):
|
|
"""Test next steps with API mode"""
|
|
mock_call.return_value = json.dumps(
|
|
{
|
|
"next_steps": [
|
|
"How to handle async workflows",
|
|
"How to add error handling",
|
|
"How to implement caching",
|
|
]
|
|
}
|
|
)
|
|
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="api")
|
|
if enhancer.mode != "api":
|
|
pytest.skip("API mode not available")
|
|
|
|
enhancer.client = Mock()
|
|
|
|
guide_data = {
|
|
"title": "How to Scrape Docs",
|
|
"description": "Basic scraping",
|
|
}
|
|
result = enhancer.enhance_next_steps(guide_data)
|
|
|
|
assert len(result) == 3
|
|
assert "async" in result[0].lower()
|
|
assert "error" in result[1].lower()
|
|
|
|
|
|
class TestGuideEnhancerUseCases:
|
|
"""Test use case enhancement"""
|
|
|
|
def test_enhance_use_cases_none_mode(self):
|
|
"""Test use cases in none mode"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
guide_data = {"title": "Test Guide", "description": "Test"}
|
|
result = enhancer.enhance_use_cases(guide_data)
|
|
assert result == []
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_enhance_use_cases_api_mode(self, mock_call):
|
|
"""Test use cases with API mode"""
|
|
mock_call.return_value = json.dumps(
|
|
{
|
|
"use_cases": [
|
|
"Use when you need to automate documentation extraction",
|
|
"Ideal for building knowledge bases from technical docs",
|
|
]
|
|
}
|
|
)
|
|
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="api")
|
|
if enhancer.mode != "api":
|
|
pytest.skip("API mode not available")
|
|
|
|
enhancer.client = Mock()
|
|
|
|
guide_data = {
|
|
"title": "How to Scrape Docs",
|
|
"description": "Documentation scraping",
|
|
}
|
|
result = enhancer.enhance_use_cases(guide_data)
|
|
|
|
assert len(result) == 2
|
|
assert "automate" in result[0].lower()
|
|
assert "knowledge base" in result[1].lower()
|
|
|
|
|
|
class TestGuideEnhancerFullWorkflow:
|
|
"""Test complete guide enhancement workflow"""
|
|
|
|
def test_enhance_guide_none_mode(self):
|
|
"""Test full guide enhancement in none mode"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
guide_data = {
|
|
"title": "How to Scrape Documentation",
|
|
"steps": [
|
|
{"description": "Import libraries", "code": "import requests"},
|
|
{"description": "Create scraper", "code": "scraper = Scraper()"},
|
|
],
|
|
"language": "python",
|
|
"prerequisites": ["requests"],
|
|
"description": "Basic scraping guide",
|
|
}
|
|
|
|
result = enhancer.enhance_guide(guide_data)
|
|
|
|
# In none mode, should return original guide
|
|
assert result["title"] == guide_data["title"]
|
|
assert len(result["steps"]) == 2
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_enhance_guide_api_mode_success(self, mock_call):
|
|
"""Test successful full guide enhancement via API"""
|
|
mock_call.return_value = json.dumps(
|
|
{
|
|
"step_descriptions": [
|
|
{
|
|
"step_index": 0,
|
|
"explanation": "Import required libraries",
|
|
"variations": [],
|
|
},
|
|
{
|
|
"step_index": 1,
|
|
"explanation": "Initialize scraper instance",
|
|
"variations": [],
|
|
},
|
|
],
|
|
"troubleshooting": [
|
|
{
|
|
"problem": "Import error",
|
|
"symptoms": ["Module not found"],
|
|
"diagnostic_steps": ["Check installation"],
|
|
"solution": "pip install requests",
|
|
}
|
|
],
|
|
"prerequisites_detailed": [
|
|
{
|
|
"name": "requests",
|
|
"why": "HTTP client",
|
|
"setup": "pip install requests",
|
|
}
|
|
],
|
|
"next_steps": ["How to add authentication"],
|
|
"use_cases": ["Automate documentation extraction"],
|
|
}
|
|
)
|
|
|
|
with (
|
|
patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}),
|
|
patch("skill_seekers.cli.guide_enhancer.ANTHROPIC_AVAILABLE", True),
|
|
patch("skill_seekers.cli.guide_enhancer.anthropic", create=True) as mock_anthropic,
|
|
):
|
|
mock_anthropic.Anthropic = Mock()
|
|
enhancer = GuideEnhancer(mode="api")
|
|
if enhancer.mode != "api":
|
|
pytest.skip("API mode not available")
|
|
|
|
enhancer.client = Mock()
|
|
|
|
guide_data = {
|
|
"title": "How to Scrape Documentation",
|
|
"steps": [
|
|
{"description": "Import libraries", "code": "import requests"},
|
|
{"description": "Create scraper", "code": "scraper = Scraper()"},
|
|
],
|
|
"language": "python",
|
|
"prerequisites": ["requests"],
|
|
"description": "Basic scraping guide",
|
|
}
|
|
|
|
result = enhancer.enhance_guide(guide_data)
|
|
|
|
# Check enhancements were applied
|
|
assert "step_enhancements" in result
|
|
assert "troubleshooting_detailed" in result
|
|
assert "prerequisites_detailed" in result
|
|
assert "next_steps_detailed" in result
|
|
assert "use_cases" in result
|
|
|
|
def test_enhance_guide_error_fallback(self):
|
|
"""Test graceful fallback on enhancement error"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
with patch.object(enhancer, "enhance_guide", side_effect=Exception("API error")):
|
|
guide_data = {
|
|
"title": "Test",
|
|
"steps": [],
|
|
"language": "python",
|
|
"prerequisites": [],
|
|
"description": "Test",
|
|
}
|
|
|
|
# Should not raise exception - graceful fallback
|
|
try:
|
|
enhancer = GuideEnhancer(mode="none")
|
|
result = enhancer.enhance_guide(guide_data)
|
|
# In none mode with error, returns original
|
|
assert result["title"] == guide_data["title"]
|
|
except Exception:
|
|
pytest.fail("Should handle errors gracefully")
|
|
|
|
|
|
class TestGuideEnhancerLocalMode:
|
|
"""Test LOCAL mode (Claude Code CLI)"""
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_call_ai_local_success(self, mock_call_ai):
|
|
"""Test successful LOCAL mode call via AgentClient"""
|
|
mock_call_ai.return_value = json.dumps(
|
|
{
|
|
"step_descriptions": [],
|
|
"troubleshooting": [],
|
|
"prerequisites_detailed": [],
|
|
"next_steps": [],
|
|
"use_cases": [],
|
|
}
|
|
)
|
|
|
|
enhancer = GuideEnhancer(mode="local")
|
|
prompt = "Test prompt"
|
|
result = enhancer._call_ai(prompt)
|
|
|
|
assert result is not None
|
|
assert mock_call_ai.called
|
|
|
|
@patch.object(GuideEnhancer, "_call_ai")
|
|
def test_call_ai_local_timeout(self, mock_call_ai):
|
|
"""Test LOCAL mode timeout handling via AgentClient"""
|
|
mock_call_ai.return_value = None
|
|
|
|
enhancer = GuideEnhancer(mode="local")
|
|
prompt = "Test prompt"
|
|
result = enhancer._call_ai(prompt)
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestGuideEnhancerPromptGeneration:
|
|
"""Test prompt generation"""
|
|
|
|
def test_create_enhancement_prompt(self):
|
|
"""Test comprehensive enhancement prompt generation"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
guide_data = {
|
|
"title": "How to Test",
|
|
"steps": [{"description": "Write test", "code": "def test_example(): pass"}],
|
|
"language": "python",
|
|
"prerequisites": ["pytest"],
|
|
}
|
|
|
|
prompt = enhancer._create_enhancement_prompt(guide_data)
|
|
|
|
assert "How to Test" in prompt
|
|
assert "pytest" in prompt
|
|
assert "STEP_DESCRIPTIONS" in prompt
|
|
assert "TROUBLESHOOTING" in prompt
|
|
assert "PREREQUISITES" in prompt
|
|
assert "NEXT_STEPS" in prompt
|
|
assert "USE_CASES" in prompt
|
|
assert "JSON" in prompt
|
|
|
|
def test_format_steps_for_prompt(self):
|
|
"""Test step formatting for prompts"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
steps = [
|
|
{"description": "Import", "code": "import requests"},
|
|
{"description": "Create", "code": "obj = Object()"},
|
|
]
|
|
|
|
formatted = enhancer._format_steps_for_prompt(steps)
|
|
|
|
assert "Step 1" in formatted
|
|
assert "Step 2" in formatted
|
|
assert "import requests" in formatted
|
|
assert "obj = Object()" in formatted
|
|
|
|
def test_format_steps_empty(self):
|
|
"""Test formatting empty steps list"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
formatted = enhancer._format_steps_for_prompt([])
|
|
assert formatted == "No steps provided"
|
|
|
|
|
|
class TestGuideEnhancerResponseParsing:
|
|
"""Test response parsing"""
|
|
|
|
def test_parse_enhancement_response_valid_json(self):
|
|
"""Test parsing valid JSON response"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
response = json.dumps(
|
|
{
|
|
"step_descriptions": [{"step_index": 0, "explanation": "Test", "variations": []}],
|
|
"troubleshooting": [],
|
|
"prerequisites_detailed": [],
|
|
"next_steps": [],
|
|
"use_cases": [],
|
|
}
|
|
)
|
|
|
|
guide_data = {
|
|
"title": "Test",
|
|
"steps": [{"description": "Test", "code": "test"}],
|
|
"language": "python",
|
|
}
|
|
|
|
result = enhancer._parse_enhancement_response(response, guide_data)
|
|
|
|
assert "step_enhancements" in result
|
|
assert len(result["step_enhancements"]) == 1
|
|
|
|
def test_parse_enhancement_response_with_extra_text(self):
|
|
"""Test parsing JSON embedded in text"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
json_data = {
|
|
"step_descriptions": [],
|
|
"troubleshooting": [],
|
|
"prerequisites_detailed": [],
|
|
"next_steps": [],
|
|
"use_cases": [],
|
|
}
|
|
|
|
response = f"Here's the result:\n{json.dumps(json_data)}\nDone!"
|
|
|
|
guide_data = {"title": "Test", "steps": [], "language": "python"}
|
|
result = enhancer._parse_enhancement_response(response, guide_data)
|
|
|
|
# Should extract JSON successfully
|
|
assert "title" in result
|
|
|
|
def test_parse_enhancement_response_invalid_json(self):
|
|
"""Test handling invalid JSON"""
|
|
enhancer = GuideEnhancer(mode="none")
|
|
|
|
response = "This is not valid JSON"
|
|
guide_data = {"title": "Test", "steps": [], "language": "python"}
|
|
|
|
result = enhancer._parse_enhancement_response(response, guide_data)
|
|
|
|
# Should return original guide_data on parse error
|
|
assert result["title"] == "Test"
|
|
|
|
|
|
class TestEnhanceViaLocalRegression:
|
|
"""Regression for CBB-01: _enhance_via_local must call the real _call_ai.
|
|
|
|
The bug called a non-existent _call_claude_local; the AttributeError was
|
|
swallowed by enhance_guide's broad except, so LOCAL-mode enhancement
|
|
silently returned the guide unenhanced.
|
|
"""
|
|
|
|
def test_enhance_via_local_routes_through_call_ai(self):
|
|
enhancer = GuideEnhancer(mode="none")
|
|
guide = {"title": "X", "steps": [], "language": "python"}
|
|
with patch.object(GuideEnhancer, "_call_ai", return_value="") as mock_ai:
|
|
result = enhancer._enhance_via_local(guide)
|
|
mock_ai.assert_called_once()
|
|
# Falsy AI response → original guide returned unchanged (no AttributeError).
|
|
assert result == guide
|
|
|
|
def test_call_claude_local_method_does_not_exist(self):
|
|
assert not hasattr(GuideEnhancer, "_call_claude_local")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|
|
|
|
|
|
class TestNoEnhancerNameCollisions:
|
|
"""unified_enhancer must not re-introduce same-named shadow classes for
|
|
the canonical per-feature enhancers (they had zero importers but shadowed
|
|
guide_enhancer.GuideEnhancer, ai_enhancer.PatternEnhancer, etc.)."""
|
|
|
|
def test_unified_enhancer_defines_no_colliding_names(self):
|
|
import skill_seekers.cli.unified_enhancer as ue
|
|
|
|
for name in ("PatternEnhancer", "TestExampleEnhancer", "GuideEnhancer", "ConfigEnhancer"):
|
|
assert not hasattr(ue, name), (
|
|
f"unified_enhancer defines {name}, colliding with the canonical "
|
|
f"class of the same name in its own module"
|
|
)
|
|
|
|
def test_canonical_classes_importable(self):
|
|
from skill_seekers.cli.ai_enhancer import PatternEnhancer, TestExampleEnhancer # noqa: F401
|
|
from skill_seekers.cli.guide_enhancer import GuideEnhancer # noqa: F401
|