388 lines
13 KiB
Python
Executable File
388 lines
13 KiB
Python
Executable File
#!/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())
|