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

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())