#!/usr/bin/env python3 """ Beteiligte Sync Test Script Testet die vollständige Sync-Funktionalität: 1. Mapper-Transformationen 2. Lock-Mechanismus 3. Timestamp-Vergleich 4. CREATE in Advoware (optional) 5. UPDATE Sync (optional) 6. Konflikt-Resolution Usage: python test_beteiligte_sync.py --test-transforms # Nur Mapper testen python test_beteiligte_sync.py --test-live # Live-Test mit echten APIs python test_beteiligte_sync.py --entity-id=XXX # Spezifische Entity testen """ import asyncio import sys import argparse from datetime import datetime import json sys.path.insert(0, '/opt/motia-app/bitbylaw') from services.espocrm import EspoCRMAPI from services.advoware import AdvowareAPI from services.espocrm_mapper import BeteiligteMapper from services.beteiligte_sync_utils import BeteiligteSync class MockContext: """Mock Context für Testing ohne Motia Workbench""" class Logger: def info(self, msg): print(f"ℹ️ {msg}") def debug(self, msg): print(f"🔍 {msg}") def warning(self, msg): print(f"⚠️ {msg}") def error(self, msg): print(f"❌ {msg}") def __init__(self): self.logger = self.Logger() async def test_transforms(): """Test 1: Mapper-Transformationen""" print("\n" + "="*80) print("TEST 1: Mapper-Transformationen") print("="*80) mapper = BeteiligteMapper() # Test 1a: Person EspoCRM → Advoware print("\n📤 Test 1a: Person EspoCRM → Advoware") espo_person = { 'id': 'test123', 'firstName': 'Angela', 'lastName': 'Mustermann', 'rechtsform': 'Frau', 'emailAddress': 'angela@example.com', 'emailAddressData': [ {'emailAddress': 'angela@example.com', 'primary': True} ], 'phoneNumber': '+49123456789', 'dateOfBirth': '1980-05-15' } advo_result = mapper.map_cbeteiligte_to_advoware(espo_person) print(f"✅ Mapped:") print(json.dumps(advo_result, indent=2, ensure_ascii=False)) # Test 1b: Firma EspoCRM → Advoware print("\n📤 Test 1b: Firma EspoCRM → Advoware") espo_firma = { 'id': 'test456', 'firmenname': 'Mustermann GmbH', 'rechtsform': 'GmbH', 'emailAddress': 'info@mustermann.de', 'handelsregisterNummer': 'HRB 12345' } advo_firma = mapper.map_cbeteiligte_to_advoware(espo_firma) print(f"✅ Mapped:") print(json.dumps(advo_firma, indent=2, ensure_ascii=False)) # Test 1c: Advoware → EspoCRM (Person) print("\n📥 Test 1c: Advoware → EspoCRM (Person)") advo_person = { 'betNr': 104860, 'vorname': 'Max', 'name': 'Mustermann', 'rechtsform': 'Herr', 'emailGesch': 'max@example.com', 'telGesch': '+49987654321', 'geburtsdatum': '1975-03-20' } espo_result = mapper.map_advoware_to_cbeteiligte(advo_person) print(f"✅ Mapped:") print(json.dumps(espo_result, indent=2, ensure_ascii=False)) print("\n✅ MAPPER TESTS PASSED!") async def test_lock_mechanism(): """Test 2: Lock-Mechanismus""" print("\n" + "="*80) print("TEST 2: Lock-Mechanismus") print("="*80) espocrm = EspoCRMAPI() context = MockContext() sync_utils = BeteiligteSync(espocrm, context) # Hole erste Entity result = await espocrm.list_entities('CBeteiligte', max_size=1) if not result.get('list'): print("❌ Keine Entities gefunden zum Testen") return entity = result['list'][0] entity_id = entity['id'] original_status = entity.get('syncStatus', 'clean') print(f"\n🔒 Test Lock für Entity: {entity.get('name')} (ID: {entity_id})") print(f" Original Status: {original_status}") # Test Lock Acquire print("\n1. Acquire Lock...") lock1 = await sync_utils.acquire_sync_lock(entity_id) print(f" Lock 1: {lock1} (erwartet: True)") # Verify Status entity_check = await espocrm.get_entity('CBeteiligte', entity_id) print(f" Status nach Lock: {entity_check.get('syncStatus')} (erwartet: syncing)") # Test Lock Already Held print("\n2. Versuche Lock erneut zu holen (sollte fehlschlagen)...") lock2 = await sync_utils.acquire_sync_lock(entity_id) print(f" Lock 2: {lock2} (erwartet: False)") # Release Lock print("\n3. Release Lock...") await sync_utils.release_sync_lock(entity_id, 'clean') entity_final = await espocrm.get_entity('CBeteiligte', entity_id) print(f" Status nach Release: {entity_final.get('syncStatus')} (erwartet: clean)") # Restore Original if original_status != 'clean': await espocrm.update_entity('CBeteiligte', entity_id, {'syncStatus': original_status}) print(f" Status restored to: {original_status}") print("\n✅ LOCK TESTS PASSED!") async def test_timestamp_comparison(): """Test 3: Timestamp-Vergleich""" print("\n" + "="*80) print("TEST 3: Timestamp-Vergleich") print("="*80) espocrm = EspoCRMAPI() context = MockContext() sync_utils = BeteiligteSync(espocrm, context) # Test-Timestamps now = datetime.now() old = datetime(2026, 2, 1, 10, 0, 0) newer = datetime(2026, 2, 7, 14, 0, 0) print("\n📅 Test-Timestamps:") print(f" old: {old}") print(f" newer: {newer}") print(f" now: {now}") # Scenario 1: EspoCRM neuer print("\n1. EspoCRM neuer als Advoware:") result = sync_utils.compare_timestamps(newer, old, old) print(f" Result: {result} (erwartet: espocrm_newer)") # Scenario 2: Advoware neuer print("\n2. Advoware neuer als EspoCRM:") result = sync_utils.compare_timestamps(old, newer, old) print(f" Result: {result} (erwartet: advoware_newer)") # Scenario 3: Konflikt (beide geändert) print("\n3. Beide nach last_sync geändert (Konflikt):") result = sync_utils.compare_timestamps(newer, newer, old) print(f" Result: {result} (erwartet: conflict)") # Scenario 4: Keine Änderungen print("\n4. Keine Änderungen seit last_sync:") result = sync_utils.compare_timestamps(old, old, newer) print(f" Result: {result} (erwartet: no_change)") print("\n✅ TIMESTAMP TESTS PASSED!") async def test_live_entity(entity_id=None, dry_run=True): """Test 4: Live Entity Sync (optional mit echtem API-Call)""" print("\n" + "="*80) print("TEST 4: Live Entity Sync") print("="*80) espocrm = EspoCRMAPI() advoware = AdvowareAPI(MockContext()) mapper = BeteiligteMapper() context = MockContext() sync_utils = BeteiligteSync(espocrm, context) # Hole Entity if not entity_id: result = await espocrm.list_entities('CBeteiligte', max_size=5) entities = result.get('list', []) # Suche eine mit betnr entity = next((e for e in entities if e.get('betnr')), entities[0] if entities else None) if not entity: print("❌ Keine Entity gefunden") return entity_id = entity['id'] else: entity = await espocrm.get_entity('CBeteiligte', entity_id) print(f"\n📋 Test Entity:") print(f" ID: {entity_id}") print(f" Name: {entity.get('name')}") print(f" betNr: {entity.get('betnr')}") print(f" syncStatus: {entity.get('syncStatus')}") print(f" modifiedAt: {entity.get('modifiedAt')}") print(f" advowareLastSync: {entity.get('advowareLastSync')}") betnr = entity.get('betnr') # Test Transformation print("\n🔄 Transformation EspoCRM → Advoware:") advo_data = mapper.map_cbeteiligte_to_advoware(entity) print(json.dumps(advo_data, indent=2, ensure_ascii=False)) # Wenn betnr vorhanden, teste Fetch von Advoware if betnr: print(f"\n📥 Fetch von Advoware (betNr={betnr}):") try: advo_result = await advoware.api_call( f'api/v1/advonet/Beteiligte/{betnr}', method='GET' ) if isinstance(advo_result, list): advo_entity = advo_result[0] if advo_result else None else: advo_entity = advo_result if advo_entity: print(f"✅ Von Advoware geladen:") print(f" name: {advo_entity.get('name')}") print(f" vorname: {advo_entity.get('vorname')}") print(f" geaendertAm: {advo_entity.get('geaendertAm')}") # Timestamp-Vergleich print(f"\n⏱️ Timestamp-Vergleich:") comparison = sync_utils.compare_timestamps( entity.get('modifiedAt'), advo_entity.get('geaendertAm'), entity.get('advowareLastSync') ) print(f" Result: {comparison}") # Zeige was geändert wäre changed = mapper.get_changed_fields(entity, advo_entity) if changed: print(f"\n📝 Geänderte Felder: {', '.join(changed)}") else: print(f"\n✅ Keine Feld-Unterschiede") else: print("❌ Keine Daten von Advoware") except Exception as e: print(f"⚠️ Fehler beim Fetch von Advoware: {e}") else: print("\n⚠️ Keine betnr vorhanden (Entity wurde noch nicht gesynct)") if not dry_run: print("\n🆕 Würde CREATE in Advoware ausführen:") print(f" POST /api/v1/advonet/Beteiligte") print(f" Data: {json.dumps(advo_data, indent=2, ensure_ascii=False)}") print("\n⚠️ DRY RUN - Nicht ausgeführt!") print("\n✅ LIVE ENTITY TEST COMPLETE!") async def test_full_sync_handler(entity_id): """Test 5: Vollständiger Sync-Handler (simuliert Event)""" print("\n" + "="*80) print("TEST 5: Vollständiger Sync-Handler") print("="*80) # Import Handler sys.path.insert(0, '/opt/motia-app/bitbylaw/steps/vmh') import beteiligte_sync_event_step # Mock Event Data event_data = { 'entity_id': entity_id, 'action': 'sync_check', 'source': 'test_script', 'timestamp': datetime.now().isoformat() } context = MockContext() print(f"\n🎬 Simuliere Event für Entity: {entity_id}") print(f" Event: {json.dumps(event_data, indent=2)}") print("\n⚠️ ACHTUNG: Dies führt ECHTE API-Calls aus!") response = input("Fortfahren? (y/N): ") if response.lower() != 'y': print("❌ Abgebrochen") return print("\n🚀 Handler wird ausgeführt...\n") try: await beteiligte_sync_event_step.handler(event_data, context) print("\n✅ HANDLER ERFOLGREICH!") except Exception as e: print(f"\n❌ HANDLER FEHLER: {e}") import traceback traceback.print_exc() async def main(): parser = argparse.ArgumentParser(description='Test Beteiligte Sync') parser.add_argument('--test-transforms', action='store_true', help='Nur Mapper testen') parser.add_argument('--test-lock', action='store_true', help='Lock-Mechanismus testen') parser.add_argument('--test-timestamps', action='store_true', help='Timestamp-Vergleich testen') parser.add_argument('--test-live', action='store_true', help='Live-Test mit APIs') parser.add_argument('--test-full-handler', action='store_true', help='Vollständiger Handler (ECHTE CALLS!)') parser.add_argument('--entity-id', type=str, help='Spezifische Entity-ID testen') parser.add_argument('--all', action='store_true', help='Alle Tests ausführen') args = parser.parse_args() # Default: Alle Tests if not any([args.test_transforms, args.test_lock, args.test_timestamps, args.test_live, args.test_full_handler, args.all]): args.all = True print("🧪 Beteiligte Sync Test Suite") print("="*80) try: if args.all or args.test_transforms: await test_transforms() if args.all or args.test_lock: await test_lock_mechanism() if args.all or args.test_timestamps: await test_timestamp_comparison() if args.all or args.test_live: await test_live_entity(args.entity_id, dry_run=True) if args.test_full_handler: if not args.entity_id: print("\n❌ --entity-id erforderlich für --test-full-handler") else: await test_full_sync_handler(args.entity_id) print("\n" + "="*80) print("✅ ALLE TESTS ABGESCHLOSSEN!") print("="*80) except Exception as e: print(f"\n❌ FEHLER: {e}") import traceback traceback.print_exc() sys.exit(1) if __name__ == '__main__': asyncio.run(main())