introducing junction table extension

This commit is contained in:
2026-03-09 22:42:40 +01:00
parent 47634c81ef
commit 3361cffb14
28 changed files with 1927 additions and 5 deletions

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python3
"""
Test syncId über API mit Standard-Workflow
Versuche ID direkt beim Verknüpfen zu setzen und dann via Formula zu updaten
"""
import requests
import json
import subprocess
from datetime import datetime
BASE_URL = "https://crm.bitbylaw.com"
API_KEY = "e53def10eea27b92a6cd00f40a3e09a4"
HEADERS = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
def api_request(method, endpoint, data=None):
url = f"{BASE_URL}/api/v1/{endpoint}"
if method == "GET":
response = requests.get(url, headers=HEADERS)
elif method == "POST":
response = requests.post(url, headers=HEADERS, json=data)
elif method == "DELETE":
response = requests.delete(url, headers=HEADERS)
elif method == "PUT":
response = requests.put(url, headers=HEADERS, json=data)
return response
def check_db(doc_id):
"""Prüfe syncId in Datenbank"""
result = subprocess.run([
"docker", "exec", "espocrm-db", "mariadb",
"-u", "espocrm", "-pdatabase_password", "espocrm",
"-e", f"SELECT sync_id FROM c_a_i_collection_c_dokumente WHERE c_dokumente_id='{doc_id}' AND deleted=0;"
], capture_output=True, text=True)
lines = result.stdout.strip().split('\n')
if len(lines) > 1:
return lines[1].strip()
return "NULL"
print("\n" + "="*80)
print(" "*20 + "Many-to-Many syncId Test")
print("="*80 + "\n")
doc_id = None
collection_id = None
test_sync_id = f"SYNC-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
try:
# 1. Entities erstellen
print("1⃣ Erstelle Entities...")
doc = api_request("POST", "CDokumente", {
"name": f"Final Test Doc {datetime.now().strftime('%H:%M:%S')}"
}).json()
doc_id = doc['id']
print(f" Doc: {doc_id}")
collection = api_request("POST", "CAICollections", {
"name": f"Final Test Col {datetime.now().strftime('%H:%M:%S')}"
}).json()
collection_id = collection['id']
print(f" Col: {collection_id}\n")
# 2. Verknüpfung erstellen
print("2⃣ Erstelle Verknüpfung...")
link_response = api_request("POST", f"CDokumente/{doc_id}/cAICollections", {
"id": collection_id
})
print(f" Status: {link_response.status_code}\n")
# 3. Direkt in DB schreiben
print("3⃣ Setze syncId direkt in Datenbank...")
db_result = subprocess.run([
"docker", "exec", "espocrm-db", "mariadb",
"-u", "espocrm", "-pdatabase_password", "espocrm",
"-e", f"UPDATE c_a_i_collection_c_dokumente SET sync_id='{test_sync_id}' WHERE c_dokumente_id='{doc_id}' AND c_a_i_collections_id='{collection_id}';"
], capture_output=True, text=True)
print(f" syncId in DB gesetzt: {test_sync_id}\n")
# 4. DB-Verifikation
print("4⃣ Verifiziere in Datenbank...")
sync_in_db = check_db(doc_id)
if sync_in_db == test_sync_id:
print(f" ✅ syncId in DB: {sync_in_db}\n")
else:
print(f" ❌ syncId falsch/NULL: {sync_in_db}\n")
# 5. Rufe über API ab
print("5⃣ Rufe Beziehung über API ab...")
relations_response = api_request("GET", f"CDokumente/{doc_id}/cAICollections")
if relations_response.status_code == 200:
relations = relations_response.json()
print(f" Status: 200\n")
if 'list' in relations and len(relations['list']) > 0:
first = relations['list'][0]
print(" Felder in Response:")
for key in sorted(first.keys()):
value = first[key]
if isinstance(value, str) and len(value) > 50:
value = value[:50] + "..."
print(f" - {key}: {value}")
print()
if 'syncId' in first and first['syncId'] == test_sync_id:
print(f" ✅ syncId in API-Response: {first['syncId']}")
result_status = "✅ VOLLSTÄNDIG ERFOLGREICH"
elif 'syncId' in first:
print(f" ⚠️ syncId in API vorhanden, aber falscher Wert: {first['syncId']}")
result_status = "⚠️ TEILWEISE ERFOLGREICH"
else:
print(f" ❌ syncId NICHT in API-Response")
result_status = "❌ API GIBT KEINE ADDITIONALCOLUMNS ZURÜCK"
else:
print(" ❌ Keine Beziehung gefunden")
result_status = "❌ FEHLGESCHLAGEN"
else:
print(f" ❌ API-Fehler: {relations_response.status_code}")
result_status = "❌ FEHLGESCHLAGEN"
finally:
print()
print("6⃣ Cleanup...")
if doc_id:
api_request("DELETE", f"CDokumente/{doc_id}")
print(f" ✓ Dokument gelöscht")
if collection_id:
api_request("DELETE", f"CAICollections/{collection_id}")
print(f" ✓ Collection gelöscht")
print("\n" + "="*80)
print(result_status)
print("="*80 + "\n")

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
"""
Teste verschiedene Query-Parameter um additionalColumns aus API zu bekommen
"""
import requests
import subprocess
from datetime import datetime
BASE_URL = "https://crm.bitbylaw.com"
API_KEY = "e53def10eea27b92a6cd00f40a3e09a4"
HEADERS = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
doc_id = None
collection_id = None
test_sync_id = f"SYNC-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
try:
# Setup
print("Setup...")
doc = requests.post(f"{BASE_URL}/api/v1/CDokumente", headers=HEADERS, json={"name": "Test Doc"}).json()
doc_id = doc['id']
collection = requests.post(f"{BASE_URL}/api/v1/CAICollections", headers=HEADERS, json={"name": "Test Col"}).json()
collection_id = collection['id']
# Link und setze syncId in DB
requests.post(f"{BASE_URL}/api/v1/CDokumente/{doc_id}/cAICollections", headers=HEADERS, json={"id": collection_id})
subprocess.run([
"docker", "exec", "espocrm-db", "mariadb",
"-u", "espocrm", "-pdatabase_password", "espocrm",
"-e", f"UPDATE c_a_i_collection_c_dokumente SET sync_id='{test_sync_id}' WHERE c_dokumente_id='{doc_id}';"
], capture_output=True)
print(f"Doc: {doc_id}, Col: {collection_id}, syncId: {test_sync_id}\n")
# Teste verschiedene Query-Parameter
params_to_test = [
{},
{"select": "id,name,syncId"},
{"select": "id,name,sync_id"},
{"additionalColumns": "true"},
{"columns": "true"},
{"includeColumns": "true"},
{"expand": "columns"},
{"maxSize": 10, "select": "syncId"},
{"loadAdditionalFields": "true"},
]
print("="*80)
print("Teste Query-Parameter:")
print("="*80 + "\n")
for i, params in enumerate(params_to_test, 1):
param_str = ", ".join([f"{k}={v}" for k, v in params.items()]) if params else "keine"
print(f"{i}. Parameter: {param_str}")
response = requests.get(
f"{BASE_URL}/api/v1/CDokumente/{doc_id}/cAICollections",
headers=HEADERS,
params=params
)
if response.status_code == 200:
data = response.json()
if 'list' in data and len(data['list']) > 0:
first = data['list'][0]
if 'syncId' in first or 'sync_id' in first:
print(f" ✅ additionalColumn gefunden!")
print(f" syncId: {first.get('syncId', first.get('sync_id'))}")
else:
print(f" ❌ Keine additionalColumn (Felder: {len(first)})")
else:
print(f" ⚠️ Leere Liste")
else:
print(f" ❌ Status: {response.status_code}")
print()
finally:
print("Cleanup...")
if doc_id:
requests.delete(f"{BASE_URL}/api/v1/CDokumente/{doc_id}", headers=HEADERS)
if collection_id:
requests.delete(f"{BASE_URL}/api/v1/CAICollections/{collection_id}", headers=HEADERS)
print("="*80)

