|
"""
|
|
وحدة اختبار التطبيق لنظام إدارة المناقصات - Hybrid Face
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import logging
|
|
import unittest
|
|
import tkinter as tk
|
|
from pathlib import Path
|
|
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger('test_app')
|
|
|
|
|
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
|
|
from database.db_connector import DatabaseConnector
|
|
from database.models import User, Project, Document, ProjectItem, Resource, Risk, Report, SystemLog
|
|
from modules.document_analysis.analyzer import DocumentAnalyzer
|
|
from modules.pricing.pricing_engine import PricingEngine
|
|
from modules.risk_analysis.risk_analyzer import RiskAnalyzer
|
|
from modules.ai_assistant.assistant import AIAssistant
|
|
from styling.theme import AppTheme
|
|
from styling.icons import IconGenerator
|
|
from styling.charts import ChartGenerator
|
|
from config import AppConfig
|
|
|
|
class TestDatabaseConnector(unittest.TestCase):
|
|
"""اختبار وحدة اتصال قاعدة البيانات"""
|
|
|
|
def setUp(self):
|
|
"""إعداد بيئة الاختبار"""
|
|
|
|
self.config = AppConfig()
|
|
self.config.DB_NAME = "test_tender_system.db"
|
|
self.db = DatabaseConnector(self.config)
|
|
|
|
def tearDown(self):
|
|
"""تنظيف بيئة الاختبار"""
|
|
self.db.disconnect()
|
|
|
|
if os.path.exists(self.config.DB_NAME):
|
|
os.remove(self.config.DB_NAME)
|
|
|
|
def test_connection(self):
|
|
"""اختبار الاتصال بقاعدة البيانات"""
|
|
self.assertTrue(self.db.is_connected)
|
|
|
|
def test_execute_query(self):
|
|
"""اختبار تنفيذ استعلام"""
|
|
cursor = self.db.execute("SELECT 1")
|
|
self.assertIsNotNone(cursor)
|
|
result = cursor.fetchone()
|
|
self.assertEqual(result[0], 1)
|
|
|
|
def test_insert_and_fetch(self):
|
|
"""اختبار إدراج واسترجاع البيانات"""
|
|
|
|
user_data = {
|
|
"username": "test_user",
|
|
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
|
|
"full_name": "مستخدم اختبار",
|
|
"email": "[email protected]",
|
|
"role": "user"
|
|
}
|
|
user_id = self.db.insert("users", user_data)
|
|
self.assertIsNotNone(user_id)
|
|
|
|
|
|
user = self.db.fetch_one("SELECT * FROM users WHERE id = ?", (user_id,))
|
|
self.assertIsNotNone(user)
|
|
self.assertEqual(user["username"], "test_user")
|
|
self.assertEqual(user["full_name"], "مستخدم اختبار")
|
|
|
|
def test_update(self):
|
|
"""اختبار تحديث البيانات"""
|
|
|
|
user_data = {
|
|
"username": "update_user",
|
|
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
|
|
"full_name": "مستخدم للتحديث",
|
|
"email": "[email protected]",
|
|
"role": "user"
|
|
}
|
|
user_id = self.db.insert("users", user_data)
|
|
|
|
|
|
updated_data = {
|
|
"full_name": "مستخدم تم تحديثه",
|
|
"role": "admin"
|
|
}
|
|
rows_affected = self.db.update("users", updated_data, "id = ?", (user_id,))
|
|
self.assertEqual(rows_affected, 1)
|
|
|
|
|
|
user = self.db.fetch_one("SELECT * FROM users WHERE id = ?", (user_id,))
|
|
self.assertEqual(user["full_name"], "مستخدم تم تحديثه")
|
|
self.assertEqual(user["role"], "admin")
|
|
|
|
def test_delete(self):
|
|
"""اختبار حذف البيانات"""
|
|
|
|
user_data = {
|
|
"username": "delete_user",
|
|
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
|
|
"full_name": "مستخدم للحذف",
|
|
"email": "[email protected]",
|
|
"role": "user"
|
|
}
|
|
user_id = self.db.insert("users", user_data)
|
|
|
|
|
|
rows_affected = self.db.delete("users", "id = ?", (user_id,))
|
|
self.assertEqual(rows_affected, 1)
|
|
|
|
|
|
user = self.db.fetch_one("SELECT * FROM users WHERE id = ?", (user_id,))
|
|
self.assertIsNone(user)
|
|
|
|
|
|
class TestModels(unittest.TestCase):
|
|
"""اختبار نماذج البيانات"""
|
|
|
|
def setUp(self):
|
|
"""إعداد بيئة الاختبار"""
|
|
|
|
self.config = AppConfig()
|
|
self.config.DB_NAME = "test_models.db"
|
|
self.db = DatabaseConnector(self.config)
|
|
|
|
def tearDown(self):
|
|
"""تنظيف بيئة الاختبار"""
|
|
self.db.disconnect()
|
|
|
|
if os.path.exists(self.config.DB_NAME):
|
|
os.remove(self.config.DB_NAME)
|
|
|
|
def test_user_model(self):
|
|
"""اختبار نموذج المستخدم"""
|
|
|
|
user = User(self.db)
|
|
user.data = {
|
|
"username": "model_user",
|
|
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
|
|
"full_name": "مستخدم نموذج",
|
|
"email": "[email protected]",
|
|
"role": "user",
|
|
"is_active": 1
|
|
}
|
|
|
|
|
|
self.assertTrue(user.save())
|
|
self.assertIsNotNone(user.data.get("id"))
|
|
|
|
|
|
retrieved_user = User.get_by_id(user.data["id"], self.db)
|
|
self.assertIsNotNone(retrieved_user)
|
|
self.assertEqual(retrieved_user.data["username"], "model_user")
|
|
|
|
|
|
authenticated_user = User.authenticate("model_user", "admin", self.db)
|
|
self.assertIsNotNone(authenticated_user)
|
|
self.assertEqual(authenticated_user.data["username"], "model_user")
|
|
|
|
|
|
user.set_password("newpassword")
|
|
user.save()
|
|
|
|
|
|
authenticated_user = User.authenticate("model_user", "newpassword", self.db)
|
|
self.assertIsNotNone(authenticated_user)
|
|
|
|
|
|
self.assertTrue(user.delete())
|
|
|
|
def test_project_model(self):
|
|
"""اختبار نموذج المشروع"""
|
|
|
|
user = User(self.db)
|
|
user.data = {
|
|
"username": "project_user",
|
|
"password": "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918",
|
|
"full_name": "مستخدم المشروع",
|
|
"email": "[email protected]",
|
|
"role": "user",
|
|
"is_active": 1
|
|
}
|
|
user.save()
|
|
|
|
|
|
project = Project(self.db)
|
|
project.data = {
|
|
"name": "مشروع اختبار",
|
|
"client": "عميل اختبار",
|
|
"description": "وصف مشروع الاختبار",
|
|
"start_date": "2025-01-01",
|
|
"end_date": "2025-12-31",
|
|
"status": "تخطيط",
|
|
"created_by": user.data["id"]
|
|
}
|
|
|
|
|
|
self.assertTrue(project.save())
|
|
self.assertIsNotNone(project.data.get("id"))
|
|
|
|
|
|
retrieved_project = Project.get_by_id(project.data["id"], self.db)
|
|
self.assertIsNotNone(retrieved_project)
|
|
self.assertEqual(retrieved_project.data["name"], "مشروع اختبار")
|
|
|
|
|
|
item = ProjectItem(self.db)
|
|
item.data = {
|
|
"project_id": project.data["id"],
|
|
"name": "بند اختبار",
|
|
"description": "وصف بند الاختبار",
|
|
"unit": "م²",
|
|
"quantity": 100,
|
|
"unit_price": 500,
|
|
"total_price": 50000
|
|
}
|
|
item.save()
|
|
|
|
|
|
total_cost = project.calculate_total_cost()
|
|
self.assertEqual(total_cost, 50000)
|
|
|
|
|
|
self.assertTrue(project.delete())
|
|
|
|
|
|
self.assertTrue(user.delete())
|
|
|
|
|
|
class TestDocumentAnalyzer(unittest.TestCase):
|
|
"""اختبار محلل المستندات"""
|
|
|
|
def setUp(self):
|
|
"""إعداد بيئة الاختبار"""
|
|
self.config = AppConfig()
|
|
self.analyzer = DocumentAnalyzer(self.config)
|
|
|
|
|
|
self.test_docs_dir = Path("test_documents")
|
|
self.test_docs_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
self.test_doc_path = self.test_docs_dir / "test_document.txt"
|
|
with open(self.test_doc_path, "w", encoding="utf-8") as f:
|
|
f.write("هذا مستند اختبار لمحلل المستندات")
|
|
|
|
def tearDown(self):
|
|
"""تنظيف بيئة الاختبار"""
|
|
|
|
if self.test_doc_path.exists():
|
|
self.test_doc_path.unlink()
|
|
|
|
|
|
if self.test_docs_dir.exists():
|
|
self.test_docs_dir.rmdir()
|
|
|
|
def test_analyze_document(self):
|
|
"""اختبار تحليل المستند"""
|
|
|
|
result = self.analyzer.analyze_document(str(self.test_doc_path), "tender")
|
|
self.assertTrue(result)
|
|
|
|
|
|
import time
|
|
max_wait = 5
|
|
waited = 0
|
|
while self.analyzer.analysis_in_progress and waited < max_wait:
|
|
time.sleep(0.5)
|
|
waited += 0.5
|
|
|
|
|
|
self.assertFalse(self.analyzer.analysis_in_progress)
|
|
results = self.analyzer.get_analysis_results()
|
|
self.assertEqual(results["status"], "اكتمل التحليل")
|
|
self.assertEqual(results["document_path"], str(self.test_doc_path))
|
|
|
|
def test_export_analysis_results(self):
|
|
"""اختبار تصدير نتائج التحليل"""
|
|
|
|
self.analyzer.analyze_document(str(self.test_doc_path), "tender")
|
|
|
|
|
|
import time
|
|
max_wait = 5
|
|
waited = 0
|
|
while self.analyzer.analysis_in_progress and waited < max_wait:
|
|
time.sleep(0.5)
|
|
waited += 0.5
|
|
|
|
|
|
export_path = self.test_docs_dir / "analysis_results.json"
|
|
result_path = self.analyzer.export_analysis_results(str(export_path))
|
|
self.assertIsNotNone(result_path)
|
|
|
|
|
|
self.assertTrue(export_path.exists())
|
|
|
|
|
|
if export_path.exists():
|
|
export_path.unlink()
|
|
|
|
|
|
class TestPricingEngine(unittest.TestCase):
|
|
"""اختبار محرك التسعير"""
|
|
|
|
def setUp(self):
|
|
"""إعداد بيئة الاختبار"""
|
|
self.config = AppConfig()
|
|
self.pricing_engine = PricingEngine(self.config)
|
|
|
|
def test_calculate_pricing(self):
|
|
"""اختبار حساب التسعير"""
|
|
|
|
result = self.pricing_engine.calculate_pricing(1, "comprehensive")
|
|
self.assertTrue(result)
|
|
|
|
|
|
import time
|
|
max_wait = 5
|
|
waited = 0
|
|
while self.pricing_engine.pricing_in_progress and waited < max_wait:
|
|
time.sleep(0.5)
|
|
waited += 0.5
|
|
|
|
|
|
self.assertFalse(self.pricing_engine.pricing_in_progress)
|
|
results = self.pricing_engine.get_pricing_results()
|
|
self.assertEqual(results["status"], "اكتمل التسعير")
|
|
self.assertEqual(results["project_id"], 1)
|
|
self.assertEqual(results["strategy"], "comprehensive")
|
|
|
|
|
|
self.assertIn("direct_costs", results)
|
|
self.assertIn("total_direct_costs", results["direct_costs"])
|
|
|
|
|
|
self.assertIn("indirect_costs", results)
|
|
self.assertIn("total_indirect_costs", results["indirect_costs"])
|
|
|
|
|
|
self.assertIn("risk_costs", results)
|
|
self.assertIn("total_risk_cost", results["risk_costs"])
|
|
|
|
|
|
self.assertIn("summary", results)
|
|
self.assertIn("final_price", results["summary"])
|
|
|
|
|
|
class TestRiskAnalyzer(unittest.TestCase):
|
|
"""اختبار محلل المخاطر"""
|
|
|
|
def setUp(self):
|
|
"""إعداد بيئة الاختبار"""
|
|
self.config = AppConfig()
|
|
self.risk_analyzer = RiskAnalyzer(self.config)
|
|
|
|
def test_analyze_risks(self):
|
|
"""اختبار تحليل المخاطر"""
|
|
|
|
result = self.risk_analyzer.analyze_risks(1, "comprehensive")
|
|
self.assertTrue(result)
|
|
|
|
|
|
import time
|
|
max_wait = 5
|
|
waited = 0
|
|
while self.risk_analyzer.analysis_in_progress and waited < max_wait:
|
|
time.sleep(0.5)
|
|
waited += 0.5
|
|
|
|
|
|
self.assertFalse(self.risk_analyzer.analysis_in_progress)
|
|
results = self.risk_analyzer.get_analysis_results()
|
|
self.assertEqual(results["status"], "اكتمل التحليل")
|
|
self.assertEqual(results["project_id"], 1)
|
|
self.assertEqual(results["method"], "comprehensive")
|
|
|
|
|
|
self.assertIn("identified_risks", results)
|
|
self.assertTrue(len(results["identified_risks"]) > 0)
|
|
|
|
|
|
self.assertIn("risk_categories", results)
|
|
|
|
|
|
self.assertIn("risk_matrix", results)
|
|
|
|
|
|
self.assertIn("mitigation_strategies", results)
|
|
self.assertTrue(len(results["mitigation_strategies"]) > 0)
|
|
|
|
|
|
self.assertIn("summary", results)
|
|
self.assertIn("overall_risk_level", results["summary"])
|
|
|
|
|
|
class TestAIAssistant(unittest.TestCase):
|
|
"""اختبار المساعد الذكي"""
|
|
|
|
def setUp(self):
|
|
"""إعداد بيئة الاختبار"""
|
|
self.config = AppConfig()
|
|
self.assistant = AIAssistant(self.config)
|
|
|
|
def test_process_query(self):
|
|
"""اختبار معالجة الاستعلام"""
|
|
|
|
query = "كيف يمكنني تحليل مستند مناقصة؟"
|
|
result = self.assistant.process_query(query)
|
|
self.assertTrue(result)
|
|
|
|
|
|
import time
|
|
max_wait = 5
|
|
waited = 0
|
|
while self.assistant.processing_in_progress and waited < max_wait:
|
|
time.sleep(0.5)
|
|
waited += 0.5
|
|
|
|
|
|
self.assertFalse(self.assistant.processing_in_progress)
|
|
results = self.assistant.get_processing_results()
|
|
self.assertEqual(results["status"], "اكتملت المعالجة")
|
|
self.assertEqual(results["query"], query)
|
|
|
|
|
|
self.assertIn("response", results)
|
|
self.assertTrue(len(results["response"]) > 0)
|
|
|
|
|
|
self.assertIn("suggestions", results)
|
|
self.assertTrue(len(results["suggestions"]) > 0)
|
|
|
|
def test_conversation_history(self):
|
|
"""اختبار سجل المحادثة"""
|
|
|
|
query = "ما هي استراتيجيات التسعير المتاحة؟"
|
|
self.assistant.process_query(query)
|
|
|
|
|
|
import time
|
|
max_wait = 5
|
|
waited = 0
|
|
while self.assistant.processing_in_progress and waited < max_wait:
|
|
time.sleep(0.5)
|
|
waited += 0.5
|
|
|
|
|
|
history = self.assistant.get_conversation_history()
|
|
self.assertEqual(len(history), 2)
|
|
self.assertEqual(history[0]["role"], "user")
|
|
self.assertEqual(history[0]["content"], query)
|
|
self.assertEqual(history[1]["role"], "assistant")
|
|
|
|
|
|
self.assertTrue(self.assistant.clear_conversation_history())
|
|
history = self.assistant.get_conversation_history()
|
|
self.assertEqual(len(history), 0)
|
|
|
|
|
|
class TestStyling(unittest.TestCase):
|
|
"""اختبار وحدات التصميم"""
|
|
|
|
def test_app_theme(self):
|
|
"""اختبار نمط التطبيق"""
|
|
theme = AppTheme()
|
|
|
|
|
|
self.assertIsNotNone(theme.get_color("bg_color"))
|
|
self.assertIsNotNone(theme.get_color("fg_color"))
|
|
|
|
|
|
self.assertIsNotNone(theme.get_font("body"))
|
|
self.assertIsNotNone(theme.get_font("title"))
|
|
|
|
|
|
self.assertIsNotNone(theme.get_size("padding_medium"))
|
|
self.assertIsNotNone(theme.get_size("border_radius"))
|
|
|
|
|
|
self.assertTrue(theme.set_theme("dark"))
|
|
self.assertEqual(theme.current_theme, "dark")
|
|
|
|
|
|
self.assertTrue(theme.set_language("en"))
|
|
self.assertEqual(theme.current_language, "en")
|
|
|
|
def test_icon_generator(self):
|
|
"""اختبار مولد الأيقونات"""
|
|
icon_generator = IconGenerator()
|
|
|
|
|
|
icon_generator.generate_default_icons()
|
|
|
|
|
|
self.assertTrue(Path('assets/icons').exists())
|
|
|
|
|
|
self.assertTrue(Path('assets/icons/dashboard.png').exists())
|
|
self.assertTrue(Path('assets/icons/projects.png').exists())
|
|
|
|
def test_chart_generator(self):
|
|
"""اختبار مولد الرسوم البيانية"""
|
|
theme = AppTheme()
|
|
chart_generator = ChartGenerator(theme)
|
|
|
|
|
|
bar_data = {
|
|
'labels': ['الربع الأول', 'الربع الثاني', 'الربع الثالث', 'الربع الرابع'],
|
|
'values': [15000, 20000, 18000, 25000]
|
|
}
|
|
|
|
|
|
fig = chart_generator.create_bar_chart(
|
|
bar_data,
|
|
'الإيرادات الفصلية',
|
|
'الفصل',
|
|
'الإيرادات (ريال)'
|
|
)
|
|
|
|
|
|
self.assertIsNotNone(fig)
|
|
|
|
|
|
save_path = 'test_chart.png'
|
|
chart_generator.create_bar_chart(
|
|
bar_data,
|
|
'الإيرادات الفصلية',
|
|
'الفصل',
|
|
'الإيرادات (ريال)',
|
|
save_path=save_path
|
|
)
|
|
|
|
|
|
self.assertTrue(Path(save_path).exists())
|
|
|
|
|
|
if Path(save_path).exists():
|
|
Path(save_path).unlink()
|
|
|
|
|
|
def run_tests():
|
|
"""تشغيل الاختبارات"""
|
|
|
|
test_dir = Path('test_results')
|
|
test_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
test_results_file = test_dir / 'test_results.txt'
|
|
|
|
|
|
with open(test_results_file, 'w', encoding='utf-8') as f:
|
|
runner = unittest.TextTestRunner(stream=f, verbosity=2)
|
|
suite = unittest.TestSuite()
|
|
|
|
|
|
suite.addTest(unittest.makeSuite(TestDatabaseConnector))
|
|
suite.addTest(unittest.makeSuite(TestModels))
|
|
|
|
|
|
suite.addTest(unittest.makeSuite(TestDocumentAnalyzer))
|
|
suite.addTest(unittest.makeSuite(TestPricingEngine))
|
|
suite.addTest(unittest.makeSuite(TestRiskAnalyzer))
|
|
suite.addTest(unittest.makeSuite(TestAIAssistant))
|
|
|
|
|
|
suite.addTest(unittest.makeSuite(TestStyling))
|
|
|
|
|
|
result = runner.run(suite)
|
|
|
|
|
|
f.write("\n\n=== ملخص نتائج الاختبارات ===\n")
|
|
f.write(f"عدد الاختبارات: {result.testsRun}\n")
|
|
f.write(f"عدد النجاحات: {result.testsRun - len(result.failures) - len(result.errors)}\n")
|
|
f.write(f"عدد الإخفاقات: {len(result.failures)}\n")
|
|
f.write(f"عدد الأخطاء: {len(result.errors)}\n")
|
|
|
|
|
|
logger.info(f"تم تشغيل {result.testsRun} اختبار")
|
|
logger.info(f"النجاحات: {result.testsRun - len(result.failures) - len(result.errors)}")
|
|
logger.info(f"الإخفاقات: {len(result.failures)}")
|
|
logger.info(f"الأخطاء: {len(result.errors)}")
|
|
logger.info(f"تم حفظ نتائج الاختبارات في: {test_results_file}")
|
|
|
|
return result
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_tests()
|
|
|