|
""" |
|
وحدة اختبار التطبيق لنظام إدارة المناقصات - 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() |
|
|