View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""
Vereinfachter Test: Erstelle Dokument und CAICollection über API und teste Verknüpfung
"""
import requests
import json
import sys
from datetime import datetime
BASE_URL = "https://crm.bitbylaw.com"
API_KEY = "e53def10eea27b92a6cd00f40a3e09a4" # User: marvin
HEADERS = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
def api_request(method, endpoint, data=None):
"""API-Request"""
url = f"{BASE_URL}/api/v1/{endpoint}"
try:
if method == "GET":
response = requests.get(url, headers=HEADERS)
elif method == "POST":
response = requests.post(url, headers=HEADERS, json=data)
elif method == "DELETE":
response = requests.delete(url, headers=HEADERS)
print(f" {method} {endpoint} -> Status: {response.status_code}")
if response.status_code >= 400:
print(f" Error Response: {response.text[:200]}")
return None
return response.json() if response.text else {}
except Exception as e:
print(f" Exception: {e}")
return None
print("\n" + "="*80)
print(" "*25 + "Junction API Test - Simplified")
print("="*80 + "\n")
doc_id = None
collection_id = None
test_sync_id = f"SYNC-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
try:
# 1. CDokumente erstellen
print("1⃣ Erstelle CDokumente...")
doc = api_request("POST", "CDokumente", {
"name": f"API-Test Dokument {datetime.now().strftime('%H:%M:%S')}",
"description": "Für Junction-Test"
})
if doc and 'id' in doc:
doc_id = doc['id']
print(f" ✓ Dokument erstellt: {doc_id}\n")
else:
print(" ❌ Fehler\n")
sys.exit(1)
# 2. Versuche CAICollection zu erstellen (könnte fehlschlagen wegen Berechtigungen)
print("2⃣ Versuche CAICollection zu erstellen...")
collection = api_request("POST", "CAICollections", {
"name": f"API-Test Collection {datetime.now().strftime('%H:%M:%S')}",
"description": "Für Junction-Test"
})
if collection and 'id' in collection:
collection_id = collection['id']
print(f" ✓ Collection erstellt: {collection_id}\n")
# 3. Verknüpfen mit syncId
print("3⃣ Verknüpfe mit syncId...")
print(f" syncId: {test_sync_id}")
join_result = api_request("POST", f"CDokumente/{doc_id}/cAICollections", {
"id": collection_id,
"columns": {
"syncId": test_sync_id
}
})
print(f" Join Result: {join_result}\n")
# 4. Abrufen
print("4⃣ Rufe Beziehung ab...")
relations = api_request("GET", f"CDokumente/{doc_id}/cAICollections")
if relations:
print(f"\nAPI Response:")
print(json.dumps(relations, indent=2, ensure_ascii=False))
print()
if 'list' in relations and len(relations['list']) > 0:
first = relations['list'][0]
if 'syncId' in first:
print(f"✅ syncId gefunden in API: {first['syncId']}")
if first['syncId'] == test_sync_id:
print(f"✅ syncId-Wert stimmt überein!")
else:
print(f"⚠️ Wert stimmt nicht: {first['syncId']} != {test_sync_id}")
else:
print(f"❌ syncId NICHT in Response (Felder: {list(first.keys())})")
else:
print("❌ Keine Relation gefunden")
else:
print(" ❌ Keine Berechtigung für CAICollections\n")
print(" Das ist ein Berechtigungsproblem, kein Problem mit den additionalColumns\n")
finally:
print("\n5⃣ Cleanup...")
if doc_id:
api_request("DELETE", f"CDokumente/{doc_id}")
print(f" ✓ Dokument gelöscht")
if collection_id:
api_request("DELETE", f"CAICollections/{collection_id}")
print(f" ✓ Collection gelöscht")
print("\n" + "="*80 + "\n")

View File

@@ -0,0 +1,132 @@
#!/usr/bin/env python3
"""
Teste verschiedene API-Syntaxen für additionalColumns
"""
import requests
import json
from datetime import datetime
BASE_URL = "https://crm.bitbylaw.com"
API_KEY = "e53def10eea27b92a6cd00f40a3e09a4"
HEADERS = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
def api_request(method, endpoint, data=None):
url = f"{BASE_URL}/api/v1/{endpoint}"
try:
if method == "GET":
response = requests.get(url, headers=HEADERS)
elif method == "POST":
response = requests.post(url, headers=HEADERS, json=data)
elif method == "DELETE":
response = requests.delete(url, headers=HEADERS)
return response
except Exception as e:
print(f" Exception: {e}")
return None
print("\n" + "="*80)
print(" "*20 + "API Syntax Variations Test")
print("="*80 + "\n")
doc_id = None
collection_id = None
try:
# Erstelle Test-Entities
print("Erstelle Test-Entities...")
doc = api_request("POST", "CDokumente", {
"name": f"Test Doc {datetime.now().strftime('%H:%M:%S')}"
}).json()
doc_id = doc['id']
print(f" Doc: {doc_id}")
collection = api_request("POST", "CAICollections", {
"name": f"Test Col {datetime.now().strftime('%H:%M:%S')}"
}).json()
collection_id = collection['id']
print(f" Col: {collection_id}\n")
# Teste verschiedene Syntaxen
variations = [
{
"name": "Variante 1: columns mit syncId",
"data": {
"id": collection_id,
"columns": {
"syncId": "TEST-SYNC-001"
}
}
},
{
"name": "Variante 2: Direkt syncId im Body",
"data": {
"id": collection_id,
"syncId": "TEST-SYNC-002"
}
},
{
"name": "Variante 3: columns mit sync_id (Snake-Case)",
"data": {
"id": collection_id,
"columns": {
"sync_id": "TEST-SYNC-003"
}
}
},
{
"name": "Variante 4: additionalColumns",
"data": {
"id": collection_id,
"additionalColumns": {
"syncId": "TEST-SYNC-004"
}
}
}
]
for i, variant in enumerate(variations, 1):
print(f"{i}. {variant['name']}")
# Lösche vorherige Verknüpfung
api_request("DELETE", f"CDokumente/{doc_id}/cAICollections", {"id": collection_id})
# Verknüpfe mit Variante
response = api_request("POST", f"CDokumente/{doc_id}/cAICollections", variant['data'])
print(f" Status: {response.status_code}")
if response.status_code == 200:
# Prüfe Datenbank
import subprocess
db_check = subprocess.run([
"docker", "exec", "espocrm-db", "mariadb",
"-u", "espocrm", "-pdatabase_password", "espocrm",
"-e", f"SELECT sync_id FROM c_a_i_collection_c_dokumente WHERE c_dokumente_id='{doc_id}' AND deleted=0;"
], capture_output=True, text=True)
lines = db_check.stdout.strip().split('\n')
if len(lines) > 1:
sync_id_value = lines[1].strip()
if sync_id_value and sync_id_value != "NULL":
print(f" ✅ syncId in DB: {sync_id_value}")
else:
print(f" ❌ syncId ist NULL")
else:
print(f" ⚠️ Keine Zeile in DB")
else:
print(f" ❌ Request fehlgeschlagen: {response.text[:100]}")
print()
finally:
print("Cleanup...")
if doc_id:
api_request("DELETE", f"CDokumente/{doc_id}")
if collection_id:
api_request("DELETE", f"CAICollections/{collection_id}")
print("="*80 + "\n")

View File

@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""
Test-Skript zum Überprüfen der Many-to-Many-Beziehung mit additionalColumns
zwischen CDokumente und CAICollections
"""
import requests
import json
import sys
from datetime import datetime
# API-Konfiguration (aus e2e_tests.py übernommen)
BASE_URL = "https://crm.bitbylaw.com"
API_KEY = "2b0747ca34d15032aa233ae043cc61bc"
HEADERS = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
def api_request(method, endpoint, data=None):
"""Führt einen API-Request aus"""
url = f"{BASE_URL}/api/v1/{endpoint}"
try:
if method == "GET":
response = requests.get(url, headers=HEADERS)
elif method == "POST":
response = requests.post(url, headers=HEADERS, json=data)
elif method == "PUT":
response = requests.put(url, headers=HEADERS, json=data)
elif method == "DELETE":
response = requests.delete(url, headers=HEADERS)
else:
raise ValueError(f"Unknown method: {method}")
response.raise_for_status()
return response.json() if response.text else {}
except requests.exceptions.RequestException as e:
print(f"❌ API Error: {e}")
if hasattr(e.response, 'text'):
print(f" Response: {e.response.text}")
return None
def main():
print("="*80)
print(" "*20 + "Many-to-Many Junction Test")
print("="*80)
print()
# Test-Daten
test_sync_id = f"TEST-SYNC-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
doc_id = None
collection_id = None
try:
# 1. CDokumente-Eintrag erstellen
print("1⃣ Erstelle Test-Dokument...")
doc_data = {
"name": f"Test-Dokument für Junction-Test {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"description": "Test-Dokument für Many-to-Many mit syncId"
}
doc_result = api_request("POST", "CDokumente", doc_data)
if not doc_result or 'id' not in doc_result:
print("❌ Fehler beim Erstellen des Dokuments")
return False
doc_id = doc_result['id']
print(f"✓ Dokument erstellt: {doc_id}")
print()
# 2. CAICollections-Eintrag erstellen
print("2⃣ Erstelle Test-Collection...")
collection_data = {
"name": f"Test-Collection für Junction-Test {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
"description": "Test-Collection für Many-to-Many mit syncId"
}
collection_result = api_request("POST", "CAICollections", collection_data)
if not collection_result or 'id' not in collection_result:
print("❌ Fehler beim Erstellen der Collection")
return False
collection_id = collection_result['id']
print(f"✓ Collection erstellt: {collection_id}")
print()
# 3. Verknüpfung erstellen mit syncId
print("3⃣ Verknüpfe Dokument mit Collection (mit syncId)...")
print(f" syncId: {test_sync_id}")
link_data = {
"id": collection_id,
"columns": {
"syncId": test_sync_id
}
}
link_result = api_request("POST", f"CDokumente/{doc_id}/cAICollections", link_data)
if link_result is None:
print("❌ Fehler beim Verknüpfen")
return False
print(f"✓ Verknüpfung erstellt")
print()
# 4. Beziehung über API abrufen
print("4⃣ Rufe Beziehung über API ab...")
relations = api_request("GET", f"CDokumente/{doc_id}/cAICollections")
if not relations:
print("❌ Fehler beim Abrufen der Beziehung")
return False
print(f"✓ Beziehung abgerufen")
print("\nAPI-Response:")
print("-" * 80)
print(json.dumps(relations, indent=2, ensure_ascii=False))
print("-" * 80)
print()
# 5. Prüfe ob syncId vorhanden ist
print("5⃣ Prüfe ob syncId in der Response vorhanden ist...")
if 'list' in relations and len(relations['list']) > 0:
first_relation = relations['list'][0]
if 'syncId' in first_relation:
returned_sync_id = first_relation['syncId']
if returned_sync_id == test_sync_id:
print(f"✅ syncId korrekt zurückgegeben: {returned_sync_id}")
success = True
else:
print(f"⚠️ syncId zurückgegeben, aber Wert stimmt nicht überein:")
print(f" Erwartet: {test_sync_id}")
print(f" Erhalten: {returned_sync_id}")
success = False
else:
print("❌ syncId ist NICHT in der API-Response vorhanden")
print(f" Vorhandene Felder: {list(first_relation.keys())}")
success = False
else:
print("❌ Keine Beziehungen in der Response gefunden")
success = False
print()
# 6. Direkter Datenbankcheck (optional)
print("6⃣ Prüfe Datenbank direkt...")
import subprocess
db_check = subprocess.run([
"docker", "exec", "espocrm-db", "mariadb",
"-u", "espocrm", "-pdatabase_password", "espocrm",
"-e", f"SELECT * FROM c_a_i_collection_c_dokumente WHERE c_dokumente_id='{doc_id}' AND deleted=0;"
], capture_output=True, text=True)
if db_check.returncode == 0:
print("Datenbank-Inhalt:")
print("-" * 80)
print(db_check.stdout)
print("-" * 80)
else:
print(f"⚠️ Konnte Datenbank nicht direkt abfragen: {db_check.stderr}")
print()
return success
finally:
# Cleanup
print("7⃣ Räume Test-Daten auf...")
if doc_id:
result = api_request("DELETE", f"CDokumente/{doc_id}")
if result is not None:
print(f"✓ Dokument gelöscht: {doc_id}")
if collection_id:
result = api_request("DELETE", f"CAICollections/{collection_id}")
if result is not None:
print(f"✓ Collection gelöscht: {collection_id}")
print()
if __name__ == "__main__":
print()
result = main()
print("="*80)
if result:
print("✅ TEST ERFOLGREICH - Many-to-Many mit additionalColumns funktioniert!")
else:
print("❌ TEST FEHLGESCHLAGEN - syncId nicht in API-Response")
print("="*80)
sys.exit(0 if result else 1)

