survey_custom_certificate_t.../tests/test_property_preview_generation_standalone.py
2025-11-29 08:46:04 +07:00

316 lines
11 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Standalone property-based test for preview generation.
This test verifies that preview generation works correctly by testing
the certificate generation logic with sample data.
Feature: survey-custom-certificate-template, Property 9: Preview generation
Validates: Requirements 4.2
"""
import sys
import os
import io
# Add parent directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
try:
from hypothesis import given, settings, HealthCheck
HYPOTHESIS_AVAILABLE = True
except ImportError:
print("ERROR: Hypothesis is not installed. Install with: pip install hypothesis")
sys.exit(1)
try:
from docx import Document
DOCX_AVAILABLE = True
except ImportError:
print("ERROR: python-docx is not installed. Install with: pip install python-docx")
sys.exit(1)
# Import our custom strategies
from hypothesis_strategies import docx_with_mappings_and_data
def generate_sample_data():
"""
Generate sample data for preview (mimics wizard's _generate_sample_data).
"""
from datetime import datetime
return {
'survey_title': 'Sample Course Title',
'survey_description': 'Sample course description',
'partner_name': 'John Doe',
'partner_email': 'john.doe@example.com',
'email': 'john.doe@example.com',
'create_date': datetime.now().strftime('%Y-%m-%d'),
'completion_date': datetime.now().strftime('%Y-%m-%d'),
'scoring_percentage': '95.5',
'scoring_total': '100',
}
def replace_placeholders_in_docx(docx_binary, mappings, data):
"""
Replace placeholders in a DOCX document with data.
This simulates the certificate generator's placeholder replacement logic.
"""
# Load the document
doc_stream = io.BytesIO(docx_binary)
doc = Document(doc_stream)
# Build a replacement dictionary
replacements = {}
for mapping in mappings['placeholders']:
key = mapping['key']
value_type = mapping['value_type']
if value_type == 'custom_text':
# Use custom text
replacements[key] = mapping.get('custom_text', '')
else:
# Use data from the data dictionary
value_field = mapping.get('value_field', '')
if value_field and value_field in data:
replacements[key] = str(data[value_field])
else:
# Use empty string for unmapped fields
replacements[key] = ''
# Replace placeholders in paragraphs
for paragraph in doc.paragraphs:
for key, value in replacements.items():
if key in paragraph.text:
# Replace in the paragraph text
for run in paragraph.runs:
if key in run.text:
run.text = run.text.replace(key, value)
# Replace placeholders in tables
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
for key, value in replacements.items():
if key in paragraph.text:
for run in paragraph.runs:
if key in run.text:
run.text = run.text.replace(key, value)
# Replace placeholders in headers/footers
for section in doc.sections:
# Header
for paragraph in section.header.paragraphs:
for key, value in replacements.items():
if key in paragraph.text:
for run in paragraph.runs:
if key in run.text:
run.text = run.text.replace(key, value)
# Footer
for paragraph in section.footer.paragraphs:
for key, value in replacements.items():
if key in paragraph.text:
for run in paragraph.runs:
if key in run.text:
run.text = run.text.replace(key, value)
# Save to bytes
output_stream = io.BytesIO()
doc.save(output_stream)
output_stream.seek(0)
return output_stream.read()
def extract_all_text_from_docx(docx_binary):
"""Extract all text content from a DOCX document."""
doc_stream = io.BytesIO(docx_binary)
doc = Document(doc_stream)
text_parts = []
# Extract from paragraphs
for paragraph in doc.paragraphs:
text_parts.append(paragraph.text)
# Extract from tables
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
text_parts.append(paragraph.text)
# Extract from headers/footers
for section in doc.sections:
for paragraph in section.header.paragraphs:
text_parts.append(paragraph.text)
for paragraph in section.footer.paragraphs:
text_parts.append(paragraph.text)
return ' '.join(text_parts)
def test_property_9_preview_generation():
"""
Feature: survey-custom-certificate-template, Property 9: Preview generation
For any configured template, clicking the preview button should generate
a sample certificate with all placeholders replaced.
Validates: Requirements 4.2
This property test verifies that:
1. Preview generation succeeds for any valid template with mappings
2. The generated preview is a valid DOCX document
3. All placeholders in the template are replaced (no placeholders remain)
4. The preview contains either sample data or custom text for each placeholder
"""
print("\nTesting Property 9: Preview generation")
print("=" * 60)
test_count = 0
failures = []
@given(test_case=docx_with_mappings_and_data())
@settings(
max_examples=100,
deadline=None,
suppress_health_check=[HealthCheck.function_scoped_fixture]
)
def check_preview_generation(test_case):
nonlocal test_count, failures
test_count += 1
docx_binary, mappings, data = test_case
# Generate sample data (as the wizard would do)
sample_data = generate_sample_data()
# Merge with test data to ensure we have all fields
for key, value in data.items():
if key not in sample_data:
sample_data[key] = value
# Property 1: Preview generation should succeed
try:
preview_docx = replace_placeholders_in_docx(docx_binary, mappings, sample_data)
except Exception as e:
failures.append(f"Preview generation failed: {e}")
raise AssertionError(f"Preview generation should succeed for any valid template: {e}")
# Property 2: The generated preview should be a valid DOCX
try:
preview_stream = io.BytesIO(preview_docx)
preview_doc = Document(preview_stream)
# If we can load it, it's valid
except Exception as e:
failures.append(f"Generated preview is not a valid DOCX: {e}")
raise AssertionError(f"Generated preview should be a valid DOCX document: {e}")
# Property 3: All placeholders should be replaced (no placeholders remain)
preview_text = extract_all_text_from_docx(preview_docx)
# Check for any remaining placeholders
import re
remaining_placeholders = re.findall(r'\{key\.[a-zA-Z0-9_]+\}', preview_text)
if remaining_placeholders:
failures.append(
f"Preview contains unreplaced placeholders: {remaining_placeholders}"
)
raise AssertionError(
f"All placeholders should be replaced in preview. "
f"Found unreplaced: {remaining_placeholders}"
)
# Property 4: The preview should contain expected data
# For each mapping, verify that the replacement value appears in the preview
for mapping in mappings['placeholders']:
value_type = mapping['value_type']
if value_type == 'custom_text':
# Should contain the custom text
expected_value = mapping.get('custom_text', '')
if expected_value and expected_value not in preview_text:
# Only fail if the custom text is non-empty
failures.append(
f"Preview missing custom text '{expected_value}' for {mapping['key']}"
)
raise AssertionError(
f"Preview should contain custom text '{expected_value}' "
f"for placeholder {mapping['key']}"
)
else:
# Should contain data from sample_data
value_field = mapping.get('value_field', '')
if value_field and value_field in sample_data:
expected_value = str(sample_data[value_field])
if expected_value and expected_value not in preview_text:
# Only fail if the expected value is non-empty
failures.append(
f"Preview missing data '{expected_value}' for {mapping['key']}"
)
raise AssertionError(
f"Preview should contain data '{expected_value}' "
f"for placeholder {mapping['key']}"
)
try:
check_preview_generation()
print(f"✓ Property 9 verified across {test_count} test cases")
print(" All preview generations succeeded with placeholders replaced")
print(f" Tested with various template structures and mapping configurations")
return True
except Exception as e:
print(f"✗ Property 9 FAILED after {test_count} test cases")
print(f" Error: {e}")
if failures:
print(f"\n Failure details:")
for failure in failures[:5]: # Show first 5 failures
print(f" - {failure}")
import traceback
traceback.print_exc()
return False
def main():
"""Run the property test."""
print("=" * 60)
print("Property-Based Test: Preview Generation")
print("=" * 60)
print("\nThis test verifies that preview generation works correctly")
print("for any configured template with placeholder mappings.")
print("\nTesting scenarios:")
print(" - Various template structures (paragraphs, tables, headers/footers)")
print(" - Different numbers of placeholders (1-8)")
print(" - Different value types (survey_field, user_field, custom_text)")
print(" - Placeholder replacement completeness")
print(" - Valid DOCX output")
success = test_property_9_preview_generation()
print("\n" + "=" * 60)
if success:
print("✓ Property test PASSED")
print("=" * 60)
print("\nConclusion:")
print(" The preview generation mechanism correctly creates sample")
print(" certificates with all placeholders replaced for any valid")
print(" template configuration.")
return 0
else:
print("✗ Property test FAILED")
print("=" * 60)
print("\nThe preview generation mechanism has issues that need to be addressed.")
return 1
if __name__ == '__main__':
sys.exit(main())