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