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

276 lines
9.4 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Standalone property-based test for PDF conversion.
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 15: PDF conversion
Validates: Requirements 5.4
"""
import sys
import os
import tempfile
import logging
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, assume
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_placeholders,
docx_with_mappings_and_data,
)
_logger = logging.getLogger(__name__)
def test_property_15_pdf_conversion():
"""
Feature: survey-custom-certificate-template, Property 15: PDF conversion
For any generated certificate DOCX file, the system should convert it to PDF format.
Validates: Requirements 5.4
This property test verifies that:
1. Any valid DOCX file can be converted to PDF
2. The conversion produces non-empty PDF content
3. The PDF content has the correct PDF file signature
4. The conversion completes without errors
5. Temporary files are cleaned up properly
"""
print("\nTesting Property 15: PDF conversion")
print("=" * 60)
generator = CertificateGenerator()
# Check if LibreOffice is available
is_available, error_msg = CertificateGenerator.check_libreoffice_availability()
if not is_available:
print(f"⚠ SKIPPED: LibreOffice not available: {error_msg}")
print(" PDF conversion tests require LibreOffice to be installed")
return True # Skip but don't fail
test_count = 0
@given(docx_binary=docx_with_placeholders(min_placeholders=0, max_placeholders=5))
@settings(
max_examples=100,
deadline=None,
suppress_health_check=[HealthCheck.function_scoped_fixture, HealthCheck.too_slow]
)
def check_pdf_conversion(docx_binary):
nonlocal test_count
test_count += 1
docx_data, placeholders = docx_binary
# Assume we have valid DOCX data
assume(len(docx_data) > 0)
# Create a temporary DOCX file for conversion
temp_docx = tempfile.NamedTemporaryFile(
suffix='.docx',
delete=False
)
temp_docx_path = temp_docx.name
try:
# Write the DOCX data to the temporary file
temp_docx.write(docx_data)
temp_docx.close()
# Property 1: The conversion should complete without raising exceptions
try:
pdf_content = generator.convert_to_pdf(temp_docx_path)
except Exception as e:
raise AssertionError(
f"PDF conversion raised an exception: {type(e).__name__}: {str(e)}"
)
# Property 2: The PDF content should not be None
if pdf_content is None:
raise AssertionError("PDF conversion returned None instead of PDF content")
# Property 3: The PDF content should not be empty
if len(pdf_content) == 0:
raise AssertionError("PDF conversion produced empty content")
# Property 4: The PDF content should have the correct PDF file signature
# PDF files start with "%PDF-" (bytes: 0x25 0x50 0x44 0x46 0x2D)
if not pdf_content.startswith(b'%PDF-'):
raise AssertionError(
f"PDF content does not have valid PDF signature. "
f"First 10 bytes: {pdf_content[:10]}"
)
# Property 5: The PDF should have a reasonable minimum size
# A minimal PDF is typically at least 100 bytes
if len(pdf_content) <= 100:
raise AssertionError(
f"PDF content is suspiciously small ({len(pdf_content)} bytes). "
f"This may indicate an incomplete conversion."
)
# Property 6: The PDF should contain the PDF EOF marker
# PDF files typically end with "%%EOF"
if b'%%EOF' not in pdf_content:
raise AssertionError("PDF content does not contain the expected EOF marker")
finally:
# Clean up the temporary DOCX file
if os.path.exists(temp_docx_path):
try:
os.unlink(temp_docx_path)
except Exception as e:
_logger.warning(f"Failed to delete temporary file {temp_docx_path}: {e}")
try:
check_pdf_conversion()
print(f"✓ Property 15 verified across {test_count} test cases")
print(" All DOCX files were successfully converted to valid PDF format")
return True
except Exception as e:
print(f"✗ Property 15 FAILED after {test_count} test cases")
print(f" Error: {e}")
import traceback
traceback.print_exc()
return False
def test_property_15_pdf_conversion_with_replacements():
"""
Feature: survey-custom-certificate-template, Property 15: PDF conversion (with replacements)
For any generated certificate DOCX file (with placeholder replacements),
the system should convert it to PDF format.
Validates: Requirements 5.4
This test verifies that PDF conversion works correctly even after
placeholder replacements have been made to the document.
"""
print("\nTesting Property 15: PDF conversion (with placeholder replacements)")
print("=" * 60)
generator = CertificateGenerator()
# Check if LibreOffice is available
is_available, error_msg = CertificateGenerator.check_libreoffice_availability()
if not is_available:
print(f"⚠ SKIPPED: LibreOffice not available: {error_msg}")
return True # Skip but don't fail
test_count = 0
@given(test_data=docx_with_mappings_and_data())
@settings(
max_examples=50,
deadline=None,
suppress_health_check=[HealthCheck.function_scoped_fixture, HealthCheck.too_slow]
)
def check_pdf_conversion_with_replacements(test_data):
nonlocal test_count
test_count += 1
docx_binary, mappings, data = test_data
# Assume we have valid data
assume(len(docx_binary) > 0)
# Load the document and perform placeholder replacements
doc = Document(BytesIO(docx_binary))
modified_doc = generator.replace_placeholders(doc, mappings, data)
# Save the modified document to a temporary file
temp_docx = tempfile.NamedTemporaryFile(
suffix='.docx',
delete=False
)
temp_docx_path = temp_docx.name
temp_docx.close()
try:
# Save the modified document
modified_doc.save(temp_docx_path)
# Convert to PDF
pdf_content = generator.convert_to_pdf(temp_docx_path)
# Verify the PDF is valid
if pdf_content is None:
raise AssertionError("PDF conversion returned None")
if len(pdf_content) == 0:
raise AssertionError("PDF conversion produced empty content")
if not pdf_content.startswith(b'%PDF-'):
raise AssertionError("PDF does not have valid signature")
if b'%%EOF' not in pdf_content:
raise AssertionError("PDF does not contain EOF marker")
finally:
# Clean up
if os.path.exists(temp_docx_path):
try:
os.unlink(temp_docx_path)
except Exception as e:
_logger.warning(f"Failed to delete temporary file {temp_docx_path}: {e}")
try:
check_pdf_conversion_with_replacements()
print(f"✓ Property 15 (with replacements) verified across {test_count} test cases")
print(" All modified DOCX files were successfully converted to valid PDF format")
return True
except Exception as e:
print(f"✗ Property 15 (with replacements) FAILED after {test_count} test cases")
print(f" Error: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Run the property tests."""
print("=" * 60)
print("Property-Based Test: PDF Conversion")
print("=" * 60)
# Run both test variants
success1 = test_property_15_pdf_conversion()
success2 = test_property_15_pdf_conversion_with_replacements()
print("\n" + "=" * 60)
if success1 and success2:
print("✓ All property tests PASSED")
print("=" * 60)
return 0
else:
print("✗ Some property tests FAILED")
print("=" * 60)
return 1
if __name__ == '__main__':
sys.exit(main())