Files
Reklamator/tests/contract/test_landing_routes.py
T
gurixandClaude 13051200a6 Add description field to Product model
Issue: The landing page template supported displaying product descriptions,
but the Product model didn't load the description field from config.yaml files.

Changes:
- Added optional 'description' parameter to Product.__init__()
- Updated Product.to_dict() to include description (if present)
- Updated Product.from_dict() to load description from config
- Added test to verify descriptions are displayed on landing page

All 9 landing page tests passing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 15:32:20 +02:00

214 lines
8.1 KiB
Python

"""Contract tests for landing page routes"""
import pytest
import os
import yaml
@pytest.fixture
def test_products(app):
"""Create test products with various configurations"""
with app.app_context():
products_dir = os.path.join(app.config['DATA_DIR'], 'products')
# Product 1: Active with description
product1_dir = os.path.join(products_dir, 'product-001')
os.makedirs(product1_dir, exist_ok=True)
with open(os.path.join(product1_dir, 'config.yaml'), 'w') as f:
yaml.dump({
'product_id': 'product-001',
'name': 'Zebra Product',
'submission_url_slug': 'zebra-product',
'owner_language': 'en',
'assigned_owner_ids': [],
'status': 'active',
'description': 'A product for testing'
}, f)
# Product 2: Active without description
product2_dir = os.path.join(products_dir, 'product-002')
os.makedirs(product2_dir, exist_ok=True)
with open(os.path.join(product2_dir, 'config.yaml'), 'w') as f:
yaml.dump({
'product_id': 'product-002',
'name': 'Apple Product',
'submission_url_slug': 'apple-product',
'owner_language': 'en',
'assigned_owner_ids': [],
'status': 'active'
}, f)
# Product 3: Archived (should not appear)
product3_dir = os.path.join(products_dir, 'product-003')
os.makedirs(product3_dir, exist_ok=True)
with open(os.path.join(product3_dir, 'config.yaml'), 'w') as f:
yaml.dump({
'product_id': 'product-003',
'name': 'Archived Product',
'submission_url_slug': 'archived-product',
'owner_language': 'en',
'assigned_owner_ids': [],
'status': 'archived',
'description': 'This product is archived'
}, f)
# Product 4: Active but missing slug (should not appear)
product4_dir = os.path.join(products_dir, 'product-004')
os.makedirs(product4_dir, exist_ok=True)
with open(os.path.join(product4_dir, 'config.yaml'), 'w') as f:
yaml.dump({
'product_id': 'product-004',
'name': 'No Slug Product',
'submission_url_slug': '',
'owner_language': 'en',
'assigned_owner_ids': [],
'status': 'active',
'description': 'Product with missing slug'
}, f)
# Product 5: XSS test product
product5_dir = os.path.join(products_dir, 'product-005')
os.makedirs(product5_dir, exist_ok=True)
with open(os.path.join(product5_dir, 'config.yaml'), 'w') as f:
yaml.dump({
'product_id': 'product-005',
'name': '<script>alert("xss")</script>Evil Product',
'submission_url_slug': 'xss-product',
'owner_language': 'en',
'assigned_owner_ids': [],
'status': 'active',
'description': '<img src=x onerror=alert("xss")>Malicious description'
}, f)
yield
@pytest.mark.contract
def test_get_landing_page_with_products(client, test_products):
"""T002: GET / with active products returns 200 with product list HTML"""
response = client.get('/')
assert response.status_code == 200
assert b'<html' in response.data.lower()
# Should show Apple Product (first alphabetically)
assert b'Apple Product' in response.data
# Should show Zebra Product
assert b'Zebra Product' in response.data
# Should NOT show archived product
assert b'Archived Product' not in response.data
@pytest.mark.contract
def test_get_landing_page_no_products(client, app):
"""T003: GET / with no active products returns 200 with empty state message"""
# No test products created - products directory is empty
response = client.get('/')
assert response.status_code == 200
assert b'No products are currently accepting feedback' in response.data
@pytest.mark.contract
def test_get_landing_page_filters_archived(client, test_products):
"""T004: GET / excludes archived products"""
response = client.get('/')
assert response.status_code == 200
# Active products should be visible
assert b'Apple Product' in response.data
assert b'Zebra Product' in response.data
# Archived product should NOT be visible
assert b'Archived Product' not in response.data
assert b'archived-product' not in response.data
@pytest.mark.contract
def test_get_landing_page_sorting(client, test_products):
"""T005: GET / sorts products alphabetically (name, then product_id)"""
response = client.get('/')
assert response.status_code == 200
html = response.data.decode('utf-8')
# Apple Product should appear before Zebra Product (alphabetically)
apple_pos = html.find('Apple Product')
zebra_pos = html.find('Zebra Product')
assert apple_pos != -1, "Apple Product not found in response"
assert zebra_pos != -1, "Zebra Product not found in response"
assert apple_pos < zebra_pos, "Products not sorted alphabetically"
@pytest.mark.contract
def test_get_landing_page_xss_prevention(client, test_products):
"""T006: GET / escapes HTML in product names (XSS prevention)"""
response = client.get('/')
assert response.status_code == 200
html = response.data.decode('utf-8')
# Script tags should be escaped, not executed
assert '<script>' not in html, "Script tag not escaped in product name"
assert 'alert("xss")' not in html or '&lt;script&gt;' in html, "XSS vulnerability in product name"
# Image onerror should be escaped
assert '<img src=x onerror=' not in html, "XSS vulnerability in product description"
@pytest.mark.contract
def test_get_landing_page_missing_slug(client, test_products):
"""T007: GET / excludes products with missing submission_url_slug"""
response = client.get('/')
assert response.status_code == 200
# Product with missing slug should NOT appear
assert b'No Slug Product' not in response.data
# But other active products should appear
assert b'Apple Product' in response.data
@pytest.mark.contract
def test_landing_page_with_authenticated_user_renders_correctly(client, test_products, app):
"""Test that landing page renders correctly - verifies url_for('landing.index') works
This test verifies that the base.html template references url_for('landing.index')
instead of url_for('index'), which would cause a BuildError.
"""
# Just access the landing page - if url_for references are broken, this will fail
response = client.get('/')
assert response.status_code == 200
# Should contain products (proving the page rendered successfully)
assert b'Apple Product' in response.data
assert b'Zebra Product' in response.data
@pytest.mark.contract
def test_landing_page_displays_product_descriptions(client, test_products):
"""Test that product descriptions from config.yaml are displayed on landing page
This test verifies that when a product has a description field in its config.yaml,
that description is properly loaded by the Product model and displayed on the landing page.
"""
response = client.get('/')
assert response.status_code == 200
# Zebra Product has a description in the fixture
assert b'Zebra Product' in response.data
assert b'A product for testing' in response.data
# Apple Product has no description - should not show any placeholder
assert b'Apple Product' in response.data
# No description text should appear for Apple Product
html = response.data.decode('utf-8')
# Verify Zebra description is present
assert 'A product for testing' in html
# Verify the description appears between the product name and the submit button
zebra_section_start = html.find('Zebra Product')
zebra_section_end = html.find('Submit Feedback', zebra_section_start)
zebra_section = html[zebra_section_start:zebra_section_end]
assert 'A product for testing' in zebra_section