217 lines
7.4 KiB
Python
Executable File
217 lines
7.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Standalone property-based test for placeholder replacement with actual data.
|
|
|
|
This test can run without the full Odoo environment by importing the generator
|
|
service directly from the local module structure.
|
|
|
|
Feature: survey-custom-certificate-template, Property 13: Placeholder replacement with actual data
|
|
Validates: Requirements 5.2
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import re
|
|
from io import BytesIO
|
|
|
|
# Add parent directory to path to import services
|
|
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 the generator service directly
|
|
from services.certificate_generator import CertificateGenerator
|
|
|
|
# Import our custom strategies
|
|
from hypothesis_strategies import docx_with_mappings_and_data
|
|
|
|
|
|
def extract_all_text(document):
|
|
"""
|
|
Extract all text from a document including paragraphs, tables, headers, and footers.
|
|
|
|
Args:
|
|
document: python-docx Document object
|
|
|
|
Returns:
|
|
str: All text content concatenated
|
|
"""
|
|
text_parts = []
|
|
|
|
# Extract from paragraphs
|
|
for paragraph in document.paragraphs:
|
|
text_parts.append(paragraph.text)
|
|
|
|
# Extract from tables
|
|
for table in document.tables:
|
|
for row in table.rows:
|
|
for cell in row.cells:
|
|
for paragraph in cell.paragraphs:
|
|
text_parts.append(paragraph.text)
|
|
|
|
# Extract from headers and footers
|
|
for section in document.sections:
|
|
# Header
|
|
for paragraph in section.header.paragraphs:
|
|
text_parts.append(paragraph.text)
|
|
|
|
# Footer
|
|
for paragraph in section.footer.paragraphs:
|
|
text_parts.append(paragraph.text)
|
|
|
|
return ' '.join(text_parts)
|
|
|
|
|
|
def test_property_13_placeholder_replacement_with_actual_data():
|
|
"""
|
|
Feature: survey-custom-certificate-template, Property 13: Placeholder replacement with actual data
|
|
|
|
For any certificate being generated, all placeholders should be replaced
|
|
with actual participant and survey data according to the configured mappings.
|
|
|
|
Validates: Requirements 5.2
|
|
|
|
This property test verifies that:
|
|
1. All placeholders in the template are replaced with actual data
|
|
2. No placeholders remain in the generated document
|
|
3. The replacement values match the configured mappings
|
|
4. Custom text is used when specified
|
|
5. Dynamic data from the data dictionary is used when specified
|
|
"""
|
|
print("\nTesting Property 13: Placeholder replacement with actual data")
|
|
print("=" * 60)
|
|
|
|
generator = CertificateGenerator()
|
|
test_count = 0
|
|
placeholder_pattern = r'\{key\.[a-zA-Z0-9_]+\}'
|
|
|
|
@given(test_data=docx_with_mappings_and_data())
|
|
@settings(
|
|
max_examples=100,
|
|
deadline=None,
|
|
suppress_health_check=[HealthCheck.function_scoped_fixture]
|
|
)
|
|
def check_replacement(test_data):
|
|
nonlocal test_count
|
|
test_count += 1
|
|
|
|
docx_binary, mappings, data = test_data
|
|
|
|
# Load the original template to get the placeholders
|
|
original_doc = Document(BytesIO(docx_binary))
|
|
original_text = extract_all_text(original_doc)
|
|
|
|
# Extract placeholders from original document
|
|
original_placeholders = set(re.findall(placeholder_pattern, original_text))
|
|
|
|
# Skip if no placeholders (edge case)
|
|
if not original_placeholders:
|
|
return
|
|
|
|
# Replace placeholders using the generator
|
|
result_doc = generator.replace_placeholders(
|
|
Document(BytesIO(docx_binary)),
|
|
mappings,
|
|
data
|
|
)
|
|
|
|
# Extract text from result document
|
|
result_text = extract_all_text(result_doc)
|
|
|
|
# Property 1: No placeholders should remain in the result
|
|
remaining_placeholders = set(re.findall(placeholder_pattern, result_text))
|
|
if remaining_placeholders:
|
|
raise AssertionError(
|
|
f"Placeholders still present in result: {remaining_placeholders}\n"
|
|
f"Original placeholders: {original_placeholders}\n"
|
|
f"Result text excerpt: {result_text[:500]}"
|
|
)
|
|
|
|
# Property 2: All mapped placeholders should have their values in the result
|
|
for mapping in mappings.get('placeholders', []):
|
|
placeholder_key = mapping.get('key', '')
|
|
|
|
# Skip if this placeholder wasn't in the original document
|
|
if placeholder_key not in original_placeholders:
|
|
continue
|
|
|
|
# Determine expected replacement value
|
|
if mapping.get('value_type') == 'custom_text':
|
|
expected_value = mapping.get('custom_text', '')
|
|
else:
|
|
value_field = mapping.get('value_field', '')
|
|
expected_value = data.get(value_field, '')
|
|
|
|
# Convert to string for comparison
|
|
expected_value = str(expected_value) if expected_value else ''
|
|
|
|
# If expected value is not empty, it should appear in the result
|
|
# (Empty values are valid - they replace placeholders with empty strings)
|
|
if expected_value and expected_value not in result_text:
|
|
raise AssertionError(
|
|
f"Expected value '{expected_value}' for placeholder '{placeholder_key}' "
|
|
f"not found in result document.\n"
|
|
f"Mapping: {mapping}\n"
|
|
f"Data: {data}\n"
|
|
f"Result text excerpt: {result_text[:500]}"
|
|
)
|
|
|
|
# Property 3: The original placeholder should not appear in the result
|
|
for placeholder in original_placeholders:
|
|
if placeholder in result_text:
|
|
raise AssertionError(
|
|
f"Original placeholder '{placeholder}' still present in result.\n"
|
|
f"Result text excerpt: {result_text[:500]}"
|
|
)
|
|
|
|
try:
|
|
check_replacement()
|
|
print(f"✓ Property 13 verified across {test_count} test cases")
|
|
print(" All placeholders were correctly replaced with actual data")
|
|
print(" No placeholders remained in generated documents")
|
|
print(" All mapped values appeared in results as expected")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ Property 13 FAILED after {test_count} test cases")
|
|
print(f" Error: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Run the property test."""
|
|
print("=" * 60)
|
|
print("Property-Based Test: Placeholder Replacement with Actual Data")
|
|
print("=" * 60)
|
|
|
|
success = test_property_13_placeholder_replacement_with_actual_data()
|
|
|
|
print("\n" + "=" * 60)
|
|
if success:
|
|
print("✓ Property test PASSED")
|
|
print("=" * 60)
|
|
return 0
|
|
else:
|
|
print("✗ Property test FAILED")
|
|
print("=" * 60)
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|