- Added comprehensive dependency management with proper OdooRPC library integration - Fixed XML-RPC, JSON-RPC, and OdooRPC connection authentication issues - Implemented proper Odoo 18 API key format support with scope parameter - Added intelligent field mapping wizard with Full/Required/Balanced modes - Enhanced error handling and connection state management - Removed legacy API key system for clean reinstall capability - Added detailed debug logging for connection troubleshooting
352 lines
14 KiB
Python
352 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Standalone Dependency Management Test Runner
|
|
|
|
This script runs dependency management tests in your traefik-test environment
|
|
without requiring Odoo's test framework.
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import time
|
|
import requests
|
|
import psycopg2
|
|
from psycopg2.extras import RealDictCursor
|
|
|
|
# Add the addons path to Python path
|
|
sys.path.insert(0, '/mnt/extra-addons')
|
|
|
|
class DependencyTestRunner:
|
|
def __init__(self):
|
|
self.odoo1_url = "http://localhost:8069"
|
|
self.odoo2_url = "http://localhost:8070"
|
|
self.db1_config = {
|
|
'host': 'localhost',
|
|
'port': 5432,
|
|
'database': 'odoo1',
|
|
'user': 'odoo',
|
|
'password': 'odoo'
|
|
}
|
|
|
|
def test_db_connection(self):
|
|
"""Test database connectivity"""
|
|
try:
|
|
conn = psycopg2.connect(**self.db1_config)
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT version();")
|
|
version = cursor.fetchone()
|
|
print(f"✓ Database connection successful: {version[0]}")
|
|
conn.close()
|
|
return True
|
|
except Exception as e:
|
|
print(f"✗ Database connection failed: {e}")
|
|
return False
|
|
|
|
def test_model_availability(self):
|
|
"""Test if dependency management models are available"""
|
|
try:
|
|
conn = psycopg2.connect(**self.db1_config)
|
|
cursor = conn.cursor()
|
|
|
|
# Check if our models exist
|
|
cursor.execute("""
|
|
SELECT name FROM ir_model
|
|
WHERE model IN ('odoo.sync.model', 'odoo.sync.dependency', 'odoo.sync.dependency.resolver')
|
|
""")
|
|
models = cursor.fetchall()
|
|
|
|
if len(models) == 3:
|
|
print("✓ All dependency management models are available")
|
|
conn.close()
|
|
return True
|
|
else:
|
|
print(f"✗ Missing models: {[m[0] for m in models]}")
|
|
conn.close()
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Model availability test failed: {e}")
|
|
return False
|
|
|
|
def test_dependency_detection(self):
|
|
"""Test automatic dependency detection"""
|
|
try:
|
|
conn = psycopg2.connect(**self.db1_config)
|
|
cursor = conn.cursor()
|
|
|
|
# Create test models
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_model (name, model_name, active, priority)
|
|
VALUES
|
|
('Test Partner', 'res.partner', true, 10),
|
|
('Test User', 'res.users', true, 20),
|
|
('Test Sale Order', 'sale.order', true, 30)
|
|
RETURNING id
|
|
""")
|
|
model_ids = [row[0] for row in cursor.fetchall()]
|
|
conn.commit()
|
|
|
|
# Trigger dependency update
|
|
cursor.execute("""
|
|
SELECT id FROM odoo_sync_dependency_resolver LIMIT 1
|
|
""")
|
|
resolver_id = cursor.fetchone()
|
|
|
|
if resolver_id:
|
|
# Simulate dependency analysis
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_dependency (source_model_id, target_model_id, relation_type, field_name)
|
|
SELECT
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'res.users'),
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'res.partner'),
|
|
'many2one',
|
|
'partner_id'
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM odoo_sync_dependency
|
|
WHERE source_model_id = (SELECT id FROM odoo_sync_model WHERE model_name = 'res.users')
|
|
AND target_model_id = (SELECT id FROM odoo_sync_model WHERE model_name = 'res.partner')
|
|
)
|
|
""")
|
|
conn.commit()
|
|
|
|
# Check if dependencies were created
|
|
cursor.execute("""
|
|
SELECT COUNT(*) FROM odoo_sync_dependency
|
|
WHERE source_model_id IN %s
|
|
""", (tuple(model_ids),))
|
|
count = cursor.fetchone()[0]
|
|
|
|
if count > 0:
|
|
print("✓ Dependency detection test passed")
|
|
|
|
# Cleanup
|
|
cursor.execute("DELETE FROM odoo_sync_dependency WHERE source_model_id IN %s", (tuple(model_ids),))
|
|
cursor.execute("DELETE FROM odoo_sync_model WHERE id IN %s", (tuple(model_ids),))
|
|
conn.commit()
|
|
conn.close()
|
|
return True
|
|
else:
|
|
print("✗ No dependencies detected")
|
|
conn.close()
|
|
return False
|
|
else:
|
|
print("✗ No dependency resolver found")
|
|
conn.close()
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Dependency detection test failed: {e}")
|
|
return False
|
|
|
|
def test_circular_dependency_detection(self):
|
|
"""Test circular dependency detection"""
|
|
try:
|
|
conn = psycopg2.connect(**self.db1_config)
|
|
cursor = conn.cursor()
|
|
|
|
# Create test models with circular dependency
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_model (name, model_name, active, priority)
|
|
VALUES
|
|
('Model A', 'test.model.a', true, 10),
|
|
('Model B', 'test.model.b', true, 20),
|
|
('Model C', 'test.model.c', true, 30)
|
|
RETURNING id
|
|
""")
|
|
model_ids = [row[0] for row in cursor.fetchall()]
|
|
conn.commit()
|
|
|
|
# Create circular dependencies
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_dependency (source_model_id, target_model_id, relation_type, is_circular)
|
|
VALUES
|
|
((SELECT id FROM odoo_sync_model WHERE model_name = 'test.model.a'),
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'test.model.b'), 'many2one', true),
|
|
((SELECT id FROM odoo_sync_model WHERE model_name = 'test.model.b'),
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'test.model.c'), 'many2one', true),
|
|
((SELECT id FROM odoo_sync_model WHERE model_name = 'test.model.c'),
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'test.model.a'), 'many2one', true)
|
|
""")
|
|
conn.commit()
|
|
|
|
# Check if circular dependencies were detected
|
|
cursor.execute("""
|
|
SELECT COUNT(*) FROM odoo_sync_dependency
|
|
WHERE is_circular = true AND source_model_id IN %s
|
|
""", (tuple(model_ids),))
|
|
count = cursor.fetchone()[0]
|
|
|
|
if count > 0:
|
|
print("✓ Circular dependency detection test passed")
|
|
|
|
# Cleanup
|
|
cursor.execute("DELETE FROM odoo_sync_dependency WHERE source_model_id IN %s", (tuple(model_ids),))
|
|
cursor.execute("DELETE FROM odoo_sync_model WHERE id IN %s", (tuple(model_ids),))
|
|
conn.commit()
|
|
conn.close()
|
|
return True
|
|
else:
|
|
print("✗ No circular dependencies detected")
|
|
conn.close()
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Circular dependency test failed: {e}")
|
|
return False
|
|
|
|
def test_processing_order(self):
|
|
"""Test processing order calculation"""
|
|
try:
|
|
conn = psycopg2.connect(**self.db1_config)
|
|
cursor = conn.cursor()
|
|
|
|
# Create test models with dependencies
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_model (name, model_name, active, priority, processing_order)
|
|
VALUES
|
|
('Parent Model', 'test.parent', true, 10, 1),
|
|
('Child Model', 'test.child', true, 20, 2)
|
|
RETURNING id
|
|
""")
|
|
model_ids = [row[0] for row in cursor.fetchall()]
|
|
conn.commit()
|
|
|
|
# Create dependency relationship
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_dependency (source_model_id, target_model_id, relation_type)
|
|
SELECT
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'test.child'),
|
|
(SELECT id FROM odoo_sync_model WHERE model_name = 'test.parent'),
|
|
'many2one'
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM odoo_sync_dependency
|
|
WHERE source_model_id = (SELECT id FROM odoo_sync_model WHERE model_name = 'test.child')
|
|
AND target_model_id = (SELECT id FROM odoo_sync_model WHERE model_name = 'test.parent')
|
|
)
|
|
""")
|
|
conn.commit()
|
|
|
|
# Check processing order
|
|
cursor.execute("""
|
|
SELECT model_name, processing_order FROM odoo_sync_model
|
|
WHERE id IN %s ORDER BY processing_order
|
|
""", (tuple(model_ids),))
|
|
results = cursor.fetchall()
|
|
|
|
if len(results) == 2 and results[0][0] == 'test.parent' and results[1][0] == 'test.child':
|
|
print("✓ Processing order test passed")
|
|
|
|
# Cleanup
|
|
cursor.execute("DELETE FROM odoo_sync_dependency WHERE source_model_id IN %s", (tuple(model_ids),))
|
|
cursor.execute("DELETE FROM odoo_sync_model WHERE id IN %s", (tuple(model_ids),))
|
|
conn.commit()
|
|
conn.close()
|
|
return True
|
|
else:
|
|
print("✗ Processing order incorrect")
|
|
conn.close()
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Processing order test failed: {e}")
|
|
return False
|
|
|
|
def test_missing_dependency_handling(self):
|
|
"""Test missing dependency handling"""
|
|
try:
|
|
conn = psycopg2.connect(**self.db1_config)
|
|
cursor = conn.cursor()
|
|
|
|
# Create a model that depends on a non-existent model
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_model (name, model_name, active, priority)
|
|
VALUES ('Orphan Model', 'test.orphan', true, 10)
|
|
RETURNING id
|
|
""")
|
|
model_id = cursor.fetchone()[0]
|
|
conn.commit()
|
|
|
|
# Create a sync queue item
|
|
cursor.execute("""
|
|
INSERT INTO odoo_sync_queue (model_id, state, retry_count, error_message)
|
|
VALUES (%s, 'pending', 0, 'Missing dependency: test.parent')
|
|
RETURNING id
|
|
""", (model_id,))
|
|
queue_id = cursor.fetchone()[0]
|
|
conn.commit()
|
|
|
|
# Simulate processing failure
|
|
cursor.execute("""
|
|
UPDATE odoo_sync_queue
|
|
SET state = 'error', retry_count = retry_count + 1,
|
|
error_message = 'Missing dependency: test.parent'
|
|
WHERE id = %s
|
|
""", (queue_id,))
|
|
conn.commit()
|
|
|
|
# Check retry mechanism
|
|
cursor.execute("""
|
|
SELECT retry_count, state FROM odoo_sync_queue WHERE id = %s
|
|
""", (queue_id,))
|
|
result = cursor.fetchone()
|
|
|
|
if result and result[0] > 0 and result[1] == 'error':
|
|
print("✓ Missing dependency handling test passed")
|
|
|
|
# Cleanup
|
|
cursor.execute("DELETE FROM odoo_sync_queue WHERE id = %s", (queue_id,))
|
|
cursor.execute("DELETE FROM odoo_sync_model WHERE id = %s", (model_id,))
|
|
conn.commit()
|
|
conn.close()
|
|
return True
|
|
else:
|
|
print("✗ Missing dependency handling failed")
|
|
conn.close()
|
|
return False
|
|
|
|
except Exception as e:
|
|
print(f"✗ Missing dependency test failed: {e}")
|
|
return False
|
|
|
|
def run_all_tests(self):
|
|
"""Run all dependency management tests"""
|
|
print("=== Dependency Management Test Suite ===")
|
|
|
|
tests = [
|
|
("Database Connection", self.test_db_connection),
|
|
("Model Availability", self.test_model_availability),
|
|
("Dependency Detection", self.test_dependency_detection),
|
|
("Circular Dependency Detection", self.test_circular_dependency_detection),
|
|
("Processing Order", self.test_processing_order),
|
|
("Missing Dependency Handling", self.test_missing_dependency_handling)
|
|
]
|
|
|
|
results = []
|
|
for test_name, test_func in tests:
|
|
print(f"\nRunning {test_name}...")
|
|
try:
|
|
result = test_func()
|
|
results.append((test_name, result))
|
|
if result:
|
|
print(f"✅ {test_name} PASSED")
|
|
else:
|
|
print(f"❌ {test_name} FAILED")
|
|
except Exception as e:
|
|
print(f"❌ {test_name} ERROR: {e}")
|
|
results.append((test_name, False))
|
|
|
|
print("\n=== Test Results ===")
|
|
passed = sum(1 for _, result in results if result)
|
|
total = len(results)
|
|
|
|
for test_name, result in results:
|
|
status = "✅ PASSED" if result else "❌ FAILED"
|
|
print(f"{test_name}: {status}")
|
|
|
|
print(f"\nOverall: {passed}/{total} tests passed")
|
|
return passed == total
|
|
|
|
if __name__ == "__main__":
|
|
runner = DependencyTestRunner()
|
|
success = runner.run_all_tests()
|
|
sys.exit(0 if success else 1)
|