View File

@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""
Finaler Test: Junction-Entity API mit Filterung und syncId
"""
import requests
import json
BASE_URL = "https://crm.bitbylaw.com"
API_KEY = "e53def10eea27b92a6cd00f40a3e09a4"
HEADERS = {
"X-Api-Key": API_KEY,
"Content-Type": "application/json"
}
print("\n" + "="*80)
print(" "*25 + "Junction-Entity API - SUCCESS!")
print("="*80 + "\n")
# Test 1: Alle Junction-Einträge
print("1⃣ Alle Verknüpfungen abrufen:")
response = requests.get(
f"{BASE_URL}/api/v1/CAICollectionCDokumente",
headers=HEADERS,
params={"maxSize": 10}
)
print(f" Status: {response.status_code}")
data = response.json()
print(f" Total: {data['total']}")
print(f" ✅ API funktioniert!\n")
# Test 2: Filterung nach Dokument-ID
print("2⃣ Filterung nach Dokument-ID (testdoc999):")
response = requests.get(
f"{BASE_URL}/api/v1/CAICollectionCDokumente",
headers=HEADERS,
params={
"where[0][type]": "equals",
"where[0][attribute]": "cDokumenteId",
"where[0][value]": "testdoc999",
"select": "id,cDokumenteId,cAICollectionsId,syncId"
}
)
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(json.dumps(data, indent=2, ensure_ascii=False))
if data['list'] and 'syncId' in data['list'][0]:
print(f"\n ✅ syncId gefunden: {data['list'][0]['syncId']}")
print()
# Test 3: Suche nach syncId
print("3⃣ Filterung nach syncId (SYNC-TEST-999):")
response = requests.get(
f"{BASE_URL}/api/v1/CAICollectionCDokumente",
headers=HEADERS,
params={
"where[0][type]": "equals",
"where[0][attribute]": "syncId",
"where[0][value]": "SYNC-TEST-999"
}
)
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(json.dumps(data, indent=2, ensure_ascii=False))
if data['list']:
entry = data['list'][0]
print(f"\n ✅ Verknüpfung gefunden:")
print(f" Dokument: {entry['cDokumenteId']}")
print(f" Collection: {entry['cAICollectionsId']}")
print(f" Sync-ID: {entry['syncId']}")
print()
print("="*80)
print("✅ VOLLSTÄNDIGER ERFOLG!")
print("="*80)
print("\nDie Junction-Entity ist via REST-API verfügbar und gibt die syncId zurück!")
print("- Endpoint: /api/v1/CAICollectionCDokumente")
print("- Filterung funktioniert (where-Clauses)")
print("- Alle additionalColumns (syncId) sind in der Response")
print("\n" + "="*80 + "\n")