Files
wehub-resource-sync 2cab53bc94
Test Vector Database Adaptors / Test MCP Vector DB Tools (push) Has been cancelled
Tests / Code Quality (Ruff & Mypy) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (macos-latest, 3.12) (push) Has been cancelled
Tests / Tests (push) Has been cancelled
Docker Publish / Build and Push Docker Images (map[description:Skill Seekers CLI - Convert documentation to AI skills dockerfile:Dockerfile name:skill-seekers]) (push) Has been cancelled
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) Has been cancelled
Docker Publish / Test Docker Images (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / Fast Unit Tests (parallel) (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / Serial / Integration / E2E Tests (push) Has been cancelled
Tests / MCP Server Tests (push) Has been cancelled
Test Vector Database Adaptors / Test chroma Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test faiss Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test qdrant Adaptor (push) Has been cancelled
Test Vector Database Adaptors / Test weaviate Adaptor (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:46:28 +08:00

1163 lines
40 KiB
Python

#!/usr/bin/env python3
"""
Tests for how_to_guide_builder.py - Build how-to guides from workflow examples
Test Coverage:
- WorkflowAnalyzer (6 tests) - Step extraction and metadata detection
- WorkflowGrouper (4 tests) - Grouping strategies
- GuideGenerator (5 tests) - Markdown generation
- HowToGuideBuilder (5 tests) - Main orchestrator integration
- End-to-end (1 test) - Full workflow
"""
import json
import os
import shutil
import sys
import tempfile
import unittest
from pathlib import Path
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from skill_seekers.cli.guide_enhancer import StepEnhancement
from skill_seekers.cli.how_to_guide_builder import (
GuideCollection,
GuideGenerator,
HowToGuide,
HowToGuideBuilder,
WorkflowAnalyzer,
WorkflowGrouper,
WorkflowStep,
)
class TestWorkflowAnalyzer(unittest.TestCase):
"""Tests for WorkflowAnalyzer - Extract steps from workflows"""
def setUp(self):
self.analyzer = WorkflowAnalyzer()
def test_analyze_python_workflow(self):
"""Test analysis of Python workflow with multiple steps"""
workflow = {
"code": """
def test_user_creation_workflow():
# Step 1: Create database
db = Database('test.db')
# Step 2: Create user
user = User(name='Alice', email='alice@example.com')
db.save(user)
# Step 3: Verify creation
assert db.get_user('Alice').email == 'alice@example.com'
""",
"language": "python",
"category": "workflow",
"test_name": "test_user_creation_workflow",
"file_path": "tests/test_user.py",
}
steps, metadata = self.analyzer.analyze_workflow(workflow)
# Should extract 3 steps
self.assertGreaterEqual(len(steps), 2)
# Check step structure
self.assertIsInstance(steps[0], WorkflowStep)
self.assertEqual(steps[0].step_number, 1)
self.assertIsNotNone(steps[0].description)
# Check metadata
self.assertIn("complexity_level", metadata)
self.assertIn(metadata["complexity_level"], ["beginner", "intermediate", "advanced"])
def test_detect_prerequisites(self):
"""Test detection of prerequisites from imports and fixtures"""
workflow = {
"code": """
import pytest
from myapp import Database, User
@pytest.fixture
def db():
return Database('test.db')
def test_workflow(db):
user = User(name='Bob')
db.save(user)
""",
"language": "python",
"category": "workflow",
"test_name": "test_workflow",
"file_path": "tests/test.py",
}
steps, metadata = self.analyzer.analyze_workflow(workflow)
# Should analyze workflow successfully
self.assertIsInstance(steps, list)
self.assertIsInstance(metadata, dict)
# Prerequisites detection is internal - just verify it completes
def test_find_verification_points(self):
"""Test finding verification/assertion points in workflow"""
code = """
def test_workflow():
result = calculate(5, 3)
assert result == 8 # Verify calculation
status = save_to_db(result)
assert status == True # Verify save
"""
verifications = self.analyzer._find_verification_points(code)
# Should find assertion patterns
self.assertGreaterEqual(len(verifications), 0)
def test_calculate_complexity(self):
"""Test complexity level calculation"""
# Simple workflow - beginner
simple_steps = [
WorkflowStep(1, "x = 1", "Assign variable"),
WorkflowStep(2, "print(x)", "Print variable"),
]
simple_workflow = {"code": "x = 1\nprint(x)", "category": "workflow"}
complexity_simple = self.analyzer._calculate_complexity(simple_steps, simple_workflow)
self.assertEqual(complexity_simple, "beginner")
# Complex workflow - advanced
complex_steps = [WorkflowStep(i, f"step{i}", f"Step {i}") for i in range(1, 8)]
complex_workflow = {
"code": "\n".join(
[f"async def step{i}(): await complex_operation()" for i in range(7)]
),
"category": "workflow",
}
complexity_complex = self.analyzer._calculate_complexity(complex_steps, complex_workflow)
self.assertIn(complexity_complex, ["intermediate", "advanced"])
def test_extract_steps_python_ast(self):
"""Test Python AST-based step extraction"""
code = """
def test_workflow():
db = Database('test.db')
user = User(name='Alice')
db.save(user)
result = db.query('SELECT * FROM users')
assert len(result) == 1
"""
workflow = {
"code": code,
"language": "python",
"category": "workflow",
"test_name": "test_workflow",
"file_path": "test.py",
}
steps = self.analyzer._extract_steps_python(code, workflow)
# Should extract multiple steps
self.assertGreaterEqual(len(steps), 2)
# Each step should have required fields
for step in steps:
self.assertIsInstance(step.step_number, int)
self.assertIsInstance(step.code, str)
self.assertIsInstance(step.description, str)
def test_extract_steps_heuristic(self):
"""Test heuristic-based step extraction for non-Python languages"""
code = """
func TestWorkflow(t *testing.T) {
// Step 1
db := NewDatabase("test.db")
// Step 2
user := User{Name: "Alice"}
db.Save(user)
// Step 3
result := db.Query("SELECT * FROM users")
if len(result) != 1 {
t.Error("Expected 1 user")
}
}
"""
workflow = {
"code": code,
"language": "go",
"category": "workflow",
"test_name": "TestWorkflow",
"file_path": "test.go",
}
steps = self.analyzer._extract_steps_heuristic(code, workflow)
# Should extract steps based on comments or logical blocks
self.assertGreaterEqual(len(steps), 1)
class TestWorkflowGrouper(unittest.TestCase):
"""Tests for WorkflowGrouper - Group related workflows"""
def setUp(self):
self.grouper = WorkflowGrouper()
def test_group_by_file_path(self):
"""Test grouping workflows by file path"""
workflows = [
{
"test_name": "test_user_create",
"file_path": "tests/test_user.py",
"code": "user = User()",
"category": "workflow",
},
{
"test_name": "test_user_delete",
"file_path": "tests/test_user.py",
"code": "db.delete(user)",
"category": "workflow",
},
{
"test_name": "test_db_connect",
"file_path": "tests/test_database.py",
"code": "db = Database()",
"category": "workflow",
},
]
grouped = self.grouper._group_by_file_path(workflows)
# Should create 2 groups (test_user.py and test_database.py)
self.assertEqual(len(grouped), 2)
# Check that groups were created (titles are auto-generated from file names)
self.assertTrue(all(isinstance(k, str) for k in grouped))
def test_group_by_test_name(self):
"""Test grouping workflows by test name patterns"""
workflows = [
{"test_name": "test_user_create", "code": "user = User()", "category": "workflow"},
{"test_name": "test_user_update", "code": "user.update()", "category": "workflow"},
{"test_name": "test_admin_create", "code": "admin = Admin()", "category": "workflow"},
]
grouped = self.grouper._group_by_test_name(workflows)
# Should group by common prefix (test_user_*)
self.assertGreaterEqual(len(grouped), 1)
def test_group_by_complexity(self):
"""Test grouping workflows by complexity level"""
workflows = [
{
"test_name": "test_simple",
"code": "x = 1\nprint(x)",
"category": "workflow",
"complexity_level": "beginner",
},
{
"test_name": "test_complex",
"code": "\n".join(["step()" for _ in range(10)]),
"category": "workflow",
"complexity_level": "advanced",
},
]
grouped = self.grouper._group_by_complexity(workflows)
# Should create groups by complexity
self.assertGreaterEqual(len(grouped), 1)
def test_group_by_ai_tutorial_group(self):
"""Test AI-based tutorial grouping (or fallback if no AI)"""
workflows = [
{
"test_name": "test_user_create",
"code": 'user = User(name="Alice")',
"category": "workflow",
"file_path": "tests/test_user.py",
"tutorial_group": "User Management", # Simulated AI categorization
},
{
"test_name": "test_db_connect",
"code": "db = Database()",
"category": "workflow",
"file_path": "tests/test_db.py",
"tutorial_group": "Database Operations",
},
]
grouped = self.grouper._group_by_ai_tutorial_group(workflows)
# Should group by tutorial_group or fallback to file-path
self.assertGreaterEqual(len(grouped), 1)
class TestGuideGenerator(unittest.TestCase):
"""Tests for GuideGenerator - Generate markdown guides"""
def setUp(self):
self.generator = GuideGenerator()
def test_generate_guide_markdown(self):
"""Test generation of complete markdown guide"""
guide = HowToGuide(
guide_id="test-guide-1",
title="How to Create a User",
overview="This guide demonstrates user creation workflow",
complexity_level="beginner",
prerequisites=["Database", "User model"],
required_imports=["from myapp import Database, User"],
steps=[
WorkflowStep(1, 'db = Database("test.db")', "Create database connection"),
WorkflowStep(2, 'user = User(name="Alice")', "Create user object"),
WorkflowStep(3, "db.save(user)", "Save to database"),
],
use_case="Creating new users in the system",
tags=["user", "database", "create"],
)
markdown = self.generator.generate_guide_markdown(guide)
# Check markdown contains expected sections (actual format uses "# How To:" prefix)
self.assertIn("# How To:", markdown)
self.assertIn("How to Create a User", markdown)
self.assertIn("## Overview", markdown)
self.assertIn("## Prerequisites", markdown)
self.assertIn("Step 1:", markdown)
self.assertIn("Create database connection", markdown)
def test_create_header(self):
"""Test header generation with metadata"""
guide = HowToGuide(
guide_id="test-1",
title="Test Guide",
overview="Test",
complexity_level="beginner",
tags=["test", "example"],
)
header = self.generator._create_header(guide)
# Actual format uses "# How To:" prefix
self.assertIn("# How To:", header)
self.assertIn("Test Guide", header)
self.assertIn("Beginner", header)
def test_create_steps_section(self):
"""Test steps section generation"""
steps = [
WorkflowStep(
1,
"db = Database()",
"Create database",
expected_result="Database object",
verification="assert db.is_connected()",
),
WorkflowStep(2, "user = User()", "Create user"),
]
steps_md = self.generator._create_steps_section(steps)
# Actual format uses "## Step-by-Step Guide"
self.assertIn("## Step-by-Step Guide", steps_md)
self.assertIn("### Step 1:", steps_md)
self.assertIn("Create database", steps_md)
self.assertIn("```", steps_md) # Code block
self.assertIn("Database()", steps_md)
def test_create_complete_example(self):
"""Test complete example generation"""
guide = HowToGuide(
guide_id="test-1",
title="Test",
overview="Test",
complexity_level="beginner",
steps=[WorkflowStep(1, "x = 1", "Assign"), WorkflowStep(2, "print(x)", "Print")],
workflows=[{"code": "x = 1\nprint(x)", "language": "python"}],
)
example_md = self.generator._create_complete_example(guide)
self.assertIn("## Complete Example", example_md)
self.assertIn("```python", example_md)
def test_create_index(self):
"""Test index generation for guide collection"""
guides = [
HowToGuide(
guide_id="guide-1",
title="Beginner Guide",
overview="Simple guide",
complexity_level="beginner",
tags=["user"],
),
HowToGuide(
guide_id="guide-2",
title="Advanced Guide",
overview="Complex guide",
complexity_level="advanced",
tags=["admin", "security"],
),
]
# Method is actually called generate_index
index_md = self.generator.generate_index(guides)
self.assertIn("How-To Guides", index_md)
self.assertIn("Beginner Guide", index_md)
self.assertIn("Advanced Guide", index_md)
class TestHowToGuideBuilder(unittest.TestCase):
"""Tests for HowToGuideBuilder - Main orchestrator"""
def setUp(self):
self.builder = HowToGuideBuilder(enhance_with_ai=False)
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
if os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
def test_extract_workflow_examples(self):
"""Test extraction of workflow examples from mixed examples"""
examples = [
{
"category": "workflow",
"code": "db = Database()\nuser = User()\ndb.save(user)",
"test_name": "test_user_workflow",
"file_path": "tests/test_user.py",
"language": "python",
},
{
"category": "instantiation",
"code": "db = Database()",
"test_name": "test_db",
"file_path": "tests/test_db.py",
"language": "python",
},
]
workflows = self.builder._extract_workflow_examples(examples)
# Should only extract workflow category
self.assertEqual(len(workflows), 1)
self.assertEqual(workflows[0]["category"], "workflow")
def test_create_guide_from_workflows(self):
"""Test guide creation from grouped workflows"""
workflows = [
{
"code": 'user = User(name="Alice")\ndb.save(user)',
"test_name": "test_create_user",
"file_path": "tests/test_user.py",
"language": "python",
"category": "workflow",
}
]
guide = self.builder._create_guide("User Management", workflows)
self.assertIsInstance(guide, HowToGuide)
self.assertEqual(guide.title, "User Management")
self.assertGreater(len(guide.steps), 0)
self.assertIn(guide.complexity_level, ["beginner", "intermediate", "advanced"])
def test_create_collection(self):
"""Test guide collection creation with metadata"""
guides = [
HowToGuide(
guide_id="guide-1", title="Guide 1", overview="Test", complexity_level="beginner"
),
HowToGuide(
guide_id="guide-2", title="Guide 2", overview="Test", complexity_level="advanced"
),
]
collection = self.builder._create_collection(guides)
self.assertIsInstance(collection, GuideCollection)
self.assertEqual(collection.total_guides, 2)
# Attribute is guides_by_complexity not by_complexity
self.assertEqual(collection.guides_by_complexity["beginner"], 1)
self.assertEqual(collection.guides_by_complexity["advanced"], 1)
def test_save_guides_to_files(self):
"""Test saving guides to markdown files"""
guides = [
HowToGuide(
guide_id="test-guide",
title="Test Guide",
overview="Test overview",
complexity_level="beginner",
steps=[WorkflowStep(1, "x = 1", "Test step")],
)
]
# Correct attribute names
collection = GuideCollection(
total_guides=1,
guides=guides,
guides_by_complexity={"beginner": 1},
guides_by_use_case={},
)
output_dir = Path(self.temp_dir)
self.builder._save_guides_to_files(collection, output_dir)
# Check index file was created
self.assertTrue((output_dir / "index.md").exists())
# Check index content contains guide information
index_content = (output_dir / "index.md").read_text()
self.assertIn("Test Guide", index_content)
# Check that at least one markdown file exists
md_files = list(output_dir.glob("*.md"))
self.assertGreaterEqual(len(md_files), 1)
def test_build_guides_from_examples(self):
"""Test full guide building workflow"""
examples = [
{
"category": "workflow",
"code": """
def test_user_workflow():
db = Database('test.db')
user = User(name='Alice', email='alice@test.com')
db.save(user)
assert db.get_user('Alice').email == 'alice@test.com'
""",
"test_name": "test_user_workflow",
"file_path": "tests/test_user.py",
"language": "python",
"description": "User creation workflow",
"expected_behavior": "User should be saved and retrieved",
}
]
output_dir = Path(self.temp_dir) / "guides"
collection = self.builder.build_guides_from_examples(
examples, grouping_strategy="file-path", output_dir=output_dir
)
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
self.assertTrue(output_dir.exists())
self.assertTrue((output_dir / "index.md").exists())
class TestEndToEnd(unittest.TestCase):
"""End-to-end integration test"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
if os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
def test_full_workflow(self):
"""Test complete workflow from examples to guides"""
# Create test examples JSON
examples = {
"total_examples": 2,
"examples": [
{
"category": "workflow",
"code": '''
def test_database_workflow():
"""Test complete database workflow"""
# Setup
db = Database('test.db')
# Create user
user = User(name='Alice', email='alice@example.com')
db.save(user)
# Verify
saved_user = db.get_user('Alice')
assert saved_user.email == 'alice@example.com'
''',
"test_name": "test_database_workflow",
"file_path": "tests/test_database.py",
"language": "python",
"description": "Complete database workflow",
"expected_behavior": "User saved and retrieved correctly",
},
{
"category": "workflow",
"code": '''
def test_authentication_workflow():
"""Test user authentication"""
user = User(name='Bob', password='secret123')
token = authenticate(user.name, 'secret123')
assert token is not None
assert verify_token(token) == user.name
''',
"test_name": "test_authentication_workflow",
"file_path": "tests/test_auth.py",
"language": "python",
"description": "Authentication workflow",
"expected_behavior": "User authenticated successfully",
},
],
}
# Save examples to temp file
examples_file = Path(self.temp_dir) / "test_examples.json"
with open(examples_file, "w") as f:
json.dump(examples, f)
# Build guides
builder = HowToGuideBuilder(enhance_with_ai=False)
output_dir = Path(self.temp_dir) / "tutorials"
collection = builder.build_guides_from_examples(
examples["examples"], grouping_strategy="file-path", output_dir=output_dir
)
# Verify results
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
# Check output files
self.assertTrue(output_dir.exists())
self.assertTrue((output_dir / "index.md").exists())
# Check index content
index_content = (output_dir / "index.md").read_text()
self.assertIn("How-To Guides", index_content)
# Verify guide files exist (index.md + guide(s))
guide_files = list(output_dir.glob("*.md"))
self.assertGreaterEqual(len(guide_files), 1) # At least index.md or guides
class TestAIEnhancementIntegration(unittest.TestCase):
"""Tests for AI Enhancement integration with HowToGuideBuilder (C3.3)"""
def setUp(self):
self.temp_dir = tempfile.mkdtemp()
def tearDown(self):
if os.path.exists(self.temp_dir):
shutil.rmtree(self.temp_dir)
def test_build_with_ai_enhancement_disabled(self):
"""Test building guides WITHOUT AI enhancement (backward compatibility)"""
examples = [
{
"example_id": "test_001",
"test_name": "test_user_registration",
"category": "workflow",
"code": """
def test_user_registration():
user = User.create(username="test", email="test@example.com")
assert user.id is not None
assert user.is_active is True
""",
"language": "python",
"file_path": "tests/test_user.py",
"line_start": 10,
"tags": ["authentication", "user"],
"ai_analysis": {
"tutorial_group": "User Management",
"best_practices": ["Validate email format"],
"common_mistakes": ["Not checking uniqueness"],
},
}
]
builder = HowToGuideBuilder()
output_dir = Path(self.temp_dir) / "guides"
# Build WITHOUT AI enhancement
collection = builder.build_guides_from_examples(
examples=examples,
grouping_strategy="ai-tutorial-group",
output_dir=output_dir,
enhance_with_ai=False,
ai_mode="none",
)
# Verify guides were created
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
# Verify output files exist
self.assertTrue(output_dir.exists())
self.assertTrue((output_dir / "index.md").exists())
def test_build_with_ai_enhancement_api_mode_mocked(self):
"""Test building guides WITH AI enhancement in API mode (mocked)"""
from unittest.mock import patch
examples = [
{
"example_id": "test_002",
"test_name": "test_data_scraping",
"category": "workflow",
"code": """
def test_data_scraping():
scraper = DocumentationScraper()
result = scraper.scrape("https://example.com/docs")
assert result.pages > 0
""",
"language": "python",
"file_path": "tests/test_scraper.py",
"line_start": 20,
"tags": ["scraping", "documentation"],
"ai_analysis": {
"tutorial_group": "Data Collection",
"best_practices": ["Handle rate limiting"],
"common_mistakes": ["Not handling SSL errors"],
},
}
]
builder = HowToGuideBuilder()
output_dir = Path(self.temp_dir) / "guides_enhanced"
# Mock GuideEnhancer to avoid actual AI calls
with patch("skill_seekers.cli.guide_enhancer.GuideEnhancer") as MockEnhancer:
mock_enhancer = MockEnhancer.return_value
mock_enhancer.mode = "api"
# Mock the enhance_guide method to return enhanced data
def mock_enhance_guide(guide_data):
enhanced = guide_data.copy()
# Return proper StepEnhancement objects
enhanced["step_enhancements"] = [
StepEnhancement(step_index=0, explanation="Test explanation", variations=[])
]
enhanced["troubleshooting_detailed"] = []
enhanced["prerequisites_detailed"] = []
enhanced["next_steps_detailed"] = []
enhanced["use_cases"] = []
return enhanced
mock_enhancer.enhance_guide = mock_enhance_guide
# Build WITH AI enhancement
collection = builder.build_guides_from_examples(
examples=examples,
grouping_strategy="ai-tutorial-group",
output_dir=output_dir,
enhance_with_ai=True,
ai_mode="api",
)
# Verify guides were created
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
# Verify enhancer was initialized
MockEnhancer.assert_called_once_with(mode="api")
def test_build_with_ai_enhancement_local_mode_mocked(self):
"""Test building guides WITH AI enhancement in LOCAL mode (mocked)"""
from unittest.mock import patch
examples = [
{
"example_id": "test_003",
"test_name": "test_api_integration",
"category": "workflow",
"code": """
def test_api_integration():
client = APIClient(base_url="https://api.example.com")
response = client.get("/users")
assert response.status_code == 200
""",
"language": "python",
"file_path": "tests/test_api.py",
"line_start": 30,
"tags": ["api", "integration"],
"ai_analysis": {
"tutorial_group": "API Testing",
"best_practices": ["Use environment variables"],
"common_mistakes": ["Hardcoded credentials"],
},
}
]
builder = HowToGuideBuilder()
output_dir = Path(self.temp_dir) / "guides_local"
# Mock GuideEnhancer for LOCAL mode
with patch("skill_seekers.cli.guide_enhancer.GuideEnhancer") as MockEnhancer:
mock_enhancer = MockEnhancer.return_value
mock_enhancer.mode = "local"
# Mock the enhance_guide method
def mock_enhance_guide(guide_data):
enhanced = guide_data.copy()
enhanced["step_enhancements"] = []
enhanced["troubleshooting_detailed"] = []
enhanced["prerequisites_detailed"] = []
enhanced["next_steps_detailed"] = []
enhanced["use_cases"] = []
return enhanced
mock_enhancer.enhance_guide = mock_enhance_guide
# Build WITH AI enhancement (LOCAL mode)
collection = builder.build_guides_from_examples(
examples=examples,
grouping_strategy="ai-tutorial-group",
output_dir=output_dir,
enhance_with_ai=True,
ai_mode="local",
)
# Verify guides were created
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
# Verify LOCAL mode was used
MockEnhancer.assert_called_once_with(mode="local")
def test_build_with_ai_enhancement_auto_mode(self):
"""Test building guides WITH AI enhancement in AUTO mode"""
from unittest.mock import patch
examples = [
{
"example_id": "test_004",
"test_name": "test_database_migration",
"category": "workflow",
"code": """
def test_database_migration():
migrator = DatabaseMigrator()
migrator.run_migrations()
assert migrator.current_version == "2.0"
""",
"language": "python",
"file_path": "tests/test_db.py",
"line_start": 40,
"tags": ["database", "migration"],
"ai_analysis": {
"tutorial_group": "Database Operations",
"best_practices": ["Backup before migration"],
"common_mistakes": ["Not testing rollback"],
},
}
]
builder = HowToGuideBuilder()
output_dir = Path(self.temp_dir) / "guides_auto"
# Mock GuideEnhancer for AUTO mode
with patch("skill_seekers.cli.guide_enhancer.GuideEnhancer") as MockEnhancer:
mock_enhancer = MockEnhancer.return_value
mock_enhancer.mode = "local" # AUTO mode detected LOCAL
def mock_enhance_guide(guide_data):
enhanced = guide_data.copy()
enhanced["step_enhancements"] = []
enhanced["troubleshooting_detailed"] = []
enhanced["prerequisites_detailed"] = []
enhanced["next_steps_detailed"] = []
enhanced["use_cases"] = []
return enhanced
mock_enhancer.enhance_guide = mock_enhance_guide
# Build WITH AI enhancement (AUTO mode)
collection = builder.build_guides_from_examples(
examples=examples,
grouping_strategy="ai-tutorial-group",
output_dir=output_dir,
enhance_with_ai=True,
ai_mode="auto",
)
# Verify guides were created
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
# Verify AUTO mode was used
MockEnhancer.assert_called_once_with(mode="auto")
def test_graceful_fallback_when_ai_fails(self):
"""Test graceful fallback when AI enhancement fails"""
from unittest.mock import patch
examples = [
{
"example_id": "test_005",
"test_name": "test_file_processing",
"category": "workflow",
"code": """
def test_file_processing():
processor = FileProcessor()
result = processor.process("data.csv")
assert result.rows == 100
""",
"language": "python",
"file_path": "tests/test_files.py",
"line_start": 50,
"tags": ["files", "processing"],
"ai_analysis": {
"tutorial_group": "Data Processing",
"best_practices": ["Validate file format"],
"common_mistakes": ["Not handling encoding"],
},
}
]
builder = HowToGuideBuilder()
output_dir = Path(self.temp_dir) / "guides_fallback"
# Mock GuideEnhancer to raise exception
with patch(
"skill_seekers.cli.guide_enhancer.GuideEnhancer",
side_effect=Exception("AI unavailable"),
):
# Should NOT crash - graceful fallback
collection = builder.build_guides_from_examples(
examples=examples,
grouping_strategy="ai-tutorial-group",
output_dir=output_dir,
enhance_with_ai=True,
ai_mode="api",
)
# Verify guides were still created (without enhancement)
self.assertIsInstance(collection, GuideCollection)
self.assertGreater(collection.total_guides, 0)
class TestExpandedWorkflowDetection(unittest.TestCase):
"""Tests for expanded workflow detection (issue #242)"""
def setUp(self):
self.builder = HowToGuideBuilder(enhance_with_ai=False)
def test_empty_examples_returns_empty_collection(self):
"""Test that empty examples returns valid empty GuideCollection"""
collection = self.builder.build_guides_from_examples([])
self.assertIsInstance(collection, GuideCollection)
self.assertEqual(collection.total_guides, 0)
self.assertEqual(collection.guides, [])
def test_non_workflow_examples_returns_empty_collection(self):
"""Test that non-workflow examples returns empty collection with diagnostics"""
examples = [
{"category": "instantiation", "test_name": "test_simple", "code": "x = 1"},
{"category": "method_call", "test_name": "test_call", "code": "obj.method()"},
]
collection = self.builder.build_guides_from_examples(examples)
self.assertIsInstance(collection, GuideCollection)
self.assertEqual(collection.total_guides, 0)
def test_workflow_example_detected(self):
"""Test that workflow category examples are detected"""
examples = [
{
"category": "workflow",
"test_name": "test_user_creation_workflow",
"code": "db = Database()\nuser = db.create_user()\nassert user.id",
"file_path": "tests/test.py",
"language": "python",
}
]
collection = self.builder.build_guides_from_examples(examples)
self.assertIsInstance(collection, GuideCollection)
# Should have at least one guide from the workflow
self.assertGreaterEqual(collection.total_guides, 0)
def test_guide_collection_always_valid(self):
"""Test that GuideCollection is always returned, never None"""
# Test various edge cases
test_cases = [
[], # Empty
[{"category": "unknown"}], # Unknown category
[{"category": "instantiation"}], # Non-workflow
]
for examples in test_cases:
collection = self.builder.build_guides_from_examples(examples)
self.assertIsNotNone(collection, f"Collection should not be None for {examples}")
self.assertIsInstance(collection, GuideCollection)
def test_heuristic_detection_4_assignments_3_calls(self):
"""Test heuristic detection: 4+ assignments and 3+ calls"""
# Code with 4 assignments and 3 method calls (should match heuristic)
code = """
def test_complex_setup():
db = Database() # assignment 1
user = User('Alice') # assignment 2
settings = Settings() # assignment 3
cache = Cache() # assignment 4
db.connect() # call 1
user.save() # call 2
cache.clear() # call 3
assert user.id
"""
# The heuristic should be checked in test_example_extractor
# For this test, we verify the code structure would match
import ast
tree = ast.parse(code)
func_node = tree.body[0]
# Count assignments
assignments = sum(
1 for n in ast.walk(func_node) if isinstance(n, (ast.Assign, ast.AugAssign))
)
# Count calls
calls = sum(1 for n in ast.walk(func_node) if isinstance(n, ast.Call))
# Verify heuristic thresholds
self.assertGreaterEqual(assignments, 4, "Should have 4+ assignments")
self.assertGreaterEqual(calls, 3, "Should have 3+ method calls")
def test_new_workflow_keywords_detection(self):
"""Test that new workflow keywords are detected (issue #242)"""
# New keywords added: complete, scenario, flow, multi_step, multistep,
# process, chain, sequence, pipeline, lifecycle
new_keywords = [
"complete",
"scenario",
"flow",
"multi_step",
"multistep",
"process",
"chain",
"sequence",
"pipeline",
"lifecycle",
]
# Check if all keywords are in integration_keywords list
integration_keywords = [
"workflow",
"integration",
"end_to_end",
"e2e",
"full",
"complete",
"scenario",
"flow",
"multi_step",
"multistep",
"process",
"chain",
"sequence",
"pipeline",
"lifecycle",
]
for keyword in new_keywords:
self.assertIn(
keyword,
integration_keywords,
f"Keyword '{keyword}' should be in integration_keywords",
)
def test_heuristic_does_not_match_simple_tests(self):
"""Test that simple tests don't match heuristic (< 4 assignments or < 3 calls)"""
import ast
# Simple test with only 2 assignments and 1 call (should NOT match)
simple_code = """
def test_simple():
user = User('Bob') # assignment 1
email = 'bob@test' # assignment 2
user.save() # call 1
assert user.id
"""
tree = ast.parse(simple_code)
func_node = tree.body[0]
# Count assignments
assignments = sum(
1 for n in ast.walk(func_node) if isinstance(n, (ast.Assign, ast.AugAssign))
)
# Count calls
calls = sum(1 for n in ast.walk(func_node) if isinstance(n, ast.Call))
# Verify it doesn't meet thresholds
self.assertLess(assignments, 4, "Simple test should have < 4 assignments")
self.assertLess(calls, 3, "Simple test should have < 3 calls")
def test_keyword_case_insensitive_matching(self):
"""Test that workflow keyword matching works regardless of case"""
# Keywords should match in test names regardless of case
test_cases = [
"test_workflow_example", # lowercase
"test_Workflow_Example", # mixed case
"test_WORKFLOW_EXAMPLE", # uppercase
"test_end_to_end_flow", # compound
"test_integration_scenario", # multiple keywords
]
for test_name in test_cases:
# Verify test name contains at least one keyword (case-insensitive)
integration_keywords = [
"workflow",
"integration",
"end_to_end",
"e2e",
"full",
"complete",
"scenario",
"flow",
"multi_step",
"multistep",
"process",
"chain",
"sequence",
"pipeline",
"lifecycle",
]
test_name_lower = test_name.lower()
has_keyword = any(kw in test_name_lower for kw in integration_keywords)
self.assertTrue(has_keyword, f"Test name '{test_name}' should contain workflow keyword")
if __name__ == "__main__":
unittest.main()
class TestNestedControlFlowSteps(unittest.TestCase):
"""Steps wrapped in control flow must still be extracted (regression:
top-level-only iteration returned ZERO steps for a `with`-wrapped body)."""
def setUp(self):
self.analyzer = WorkflowAnalyzer()
def test_with_block_yields_steps(self):
code = (
"def test_flow():\n"
" with open('f') as fh:\n"
" data = fh.read()\n"
" assert data\n"
)
steps = self.analyzer._extract_steps_python(code, {"code": code, "language": "python"})
self.assertEqual(len(steps), 1)
self.assertEqual(steps[0].code, "data = fh.read()")
self.assertEqual(steps[0].verification, "assert data")
def test_for_block_preserves_order(self):
code = "x = setup()\nfor i in range(3):\n y = do(i)\nassert y\n"
steps = self.analyzer._extract_steps_python(code, {"code": code, "language": "python"})
self.assertEqual([s.code for s in steps], ["x = setup()", "y = do(i)"])
self.assertEqual(steps[1].verification, "assert y")
def test_nested_function_bodies_are_not_steps(self):
code = "def helper():\n hidden = 1\n\nvisible = 2\n"
steps = self.analyzer._extract_steps_python(code, {"code": code, "language": "python"})
self.assertEqual([s.code for s in steps], ["visible = 2"])