276 lines
9.4 KiB
Python
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())
|