263 lines
10 KiB
Python
Executable File
263 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Standalone property-based test for mapping persistence.
|
|
|
|
This test simulates the mapping persistence behavior without requiring
|
|
a full Odoo environment by testing the JSON serialization/deserialization
|
|
logic directly.
|
|
|
|
Feature: survey-custom-certificate-template, Property 8: Mapping persistence
|
|
Validates: Requirements 3.4
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import json
|
|
|
|
# Add parent directory to path
|
|
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)
|
|
|
|
# Import our custom strategies
|
|
from hypothesis_strategies import valid_mappings
|
|
|
|
|
|
def serialize_mappings(placeholder_records):
|
|
"""
|
|
Simulate the wizard's action_save_template serialization logic.
|
|
|
|
This mimics what happens in the wizard when saving mappings to JSON.
|
|
"""
|
|
mappings = {
|
|
'placeholders': []
|
|
}
|
|
|
|
for placeholder in placeholder_records:
|
|
mapping = {
|
|
'key': placeholder['source_key'],
|
|
'value_type': placeholder['value_type'],
|
|
'value_field': placeholder.get('value_field', ''),
|
|
'custom_text': placeholder.get('custom_text', ''),
|
|
}
|
|
mappings['placeholders'].append(mapping)
|
|
|
|
# Convert to JSON string (as stored in database)
|
|
return json.dumps(mappings, indent=2)
|
|
|
|
|
|
def deserialize_mappings(mappings_json):
|
|
"""
|
|
Parse mappings from JSON string (as retrieved from database).
|
|
"""
|
|
return json.loads(mappings_json)
|
|
|
|
|
|
def test_property_8_mapping_persistence():
|
|
"""
|
|
Feature: survey-custom-certificate-template, Property 8: Mapping persistence
|
|
|
|
For any set of placeholder mappings configured in the wizard,
|
|
all mappings should be persisted to the database when the user saves the configuration.
|
|
|
|
Validates: Requirements 3.4
|
|
|
|
This property test verifies that:
|
|
1. All placeholder mappings can be serialized to JSON
|
|
2. The serialized mappings can be deserialized back
|
|
3. All mapping fields (key, value_type, value_field, custom_text) are preserved
|
|
4. The round-trip (serialize -> deserialize) preserves all data
|
|
"""
|
|
print("\nTesting Property 8: Mapping persistence")
|
|
print("=" * 60)
|
|
|
|
test_count = 0
|
|
failures = []
|
|
|
|
@given(mappings=valid_mappings(min_placeholders=1, max_placeholders=20))
|
|
@settings(
|
|
max_examples=100,
|
|
deadline=None,
|
|
suppress_health_check=[HealthCheck.function_scoped_fixture]
|
|
)
|
|
def check_persistence(mappings):
|
|
nonlocal test_count, failures
|
|
test_count += 1
|
|
|
|
# Convert mappings to placeholder records (as they would be in the wizard)
|
|
placeholder_records = []
|
|
for sequence, mapping in enumerate(mappings['placeholders'], start=1):
|
|
placeholder_records.append({
|
|
'source_key': mapping['key'],
|
|
'value_type': mapping['value_type'],
|
|
'value_field': mapping.get('value_field', ''),
|
|
'custom_text': mapping.get('custom_text', ''),
|
|
'sequence': sequence,
|
|
})
|
|
|
|
# Serialize to JSON (simulating save to database)
|
|
try:
|
|
mappings_json = serialize_mappings(placeholder_records)
|
|
except Exception as e:
|
|
failures.append(f"Serialization failed: {e}")
|
|
raise AssertionError(f"Failed to serialize mappings: {e}")
|
|
|
|
# Deserialize from JSON (simulating load from database)
|
|
try:
|
|
saved_mappings = deserialize_mappings(mappings_json)
|
|
except Exception as e:
|
|
failures.append(f"Deserialization failed: {e}")
|
|
raise AssertionError(f"Failed to deserialize mappings: {e}")
|
|
|
|
# Property 1: The saved mappings should have the same structure
|
|
if 'placeholders' not in saved_mappings:
|
|
failures.append("Missing 'placeholders' key in saved mappings")
|
|
raise AssertionError("Saved mappings must contain 'placeholders' key")
|
|
|
|
if not isinstance(saved_mappings['placeholders'], list):
|
|
failures.append("'placeholders' is not a list")
|
|
raise AssertionError("Saved mappings 'placeholders' must be a list")
|
|
|
|
# Property 2: The number of saved mappings should match the input
|
|
if len(saved_mappings['placeholders']) != len(mappings['placeholders']):
|
|
failures.append(
|
|
f"Count mismatch: expected {len(mappings['placeholders'])}, "
|
|
f"got {len(saved_mappings['placeholders'])}"
|
|
)
|
|
raise AssertionError(
|
|
f"Number of saved mappings ({len(saved_mappings['placeholders'])}) "
|
|
f"should match input mappings ({len(mappings['placeholders'])})"
|
|
)
|
|
|
|
# Property 3: Each mapping should be preserved exactly
|
|
for idx, (original, saved) in enumerate(zip(mappings['placeholders'], saved_mappings['placeholders'])):
|
|
# Verify key is preserved
|
|
if saved['key'] != original['key']:
|
|
failures.append(
|
|
f"Key mismatch at index {idx}: expected {original['key']}, got {saved['key']}"
|
|
)
|
|
raise AssertionError(
|
|
f"Placeholder key should be preserved: expected {original['key']}, got {saved['key']}"
|
|
)
|
|
|
|
# Verify value_type is preserved
|
|
if saved['value_type'] != original['value_type']:
|
|
failures.append(
|
|
f"Value type mismatch at index {idx} for {original['key']}: "
|
|
f"expected {original['value_type']}, got {saved['value_type']}"
|
|
)
|
|
raise AssertionError(
|
|
f"Value type should be preserved for {original['key']}: "
|
|
f"expected {original['value_type']}, got {saved['value_type']}"
|
|
)
|
|
|
|
# Verify value_field is preserved (may be empty string)
|
|
expected_value_field = original.get('value_field', '')
|
|
saved_value_field = saved.get('value_field', '')
|
|
if saved_value_field != expected_value_field:
|
|
failures.append(
|
|
f"Value field mismatch at index {idx} for {original['key']}: "
|
|
f"expected '{expected_value_field}', got '{saved_value_field}'"
|
|
)
|
|
raise AssertionError(
|
|
f"Value field should be preserved for {original['key']}: "
|
|
f"expected '{expected_value_field}', got '{saved_value_field}'"
|
|
)
|
|
|
|
# Verify custom_text is preserved (may be empty string)
|
|
expected_custom_text = original.get('custom_text', '')
|
|
saved_custom_text = saved.get('custom_text', '')
|
|
if saved_custom_text != expected_custom_text:
|
|
failures.append(
|
|
f"Custom text mismatch at index {idx} for {original['key']}: "
|
|
f"expected '{expected_custom_text}', got '{saved_custom_text}'"
|
|
)
|
|
raise AssertionError(
|
|
f"Custom text should be preserved for {original['key']}: "
|
|
f"expected '{expected_custom_text}', got '{saved_custom_text}'"
|
|
)
|
|
|
|
# Property 4: Round-trip consistency
|
|
# Serialize again and verify we get the same JSON structure
|
|
try:
|
|
# Convert saved mappings back to placeholder records
|
|
round_trip_records = []
|
|
for mapping in saved_mappings['placeholders']:
|
|
round_trip_records.append({
|
|
'source_key': mapping['key'],
|
|
'value_type': mapping['value_type'],
|
|
'value_field': mapping.get('value_field', ''),
|
|
'custom_text': mapping.get('custom_text', ''),
|
|
})
|
|
|
|
# Serialize again
|
|
round_trip_json = serialize_mappings(round_trip_records)
|
|
round_trip_mappings = deserialize_mappings(round_trip_json)
|
|
|
|
# Verify the structure is still correct
|
|
if len(round_trip_mappings['placeholders']) != len(saved_mappings['placeholders']):
|
|
failures.append("Round-trip count mismatch")
|
|
raise AssertionError("Round-trip should preserve mapping count")
|
|
|
|
except Exception as e:
|
|
failures.append(f"Round-trip failed: {e}")
|
|
raise AssertionError(f"Round-trip consistency check failed: {e}")
|
|
|
|
try:
|
|
check_persistence()
|
|
print(f"✓ Property 8 verified across {test_count} test cases")
|
|
print(" All placeholder mappings were correctly persisted and retrieved")
|
|
print(f" Tested with 1-20 placeholders per configuration")
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ Property 8 FAILED after {test_count} test cases")
|
|
print(f" Error: {e}")
|
|
if failures:
|
|
print(f"\n Failure details:")
|
|
for failure in failures[:5]: # Show first 5 failures
|
|
print(f" - {failure}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""Run the property test."""
|
|
print("=" * 60)
|
|
print("Property-Based Test: Mapping Persistence")
|
|
print("=" * 60)
|
|
print("\nThis test verifies that placeholder mappings are correctly")
|
|
print("persisted through JSON serialization/deserialization.")
|
|
print("\nTesting scenarios:")
|
|
print(" - Various numbers of placeholders (1-20)")
|
|
print(" - Different value types (survey_field, user_field, custom_text)")
|
|
print(" - Empty and non-empty custom text values")
|
|
print(" - Round-trip consistency (save -> load -> save)")
|
|
|
|
success = test_property_8_mapping_persistence()
|
|
|
|
print("\n" + "=" * 60)
|
|
if success:
|
|
print("✓ Property test PASSED")
|
|
print("=" * 60)
|
|
print("\nConclusion:")
|
|
print(" The mapping persistence mechanism correctly preserves all")
|
|
print(" placeholder configurations across serialization boundaries.")
|
|
return 0
|
|
else:
|
|
print("✗ Property test FAILED")
|
|
print("=" * 60)
|
|
print("\nThe mapping persistence mechanism has issues that need to be addressed.")
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|