test_path_generator.py•18.2 kB
"""
Unit tests for the path generator implementations.
"""
import pytest
from unittest.mock import patch, MagicMock, AsyncMock, call
from api.models import Resource, MCP, Node
from core.path_generator.category_based_subtopic_generator import CategoryBasedSubtopicGenerator
from core.path_generator.default_quiz_generator import DefaultQuizGenerator
from core.path_generator.tree_based_node_structure import TreeBasedNodeStructure
from core.path_generator.default_path_generator import DefaultPathGenerator
from core.path_generator.path_generator_factory import PathGeneratorFactory
class TestCategoryBasedSubtopicGenerator:
"""Tests for the CategoryBasedSubtopicGenerator implementation."""
def test_generate_subtopics(self):
"""Test the generate_subtopics method."""
# Mock the category service
mock_category_service = MagicMock()
mock_category_service.get_subtopics_for_category.return_value = [
"Introduction to Python",
"Python Basics",
"Python Functions",
"Python Classes"
]
with patch("core.path_generator.category_based_subtopic_generator.category_service", mock_category_service):
generator = CategoryBasedSubtopicGenerator()
subtopics = generator.generate_subtopics("Python", 10, "technology")
# Check results
assert len(subtopics) == 10
assert "Introduction to Python" in subtopics
# Check that category service was called
mock_category_service.get_subtopics_for_category.assert_called_once_with("Python", 10, "technology")
class TestDefaultQuizGenerator:
"""Tests for the DefaultQuizGenerator implementation."""
def test_generate_quiz(self):
"""Test the generate_quiz method."""
generator = DefaultQuizGenerator()
# Create test resources
resources = [
Resource(
id="r1",
title="Python Programming",
url="https://example.com/python",
type="article",
description="Learn Python programming",
duration=None,
readTime=15,
difficulty="beginner",
thumbnail=None
),
Resource(
id="r2",
title="Python Functions",
url="https://example.com/functions",
type="video",
description="Understanding Python functions",
duration=10,
readTime=None,
difficulty="intermediate",
thumbnail=None
)
]
# Generate a quiz
quiz = generator.generate_quiz("Python", "Python Basics", resources)
# Check results
assert quiz is not None
assert len(quiz.questions) >= 3
assert quiz.passingScore == 70
# Check that questions have all required fields
for question in quiz.questions:
assert question.id is not None
assert question.text is not None
assert len(question.options) == 4
assert 0 <= question.correctOptionIndex < 4
class TestTreeBasedNodeStructure:
"""Tests for the TreeBasedNodeStructure implementation."""
@pytest.mark.asyncio
async def test_create_node_structure(self):
"""Test the create_node_structure method."""
# Mock the quiz generator
mock_quiz_generator = MagicMock()
# Mock the youtube service
mock_youtube = MagicMock()
mock_youtube.search_videos_for_topic = AsyncMock(return_value=[
Resource(
id="v1",
title="Python Video",
url="https://youtube.com/watch?v=123",
type="video",
description="Python tutorial video",
duration=10,
readTime=None,
difficulty="beginner",
thumbnail=None
)
])
with patch("core.path_generator.tree_based_node_structure.youtube", mock_youtube):
service = TreeBasedNodeStructure(mock_quiz_generator)
# Create test resources
resources = [
Resource(
id="r1",
title="Python Programming",
url="https://example.com/python",
type="article",
description="Learn Python programming",
duration=None,
readTime=15,
difficulty="beginner",
thumbnail=None
),
Resource(
id="r2",
title="Python Functions",
url="https://example.com/functions",
type="video",
description="Understanding Python functions",
duration=10,
readTime=None,
difficulty="intermediate",
thumbnail=None
)
]
# Create test subtopics
subtopics = [
"Introduction to Python",
"Python Basics",
"Python Functions",
"Python Classes",
"Python Modules",
"Python Packages",
"Python Libraries",
"Python Frameworks",
"Python Web Development",
"Python Data Science"
]
# Create node structure
nodes, node_ids = await service.create_node_structure(
topic="Python",
subtopics=subtopics,
resources=resources,
min_nodes=5,
max_nodes=10,
min_width=2,
max_width=3,
min_height=2,
max_height=4,
language="en"
)
# Check results
assert len(nodes) >= 5
assert len(nodes) <= 10
assert len(node_ids) == len(nodes)
# Check that root node exists
root_id = node_ids[0]
assert root_id in nodes
assert nodes[root_id].title.startswith("Introduction to")
# Check that all nodes have resources
for node_id, node in nodes.items():
assert node.resources is not None
# Check that youtube service was called
assert mock_youtube.search_videos_for_topic.call_count > 0
def test_distribute_quizzes(self):
"""Test the distribute_quizzes method."""
# Mock the quiz generator
mock_quiz_generator = MagicMock()
service = TreeBasedNodeStructure(mock_quiz_generator)
# Create test nodes
nodes = {
"n1": Node(
id="n1",
title="Introduction to Python",
description="Get started with Python",
type="lesson",
resources=[],
prerequisites=[],
visualPosition={"x": 0, "y": 0, "level": 0}
),
"n2": Node(
id="n2",
title="Python Basics",
description="Learn Python basics",
type="lesson",
resources=[],
prerequisites=["n1"],
visualPosition={"x": 0, "y": 200, "level": 1}
),
"n3": Node(
id="n3",
title="Python Functions",
description="Learn Python functions",
type="lesson",
resources=[],
prerequisites=["n2"],
visualPosition={"x": 0, "y": 400, "level": 2}
),
"n4": Node(
id="n4",
title="Python Classes",
description="Learn Python classes",
type="lesson",
resources=[],
prerequisites=["n2"],
visualPosition={"x": 200, "y": 400, "level": 2}
)
}
# Create test resources
resources = [
Resource(
id="r1",
title="Python Programming",
url="https://example.com/python",
type="article",
description="Learn Python programming",
duration=None,
readTime=15,
difficulty="beginner",
thumbnail=None
)
]
# Distribute quizzes
updated_nodes = service.distribute_quizzes(
nodes=nodes,
node_ids=["n1", "n2", "n3", "n4"],
topic="Python",
resources=resources,
target_percentage=0.5
)
# Check results
assert len(updated_nodes) == 4
# Count nodes with quizzes
quiz_count = sum(1 for node in updated_nodes.values() if node.quiz is not None)
# A implementação atual distribui pelo menos 1 quiz, não necessariamente 50%
assert quiz_count >= 1
# Check that quiz generator was called
assert mock_quiz_generator.generate_quiz.call_count >= 1
class TestDefaultPathGenerator:
"""Tests for the DefaultPathGenerator implementation."""
@pytest.mark.asyncio
async def test_generate_learning_path(self):
"""Test the generate_learning_path method."""
# Mock the subtopic generator
mock_subtopic_generator = MagicMock()
mock_subtopic_generator.generate_subtopics.return_value = [
"Introduction to Python",
"Python Basics",
"Python Functions",
"Python Classes"
]
# Mock the node structure service
mock_node_structure = MagicMock()
mock_node_structure.create_node_structure = AsyncMock(return_value=(
{
"n1": Node(
id="n1",
title="Introduction to Python",
description="Get started with Python",
type="lesson",
resources=[],
prerequisites=[],
visualPosition={"x": 0, "y": 0, "level": 0}
),
"n2": Node(
id="n2",
title="Python Basics",
description="Learn Python basics",
type="lesson",
resources=[],
prerequisites=["n1"],
visualPosition={"x": 0, "y": 200, "level": 1}
)
},
["n1", "n2"]
))
mock_node_structure.distribute_quizzes.return_value = {
"n1": Node(
id="n1",
title="Introduction to Python",
description="Get started with Python",
type="lesson",
resources=[],
prerequisites=[],
visualPosition={"x": 0, "y": 0, "level": 0}
),
"n2": Node(
id="n2",
title="Python Basics",
description="Learn Python basics",
type="lesson",
resources=[],
prerequisites=["n1"],
visualPosition={"x": 0, "y": 200, "level": 1}
)
}
# Mock the category service
mock_category_service = MagicMock()
mock_category_service.detect_category.return_value = "technology"
# Mock the cache
mock_cache = MagicMock()
mock_cache.get.return_value = None
with patch("core.path_generator.default_path_generator.category_service", mock_category_service), \
patch("core.path_generator.default_path_generator.cache", mock_cache):
generator = DefaultPathGenerator(mock_subtopic_generator, mock_node_structure)
# Create test resources
resources = [
Resource(
id="r1",
title="Python Programming",
url="https://example.com/python",
type="article",
description="Learn Python programming",
duration=None,
readTime=15,
difficulty="beginner",
thumbnail=None
)
]
# Generate learning path
mcp = await generator.generate_learning_path(
topic="Python",
resources=resources,
min_nodes=2,
max_nodes=5,
min_width=2,
max_width=3,
min_height=2,
max_height=4,
language="en"
)
# Check results
assert isinstance(mcp, MCP)
assert mcp.topic == "Python"
assert mcp.category == "technology"
assert len(mcp.nodes) == 2
assert mcp.totalHours > 0
assert len(mcp.tags) > 0
# Check that subtopic generator was called
mock_subtopic_generator.generate_subtopics.assert_called_once()
# Check that node structure service was called
mock_node_structure.create_node_structure.assert_called_once()
mock_node_structure.distribute_quizzes.assert_called_once()
# Check that category service was called at least once with "Python"
assert mock_category_service.detect_category.call_count >= 1
assert call("Python") in mock_category_service.detect_category.call_args_list
# Check that cache was used
mock_cache.get.assert_called_once()
mock_cache.setex.assert_called_once()
def test_estimate_total_hours(self):
"""Test the estimate_total_hours method."""
generator = DefaultPathGenerator(MagicMock(), MagicMock())
# Create test resources
resources = [
Resource(
id="r1",
title="Python Programming",
url="https://example.com/python",
type="article",
description="Learn Python programming",
duration=None,
readTime=15,
difficulty="beginner",
thumbnail=None
),
Resource(
id="r2",
title="Python Functions",
url="https://example.com/functions",
type="video",
description="Understanding Python functions",
duration=10,
readTime=None,
difficulty="intermediate",
thumbnail=None
),
Resource(
id="r3",
title="Python Classes",
url="https://example.com/classes",
type="tutorial",
description="Learn Python classes",
duration=None,
readTime=None,
difficulty="advanced",
thumbnail=None
)
]
# Estimate total hours
total_hours = generator.estimate_total_hours(resources)
# Check results
assert total_hours > 0
assert isinstance(total_hours, int)
def test_generate_tags(self):
"""Test the generate_tags method."""
generator = DefaultPathGenerator(MagicMock(), MagicMock())
# Mock the category service
mock_category_service = MagicMock()
mock_category_service.detect_category.return_value = "technology"
with patch("core.path_generator.default_path_generator.category_service", mock_category_service):
# Create test resources
resources = [
Resource(
id="r1",
title="Python Programming",
url="https://example.com/python",
type="article",
description="Learn Python programming",
duration=None,
readTime=15,
difficulty="beginner",
thumbnail=None
),
Resource(
id="r2",
title="Python Functions",
url="https://example.com/functions",
type="video",
description="Understanding Python functions",
duration=10,
readTime=None,
difficulty="intermediate",
thumbnail=None
)
]
# Generate tags
tags = generator.generate_tags("Python", resources)
# Check results
assert len(tags) > 0
assert "python" in tags
assert "technology" in tags
# Check that category service was called at least once with "Python"
assert mock_category_service.detect_category.call_count >= 1
assert call("Python") in mock_category_service.detect_category.call_args_list
class TestPathGeneratorFactory:
"""Tests for the PathGeneratorFactory."""
def test_create_path_generator(self):
"""Test the create_path_generator method."""
# Clear existing instances
PathGeneratorFactory._instances = {}
# Create generator
generator = PathGeneratorFactory.create_path_generator("default")
# Check type
assert isinstance(generator, DefaultPathGenerator)
# Check singleton pattern
assert PathGeneratorFactory.create_path_generator("default") is generator