#!/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())