Remove 5 dead test files referencing deleted/renamed modules
These tests import classes and functions that no longer exist: - ContentGenerationService (renamed to ContentGenerator) - ContentRuleEngine, ContentHTMLParser (rule_engine.py deprecated) - ContentAugmenter (depends on dead rule_engine imports) - get_domain_from_site (removed from site_page_generator) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>main
parent
0c559654b4
commit
785fc9c7ac
|
|
@ -1,194 +0,0 @@
|
|||
"""
|
||||
Integration tests for content generation pipeline
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
from unittest.mock import Mock, patch
|
||||
from src.database.models import Project, User, GeneratedContent
|
||||
from src.database.repositories import ProjectRepository, GeneratedContentRepository
|
||||
from src.generation.service import ContentGenerationService
|
||||
from src.generation.job_config import JobConfig, TierConfig, ModelConfig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_project(db_session):
|
||||
"""Create a test project"""
|
||||
user = User(
|
||||
username="testuser",
|
||||
hashed_password="hashed",
|
||||
role="User"
|
||||
)
|
||||
db_session.add(user)
|
||||
db_session.commit()
|
||||
|
||||
project_data = {
|
||||
"main_keyword": "test automation",
|
||||
"word_count": 1000,
|
||||
"term_frequency": 3,
|
||||
"h2_total": 5,
|
||||
"h2_exact": 1,
|
||||
"h2_related_search": 1,
|
||||
"h2_entities": 2,
|
||||
"h3_total": 10,
|
||||
"h3_exact": 1,
|
||||
"h3_related_search": 2,
|
||||
"h3_entities": 3,
|
||||
"entities": ["automation tool", "testing framework", "ci/cd"],
|
||||
"related_searches": ["test automation best practices", "automation frameworks"]
|
||||
}
|
||||
|
||||
project_repo = ProjectRepository(db_session)
|
||||
project = project_repo.create(user.id, "Test Project", project_data)
|
||||
|
||||
return project
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_generated_content_repository(db_session, test_project):
|
||||
"""Test GeneratedContentRepository CRUD operations"""
|
||||
repo = GeneratedContentRepository(db_session)
|
||||
|
||||
content = repo.create(test_project.id, tier=1)
|
||||
|
||||
assert content.id is not None
|
||||
assert content.project_id == test_project.id
|
||||
assert content.tier == 1
|
||||
assert content.status == "pending"
|
||||
assert content.generation_stage == "title"
|
||||
|
||||
retrieved = repo.get_by_id(content.id)
|
||||
assert retrieved is not None
|
||||
assert retrieved.id == content.id
|
||||
|
||||
project_contents = repo.get_by_project_id(test_project.id)
|
||||
assert len(project_contents) == 1
|
||||
assert project_contents[0].id == content.id
|
||||
|
||||
content.title = "Test Title"
|
||||
content.status = "completed"
|
||||
updated = repo.update(content)
|
||||
assert updated.title == "Test Title"
|
||||
assert updated.status == "completed"
|
||||
|
||||
success = repo.set_active(content.id, test_project.id, tier=1)
|
||||
assert success is True
|
||||
|
||||
active = repo.get_active_by_project(test_project.id, tier=1)
|
||||
assert active is not None
|
||||
assert active.id == content.id
|
||||
assert active.is_active is True
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@patch.dict(os.environ, {"AI_API_KEY": "test-key"})
|
||||
def test_content_generation_service_initialization(db_session):
|
||||
"""Test ContentGenerationService initializes correctly"""
|
||||
with patch('src.generation.ai_client.OpenAI'):
|
||||
service = ContentGenerationService(db_session)
|
||||
|
||||
assert service.session is not None
|
||||
assert service.config is not None
|
||||
assert service.ai_client is not None
|
||||
assert service.content_repo is not None
|
||||
assert service.rule_engine is not None
|
||||
assert service.validator is not None
|
||||
assert service.augmenter is not None
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@patch.dict(os.environ, {"AI_API_KEY": "test-key"})
|
||||
def test_content_generation_flow_mocked(db_session, test_project):
|
||||
"""Test full content generation flow with mocked AI"""
|
||||
with patch('src.generation.ai_client.OpenAI'):
|
||||
service = ContentGenerationService(db_session)
|
||||
|
||||
service.ai_client.generate = Mock(return_value="Test Automation: Complete Guide")
|
||||
|
||||
outline = {
|
||||
"h1": "Test Automation Overview",
|
||||
"sections": [
|
||||
{"h2": "Test Automation Basics", "h3s": ["Getting Started", "Best Practices"]},
|
||||
{"h2": "Advanced Topics", "h3s": ["CI/CD Integration"]},
|
||||
{"h2": "Frequently Asked Questions", "h3s": ["What is test automation?", "How to start?"]}
|
||||
]
|
||||
}
|
||||
service.ai_client.generate_json = Mock(return_value=outline)
|
||||
|
||||
html_content = """
|
||||
<h1>Test Automation Overview</h1>
|
||||
<p>Test automation is essential for modern software development.</p>
|
||||
|
||||
<h2>Test Automation Basics</h2>
|
||||
<p>Understanding test automation fundamentals is crucial.</p>
|
||||
|
||||
<h3>Getting Started</h3>
|
||||
<p>Begin with test automation frameworks and tools.</p>
|
||||
|
||||
<h3>Best Practices</h3>
|
||||
<p>Follow test automation best practices for success.</p>
|
||||
|
||||
<h2>Advanced Topics</h2>
|
||||
<p>Explore advanced test automation techniques.</p>
|
||||
|
||||
<h3>CI/CD Integration</h3>
|
||||
<p>Integrate test automation with ci/cd pipelines.</p>
|
||||
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
|
||||
<h3>What is test automation?</h3>
|
||||
<p>What is test automation? Test automation is the practice of running tests automatically.</p>
|
||||
|
||||
<h3>How to start?</h3>
|
||||
<p>How to start? Begin by selecting an automation tool and testing framework.</p>
|
||||
"""
|
||||
|
||||
service.ai_client.generate = Mock(side_effect=[
|
||||
"Test Automation: Complete Guide",
|
||||
html_content
|
||||
])
|
||||
|
||||
try:
|
||||
content = service.generate_article(
|
||||
project=test_project,
|
||||
tier=1,
|
||||
title_model="test-model",
|
||||
outline_model="test-model",
|
||||
content_model="test-model",
|
||||
max_retries=1
|
||||
)
|
||||
|
||||
assert content is not None
|
||||
assert content.title is not None
|
||||
assert content.outline is not None
|
||||
assert content.status in ["completed", "failed"]
|
||||
|
||||
except Exception as e:
|
||||
pytest.skip(f"Generation failed (expected in mocked test): {e}")
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
def test_job_config_validation():
|
||||
"""Test JobConfig validation"""
|
||||
models = ModelConfig(
|
||||
title="anthropic/claude-3.5-sonnet",
|
||||
outline="anthropic/claude-3.5-sonnet",
|
||||
content="anthropic/claude-3.5-sonnet"
|
||||
)
|
||||
|
||||
tier = TierConfig(
|
||||
tier=1,
|
||||
article_count=5,
|
||||
models=models
|
||||
)
|
||||
|
||||
job = JobConfig(
|
||||
job_name="Integration Test Job",
|
||||
project_id=1,
|
||||
tiers=[tier]
|
||||
)
|
||||
|
||||
assert job.get_total_articles() == 5
|
||||
assert len(job.tiers) == 1
|
||||
assert job.tiers[0].tier == 1
|
||||
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
"""
|
||||
Unit tests for content augmenter
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from src.generation.augmenter import ContentAugmenter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def augmenter():
|
||||
return ContentAugmenter()
|
||||
|
||||
|
||||
def test_augment_outline_add_h2_keyword(augmenter):
|
||||
"""Test adding keyword to H2 headings"""
|
||||
outline = {
|
||||
"h1": "Main Title",
|
||||
"sections": [
|
||||
{"h2": "Introduction", "h3s": []},
|
||||
{"h2": "Advanced Topics", "h3s": []}
|
||||
]
|
||||
}
|
||||
|
||||
missing = {"h2_exact": 1}
|
||||
|
||||
result, log = augmenter.augment_outline(
|
||||
outline, missing, "test keyword", [], []
|
||||
)
|
||||
|
||||
assert "test keyword" in result["sections"][0]["h2"].lower()
|
||||
assert log["headings_modified"] > 0
|
||||
|
||||
|
||||
def test_augment_outline_add_h3_entities(augmenter):
|
||||
"""Test adding entity-based H3 headings"""
|
||||
outline = {
|
||||
"h1": "Main Title",
|
||||
"sections": [
|
||||
{"h2": "Section 1", "h3s": []}
|
||||
]
|
||||
}
|
||||
|
||||
missing = {"h3_entities": 2}
|
||||
entities = ["entity1", "entity2", "entity3"]
|
||||
|
||||
result, log = augmenter.augment_outline(
|
||||
outline, missing, "keyword", entities, []
|
||||
)
|
||||
|
||||
assert log["h3_added"] == 2
|
||||
assert any("entity1" in h3.lower()
|
||||
for s in result["sections"]
|
||||
for h3 in s.get("h3s", []))
|
||||
|
||||
|
||||
def test_augment_content_insert_keywords(augmenter):
|
||||
"""Test inserting keywords into content"""
|
||||
html = "<p>This is a paragraph with enough words to allow keyword insertion for testing purposes.</p>"
|
||||
missing = {"keyword_mentions": 2}
|
||||
|
||||
result, log = augmenter.augment_content(
|
||||
html, missing, "keyword", [], []
|
||||
)
|
||||
|
||||
assert log["keywords_inserted"] > 0
|
||||
assert "keyword" in result.lower()
|
||||
|
||||
|
||||
def test_augment_content_insert_entities(augmenter):
|
||||
"""Test inserting entities into content"""
|
||||
html = "<p>This is a long paragraph with many words that allows us to insert various terms naturally.</p>"
|
||||
missing = {"entity_mentions": 2}
|
||||
entities = ["entity1", "entity2"]
|
||||
|
||||
result, log = augmenter.augment_content(
|
||||
html, missing, "keyword", entities, []
|
||||
)
|
||||
|
||||
assert log["entities_inserted"] > 0
|
||||
|
||||
|
||||
def test_add_paragraph_with_terms(augmenter):
|
||||
"""Test adding a new paragraph with specific terms"""
|
||||
html = "<h1>Title</h1><p>Existing content</p>"
|
||||
terms = ["term1", "term2", "term3"]
|
||||
|
||||
result = augmenter.add_paragraph_with_terms(
|
||||
html, terms, "entity", "main keyword"
|
||||
)
|
||||
|
||||
assert "term1" in result or "term2" in result or "term3" in result
|
||||
assert "main keyword" in result
|
||||
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
"""
|
||||
Unit tests for content generation service
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
from src.generation.service import ContentGenerationService, GenerationError
|
||||
from src.database.models import Project, GeneratedContent
|
||||
from src.generation.rule_engine import ValidationResult
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_session():
|
||||
return Mock()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config():
|
||||
config = Mock()
|
||||
config.ai_service.max_tokens = 4000
|
||||
config.content_rules.cora_validation.round_averages_down = True
|
||||
config.content_rules.cora_validation.tier_1_strict = True
|
||||
return config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_project():
|
||||
project = Mock(spec=Project)
|
||||
project.id = 1
|
||||
project.main_keyword = "test keyword"
|
||||
project.word_count = 1000
|
||||
project.term_frequency = 2
|
||||
project.tier = 1
|
||||
project.h2_total = 5
|
||||
project.h2_exact = 1
|
||||
project.h2_related_search = 1
|
||||
project.h2_entities = 2
|
||||
project.h3_total = 10
|
||||
project.h3_exact = 1
|
||||
project.h3_related_search = 2
|
||||
project.h3_entities = 3
|
||||
project.entities = ["entity1", "entity2", "entity3"]
|
||||
project.related_searches = ["search1", "search2", "search3"]
|
||||
return project
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def service(mock_session, mock_config):
|
||||
with patch('src.generation.service.AIClient'):
|
||||
service = ContentGenerationService(mock_session, mock_config)
|
||||
return service
|
||||
|
||||
|
||||
def test_service_initialization(service):
|
||||
"""Test service initializes correctly"""
|
||||
assert service.session is not None
|
||||
assert service.config is not None
|
||||
assert service.ai_client is not None
|
||||
assert service.content_repo is not None
|
||||
assert service.rule_engine is not None
|
||||
|
||||
|
||||
def test_generate_title_success(service, mock_project):
|
||||
"""Test successful title generation"""
|
||||
service.ai_client.generate = Mock(return_value="Test Keyword Complete Guide")
|
||||
service.validator.validate_title = Mock(return_value=(True, []))
|
||||
|
||||
content_record = Mock(spec=GeneratedContent)
|
||||
content_record.title_attempts = 0
|
||||
service.content_repo.update = Mock()
|
||||
|
||||
result = service._generate_title(mock_project, content_record, "test-model", 3)
|
||||
|
||||
assert result == "Test Keyword Complete Guide"
|
||||
assert service.ai_client.generate.called
|
||||
|
||||
|
||||
def test_generate_title_validation_retry(service, mock_project):
|
||||
"""Test title generation retries on validation failure"""
|
||||
service.ai_client.generate = Mock(side_effect=[
|
||||
"Wrong Title",
|
||||
"Test Keyword Guide"
|
||||
])
|
||||
service.validator.validate_title = Mock(side_effect=[
|
||||
(False, ["Missing keyword"]),
|
||||
(True, [])
|
||||
])
|
||||
|
||||
content_record = Mock(spec=GeneratedContent)
|
||||
content_record.title_attempts = 0
|
||||
service.content_repo.update = Mock()
|
||||
|
||||
result = service._generate_title(mock_project, content_record, "test-model", 3)
|
||||
|
||||
assert result == "Test Keyword Guide"
|
||||
assert service.ai_client.generate.call_count == 2
|
||||
|
||||
|
||||
def test_generate_title_max_retries_exceeded(service, mock_project):
|
||||
"""Test title generation fails after max retries"""
|
||||
service.ai_client.generate = Mock(return_value="Wrong Title")
|
||||
service.validator.validate_title = Mock(return_value=(False, ["Missing keyword"]))
|
||||
|
||||
content_record = Mock(spec=GeneratedContent)
|
||||
content_record.title_attempts = 0
|
||||
service.content_repo.update = Mock()
|
||||
|
||||
with pytest.raises(GenerationError, match="validation failed"):
|
||||
service._generate_title(mock_project, content_record, "test-model", 2)
|
||||
|
||||
|
||||
def test_generate_outline_success(service, mock_project):
|
||||
"""Test successful outline generation"""
|
||||
outline_data = {
|
||||
"h1": "Test Keyword Overview",
|
||||
"sections": [
|
||||
{"h2": "Test Keyword Basics", "h3s": ["Sub 1", "Sub 2"]},
|
||||
{"h2": "Advanced Topics", "h3s": ["Sub 3"]}
|
||||
]
|
||||
}
|
||||
|
||||
service.ai_client.generate_json = Mock(return_value=outline_data)
|
||||
service.validator.validate_outline = Mock(return_value=(True, [], {}))
|
||||
|
||||
content_record = Mock(spec=GeneratedContent)
|
||||
content_record.outline_attempts = 0
|
||||
service.content_repo.update = Mock()
|
||||
|
||||
result = service._generate_outline(
|
||||
mock_project, "Test Title", content_record, "test-model", 3
|
||||
)
|
||||
|
||||
assert result == outline_data
|
||||
assert service.ai_client.generate_json.called
|
||||
|
||||
|
||||
def test_generate_outline_with_augmentation(service, mock_project):
|
||||
"""Test outline generation with programmatic augmentation"""
|
||||
initial_outline = {
|
||||
"h1": "Test Keyword Overview",
|
||||
"sections": [
|
||||
{"h2": "Introduction", "h3s": []}
|
||||
]
|
||||
}
|
||||
|
||||
augmented_outline = {
|
||||
"h1": "Test Keyword Overview",
|
||||
"sections": [
|
||||
{"h2": "Test Keyword Introduction", "h3s": ["Sub 1"]},
|
||||
{"h2": "Advanced Topics", "h3s": []}
|
||||
]
|
||||
}
|
||||
|
||||
service.ai_client.generate_json = Mock(return_value=initial_outline)
|
||||
service.validator.validate_outline = Mock(side_effect=[
|
||||
(False, ["Not enough H2s"], {"h2_exact": 1}),
|
||||
(True, [], {})
|
||||
])
|
||||
service.augmenter.augment_outline = Mock(return_value=(augmented_outline, {}))
|
||||
|
||||
content_record = Mock(spec=GeneratedContent)
|
||||
content_record.outline_attempts = 0
|
||||
content_record.augmented = False
|
||||
service.content_repo.update = Mock()
|
||||
|
||||
result = service._generate_outline(
|
||||
mock_project, "Test Title", content_record, "test-model", 3
|
||||
)
|
||||
|
||||
assert service.augmenter.augment_outline.called
|
||||
|
||||
|
||||
def test_generate_content_success(service, mock_project):
|
||||
"""Test successful content generation"""
|
||||
html_content = "<h1>Test</h1><p>Content</p>"
|
||||
|
||||
service.ai_client.generate = Mock(return_value=html_content)
|
||||
|
||||
validation_result = Mock(spec=ValidationResult)
|
||||
validation_result.passed = True
|
||||
validation_result.errors = []
|
||||
validation_result.warnings = []
|
||||
validation_result.to_dict = Mock(return_value={})
|
||||
|
||||
service.validator.validate_content = Mock(return_value=(True, validation_result))
|
||||
|
||||
content_record = Mock(spec=GeneratedContent)
|
||||
content_record.content_attempts = 0
|
||||
service.content_repo.update = Mock()
|
||||
|
||||
outline = {"h1": "Test", "sections": []}
|
||||
|
||||
result = service._generate_content(
|
||||
mock_project, "Test Title", outline, content_record, "test-model", 3
|
||||
)
|
||||
|
||||
assert result == html_content
|
||||
|
||||
|
||||
def test_format_outline_for_prompt(service):
|
||||
"""Test outline formatting for content prompt"""
|
||||
outline = {
|
||||
"h1": "Main Heading",
|
||||
"sections": [
|
||||
{"h2": "Section 1", "h3s": ["Sub 1", "Sub 2"]},
|
||||
{"h2": "Section 2", "h3s": ["Sub 3"]}
|
||||
]
|
||||
}
|
||||
|
||||
result = service._format_outline_for_prompt(outline)
|
||||
|
||||
assert "H1: Main Heading" in result
|
||||
assert "H2: Section 1" in result
|
||||
assert "H3: Sub 1" in result
|
||||
assert "H2: Section 2" in result
|
||||
|
||||
|
|
@ -1,451 +0,0 @@
|
|||
"""
|
||||
Unit tests for content rule engine
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock
|
||||
from src.generation.rule_engine import (
|
||||
ContentRuleEngine,
|
||||
ContentHTMLParser,
|
||||
ValidationResult,
|
||||
ValidationIssue
|
||||
)
|
||||
from src.database.models import Project
|
||||
from src.core.config import Config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config():
|
||||
"""Mock configuration for tests"""
|
||||
config = Mock()
|
||||
config.get = Mock(side_effect=lambda key, default={}: {
|
||||
"content_rules.universal": {
|
||||
"min_content_length": 1000,
|
||||
"max_content_length": 5000,
|
||||
"title_exact_match_required": True,
|
||||
"h1_exact_match_required": True,
|
||||
"h2_exact_match_min": 1,
|
||||
"h3_exact_match_min": 1,
|
||||
"faq_section_required": True,
|
||||
"image_alt_text_keyword_required": True,
|
||||
"image_alt_text_entity_required": True
|
||||
},
|
||||
"content_rules.cora_validation": {
|
||||
"enabled": True,
|
||||
"tier_1_strict": True,
|
||||
"tier_2_plus_warn_only": True,
|
||||
"round_averages_down": True
|
||||
}
|
||||
}.get(key, default))
|
||||
return config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_project():
|
||||
"""Sample project with CORA data"""
|
||||
project = Mock(spec=Project)
|
||||
project.id = 1
|
||||
project.main_keyword = "shaft machining"
|
||||
project.tier = 1
|
||||
project.entities = ["CNC", "lathe", "precision"]
|
||||
project.related_searches = ["shaft machining process", "machining techniques"]
|
||||
project.h1_exact = 1
|
||||
project.h1_related_search = 0
|
||||
project.h1_entities = 1
|
||||
project.h2_total = 5
|
||||
project.h2_exact = 1
|
||||
project.h2_related_search = 2
|
||||
project.h2_entities = 2
|
||||
project.h3_total = 8
|
||||
project.h3_exact = 1
|
||||
project.h3_related_search = 3
|
||||
project.h3_entities = 3
|
||||
return project
|
||||
|
||||
|
||||
class TestContentHTMLParser:
|
||||
"""Tests for HTML parser"""
|
||||
|
||||
def test_parse_title(self):
|
||||
html = "<html><head><title>Shaft Machining Guide</title></head></html>"
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
assert parser.title == "Shaft Machining Guide"
|
||||
|
||||
def test_parse_meta_description(self):
|
||||
html = '<html><head><meta name="description" content="Complete guide to shaft machining"></head></html>'
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
assert parser.meta_description == "Complete guide to shaft machining"
|
||||
|
||||
def test_parse_headings(self):
|
||||
html = """
|
||||
<html><body>
|
||||
<h1>Main Heading about Shaft Machining</h1>
|
||||
<h2>Understanding CNC</h2>
|
||||
<h2>Shaft Machining Process</h2>
|
||||
<h3>What is a lathe?</h3>
|
||||
<h3>Precision techniques</h3>
|
||||
<h3>FAQ about shaft machining</h3>
|
||||
</body></html>
|
||||
"""
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
|
||||
assert len(parser.h1_tags) == 1
|
||||
assert "Shaft Machining" in parser.h1_tags[0]
|
||||
assert len(parser.h2_tags) == 2
|
||||
assert len(parser.h3_tags) == 3
|
||||
|
||||
def test_parse_images(self):
|
||||
html = """
|
||||
<html><body>
|
||||
<img src="image1.jpg" alt="Shaft machining with CNC lathe">
|
||||
<img src="image2.jpg" alt="Precision tools">
|
||||
</body></html>
|
||||
"""
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
|
||||
assert len(parser.images) == 2
|
||||
assert parser.images[0]["alt"] == "Shaft machining with CNC lathe"
|
||||
assert parser.images[1]["alt"] == "Precision tools"
|
||||
|
||||
def test_parse_links(self):
|
||||
html = """
|
||||
<html><body>
|
||||
<a href="/home">Home Page</a>
|
||||
<a href="/article">Related Article</a>
|
||||
</body></html>
|
||||
"""
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
|
||||
assert len(parser.links) == 2
|
||||
assert parser.links[0]["href"] == "/home"
|
||||
assert "Home Page" in parser.links[0]["text"]
|
||||
|
||||
def test_parse_text_content(self):
|
||||
html = """
|
||||
<html><body>
|
||||
<h1>Title</h1>
|
||||
<p>This is some content about shaft machining and CNC operations.</p>
|
||||
<p>More content here with precision lathe work.</p>
|
||||
</body></html>
|
||||
"""
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
|
||||
assert "shaft machining" in parser.text_content.lower()
|
||||
assert "CNC" in parser.text_content
|
||||
assert len(parser.text_content.split()) > 10
|
||||
|
||||
|
||||
class TestValidationResult:
|
||||
"""Tests for ValidationResult class"""
|
||||
|
||||
def test_initial_state(self):
|
||||
result = ValidationResult(passed=True)
|
||||
assert result.passed is True
|
||||
assert len(result.errors) == 0
|
||||
assert len(result.warnings) == 0
|
||||
|
||||
def test_add_error(self):
|
||||
result = ValidationResult(passed=True)
|
||||
result.add_error("test_rule", "Test error", expected=5, actual=3)
|
||||
|
||||
assert result.passed is False
|
||||
assert len(result.errors) == 1
|
||||
assert result.errors[0].rule_name == "test_rule"
|
||||
assert result.errors[0].severity == "error"
|
||||
|
||||
def test_add_warning(self):
|
||||
result = ValidationResult(passed=True)
|
||||
result.add_warning("test_rule", "Test warning", expected=5, actual=4)
|
||||
|
||||
assert result.passed is True
|
||||
assert len(result.warnings) == 1
|
||||
assert result.warnings[0].severity == "warning"
|
||||
|
||||
def test_to_dict(self):
|
||||
result = ValidationResult(passed=False)
|
||||
result.add_error("rule1", "Error message", expected=5, actual=3)
|
||||
result.add_warning("rule2", "Warning message", expected=10, actual=8)
|
||||
|
||||
data = result.to_dict()
|
||||
assert data["passed"] is False
|
||||
assert len(data["errors"]) == 1
|
||||
assert len(data["warnings"]) == 1
|
||||
assert data["errors"][0]["rule"] == "rule1"
|
||||
assert data["warnings"][0]["rule"] == "rule2"
|
||||
|
||||
|
||||
class TestUniversalRules:
|
||||
"""Tests for universal rule validation"""
|
||||
|
||||
def test_content_length_validation(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
short_html = "<html><body><h1>Shaft machining</h1><p>Short content.</p></body></html>"
|
||||
result = engine.validate(short_html, sample_project)
|
||||
|
||||
assert not result.passed
|
||||
assert any("too short" in e.message for e in result.errors)
|
||||
|
||||
def test_title_keyword_required(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html_without_keyword = "<html><head><title>Generic Title</title></head><body>" + "word " * 1500 + "</body></html>"
|
||||
result = engine.validate(html_without_keyword, sample_project)
|
||||
|
||||
assert any("title" in e.rule_name.lower() for e in result.errors)
|
||||
|
||||
def test_h1_keyword_required(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Generic Heading</h1>
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
assert any("h1" in e.rule_name.lower() for e in result.errors)
|
||||
|
||||
def test_h2_keyword_minimum(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining Basics</h1>
|
||||
<h2>Generic Topic</h2>
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
assert any("h2_exact_match_min" in e.rule_name for e in result.errors)
|
||||
|
||||
def test_faq_section_required(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining Basics</h1>
|
||||
<h2>Shaft Machining Process</h2>
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
assert any("faq" in e.rule_name.lower() for e in result.errors)
|
||||
|
||||
def test_image_alt_text_validation(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining Basics</h1>
|
||||
<h2>FAQ about shaft machining</h2>
|
||||
<h2>Shaft Machining Techniques</h2>
|
||||
<h3>What is shaft machining?</h3>
|
||||
<img src="test.jpg" alt="Generic image">
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
assert any("image_alt_text" in e.rule_name for e in result.errors)
|
||||
|
||||
|
||||
class TestCORAValidation:
|
||||
"""Tests for CORA-specific validation"""
|
||||
|
||||
def test_tier_1_strict_validation(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
sample_project.tier = 1
|
||||
sample_project.h2_total = 5
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining with CNC</h1>
|
||||
<h2>Shaft Machining Process</h2>
|
||||
<h2>Understanding CNC</h2>
|
||||
<h3>What is shaft machining?</h3>
|
||||
<h3>FAQ</h3>
|
||||
<img src="test.jpg" alt="Shaft machining with CNC lathe">
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
h2_errors = [e for e in result.errors if "h2_total" in e.rule_name]
|
||||
assert len(h2_errors) > 0
|
||||
assert h2_errors[0].expected == 5
|
||||
assert h2_errors[0].actual == 2
|
||||
|
||||
def test_tier_2_warning_only(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
sample_project.tier = 2
|
||||
sample_project.h2_total = 5
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining with CNC</h1>
|
||||
<h2>Shaft Machining Process</h2>
|
||||
<h2>Understanding CNC</h2>
|
||||
<h3>What is shaft machining?</h3>
|
||||
<h3>FAQ</h3>
|
||||
<img src="test.jpg" alt="Shaft machining with CNC lathe">
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
h2_warnings = [w for w in result.warnings if "h2_total" in w.rule_name]
|
||||
assert len(h2_warnings) > 0
|
||||
|
||||
h2_errors = [e for e in result.errors if "h2_total" in e.rule_name]
|
||||
assert len(h2_errors) == 0
|
||||
|
||||
def test_keyword_entity_counting(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining Basics</h1>
|
||||
<h2>Shaft Machining Process</h2>
|
||||
<h2>Understanding CNC Operations</h2>
|
||||
<h2>Working with Precision Lathe</h2>
|
||||
<h3>What is shaft machining?</h3>
|
||||
<h3>CNC Techniques</h3>
|
||||
<h3>FAQ</h3>
|
||||
<img src="test.jpg" alt="Shaft machining with CNC">
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
parser = ContentHTMLParser()
|
||||
parser.feed(html)
|
||||
|
||||
counts = engine._count_keyword_entities(parser, sample_project)
|
||||
|
||||
assert counts["h1_exact"] == 1
|
||||
assert counts["h2_exact"] == 1
|
||||
assert counts["h2_entities"] >= 2
|
||||
assert counts["h3_exact"] == 1
|
||||
|
||||
def test_round_averages_down(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
sample_project.h2_total = 5.6
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Shaft Machining Guide</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining with CNC</h1>
|
||||
<h2>Shaft Machining Process</h2>
|
||||
<h2>Understanding CNC</h2>
|
||||
<h2>Lathe Operations</h2>
|
||||
<h2>Precision Work</h2>
|
||||
<h2>Best Practices</h2>
|
||||
<h3>What is shaft machining?</h3>
|
||||
<h3>FAQ</h3>
|
||||
<img src="test.jpg" alt="Shaft machining with CNC">
|
||||
<p>""" + "word " * 1500 + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
h2_issues = [e for e in result.errors if "h2_total" in e.rule_name]
|
||||
if h2_issues:
|
||||
assert h2_issues[0].expected == 5
|
||||
|
||||
|
||||
class TestValidContent:
|
||||
"""Tests for content that should pass validation"""
|
||||
|
||||
def test_fully_compliant_content(self, mock_config, sample_project):
|
||||
engine = ContentRuleEngine(mock_config)
|
||||
|
||||
html = """
|
||||
<html>
|
||||
<head><title>Complete Guide to Shaft Machining</title></head>
|
||||
<body>
|
||||
<h1>Shaft Machining: CNC Operations</h1>
|
||||
|
||||
<h2>Shaft Machining Process Explained</h2>
|
||||
<p>Content about the main process...</p>
|
||||
|
||||
<h2>Understanding CNC Technology</h2>
|
||||
<p>More content...</p>
|
||||
|
||||
<h2>Working with Precision Lathe</h2>
|
||||
<p>Additional information...</p>
|
||||
|
||||
<h2>Shaft Machining Techniques</h2>
|
||||
<p>Techniques details...</p>
|
||||
|
||||
<h2>Best Practices in CNC</h2>
|
||||
<p>Best practices...</p>
|
||||
|
||||
<h3>What is shaft machining?</h3>
|
||||
<p>Definition and explanation...</p>
|
||||
|
||||
<h3>CNC Lathe Operations</h3>
|
||||
<p>Operations details...</p>
|
||||
|
||||
<h3>Precision Techniques</h3>
|
||||
<p>Techniques information...</p>
|
||||
|
||||
<h3>Shaft Machining Process Guide</h3>
|
||||
<p>Process details...</p>
|
||||
|
||||
<h3>Understanding Machining Techniques</h3>
|
||||
<p>Techniques overview...</p>
|
||||
|
||||
<h3>CNC Setup and Shaft Machining Process</h3>
|
||||
<p>Setup instructions...</p>
|
||||
|
||||
<h3>Lathe Maintenance for Machining Techniques</h3>
|
||||
<p>Maintenance tips...</p>
|
||||
|
||||
<h3>FAQ: Common Questions about Shaft Machining</h3>
|
||||
<p>Frequently asked questions...</p>
|
||||
|
||||
<img src="image1.jpg" alt="Shaft machining with CNC lathe">
|
||||
<img src="image2.jpg" alt="Precision shaft machining setup">
|
||||
|
||||
<p>""" + " ".join(["shaft machining process details and information"] * 250) + """</p>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
result = engine.validate(html, sample_project)
|
||||
|
||||
assert result.passed is True
|
||||
assert len(result.errors) == 0
|
||||
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
"""
|
||||
Unit tests for site page generator
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import Mock, MagicMock
|
||||
from src.generation.site_page_generator import generate_site_pages, get_domain_from_site
|
||||
from src.database.models import SiteDeployment, SitePage
|
||||
|
||||
|
||||
def test_get_domain_from_site_with_custom_hostname():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.custom_hostname = "www.example.com"
|
||||
site.pull_zone_bcdn_hostname = "site123.b-cdn.net"
|
||||
|
||||
domain = get_domain_from_site(site)
|
||||
|
||||
assert domain == "www.example.com"
|
||||
|
||||
|
||||
def test_get_domain_from_site_without_custom_hostname():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.custom_hostname = None
|
||||
site.pull_zone_bcdn_hostname = "site123.b-cdn.net"
|
||||
|
||||
domain = get_domain_from_site(site)
|
||||
|
||||
assert domain == "site123.b-cdn.net"
|
||||
|
||||
|
||||
def test_generate_site_pages_success():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 1
|
||||
site.custom_hostname = "www.example.com"
|
||||
site.pull_zone_bcdn_hostname = "site123.b-cdn.net"
|
||||
site.template_name = "modern"
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(return_value=False)
|
||||
page_repo.create = Mock(return_value=Mock(spec=SitePage, id=1))
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Full HTML Page</html>")
|
||||
|
||||
pages = generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
assert len(pages) == 3
|
||||
assert page_repo.create.call_count == 3
|
||||
|
||||
create_calls = page_repo.create.call_args_list
|
||||
page_types_created = [call[1]["page_type"] for call in create_calls]
|
||||
assert "about" in page_types_created
|
||||
assert "contact" in page_types_created
|
||||
assert "privacy" in page_types_created
|
||||
|
||||
|
||||
def test_generate_site_pages_with_basic_template():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 2
|
||||
site.custom_hostname = None
|
||||
site.pull_zone_bcdn_hostname = "test-site.b-cdn.net"
|
||||
site.template_name = "basic"
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(return_value=False)
|
||||
page_repo.create = Mock(return_value=Mock(spec=SitePage, id=1))
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Page</html>")
|
||||
|
||||
pages = generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
assert len(pages) == 3
|
||||
format_calls = template_service.format_content.call_args_list
|
||||
|
||||
for call in format_calls:
|
||||
assert call[1]["template_name"] == "basic"
|
||||
|
||||
|
||||
def test_generate_site_pages_skips_existing_pages():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 3
|
||||
site.custom_hostname = "www.test.com"
|
||||
site.template_name = "modern"
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(side_effect=[True, False, False])
|
||||
page_repo.create = Mock(return_value=Mock(spec=SitePage, id=1))
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Page</html>")
|
||||
|
||||
pages = generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
assert len(pages) == 2
|
||||
assert page_repo.create.call_count == 2
|
||||
|
||||
|
||||
def test_generate_site_pages_uses_default_template_when_none():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 4
|
||||
site.custom_hostname = "www.example.com"
|
||||
site.template_name = None
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(return_value=False)
|
||||
page_repo.create = Mock(return_value=Mock(spec=SitePage, id=1))
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Page</html>")
|
||||
|
||||
pages = generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
format_calls = template_service.format_content.call_args_list
|
||||
|
||||
for call in format_calls:
|
||||
assert call[1]["template_name"] == "basic"
|
||||
|
||||
|
||||
def test_generate_site_pages_correct_content_structure():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 5
|
||||
site.custom_hostname = "www.test.com"
|
||||
site.template_name = "modern"
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(return_value=False)
|
||||
created_pages = []
|
||||
|
||||
def mock_create(**kwargs):
|
||||
page = Mock(spec=SitePage, id=len(created_pages) + 1)
|
||||
created_pages.append(kwargs)
|
||||
return page
|
||||
|
||||
page_repo.create = mock_create
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Full Page</html>")
|
||||
|
||||
pages = generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
assert len(created_pages) == 3
|
||||
|
||||
for page_data in created_pages:
|
||||
assert page_data["site_deployment_id"] == 5
|
||||
assert page_data["page_type"] in ["about", "contact", "privacy"]
|
||||
assert page_data["content"] == "<html>Full Page</html>"
|
||||
|
||||
|
||||
def test_generate_site_pages_page_titles():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 6
|
||||
site.custom_hostname = "www.test.com"
|
||||
site.template_name = "basic"
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(return_value=False)
|
||||
page_repo.create = Mock(return_value=Mock(spec=SitePage, id=1))
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Page</html>")
|
||||
|
||||
pages = generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
format_calls = template_service.format_content.call_args_list
|
||||
|
||||
titles_used = [call[1]["title"] for call in format_calls]
|
||||
assert "About Us" in titles_used
|
||||
assert "Contact" in titles_used
|
||||
assert "Privacy Policy" in titles_used
|
||||
|
||||
|
||||
def test_generate_site_pages_error_handling():
|
||||
site = Mock(spec=SiteDeployment)
|
||||
site.id = 7
|
||||
site.custom_hostname = "www.test.com"
|
||||
site.template_name = "modern"
|
||||
|
||||
page_repo = Mock()
|
||||
page_repo.exists = Mock(return_value=False)
|
||||
page_repo.create = Mock(side_effect=Exception("Database error"))
|
||||
|
||||
template_service = Mock()
|
||||
template_service.format_content = Mock(return_value="<html>Page</html>")
|
||||
|
||||
with pytest.raises(Exception) as exc_info:
|
||||
generate_site_pages(site, page_repo, template_service)
|
||||
|
||||
assert "Database error" in str(exc_info.value)
|
||||
Loading…
Reference in New Issue