quantum-framework 0.18.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/admin.py +131 -0
- quantum/cli/check.py +223 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +414 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +548 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +565 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2619 -0
- quantum/core/config_env.py +54 -0
- quantum/core/docs_links.py +8 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +208 -0
- quantum/core/expressions.py +677 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +292 -0
- quantum/core/features/data_import/src/runtime.py +533 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +147 -0
- quantum/core/features/invocation/src/runtime.py +332 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +121 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
- quantum/core/features/ui_engine/src/parser.py +1192 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/guards.py +53 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1551 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +71 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +93 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +229 -0
- quantum/core/parsers/data/invoke_parser.py +133 -0
- quantum/core/parsers/data/query_parser.py +213 -0
- quantum/core/parsers/data/transaction_parser.py +86 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +79 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +98 -0
- quantum/core/parsers/functions/param_parser.py +73 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +164 -0
- quantum/core/xml_lines.py +81 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +25 -0
- quantum/runtime/action_handler.py +532 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +1048 -0
- quantum/runtime/component_composer.py +133 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/conditions.py +54 -0
- quantum/runtime/database_service.py +595 -0
- quantum/runtime/db_schema.py +100 -0
- quantum/runtime/dev_panel.py +178 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +283 -0
- quantum/runtime/executor_registry.py +178 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +264 -0
- quantum/runtime/executors/control_flow/__init__.py +12 -0
- quantum/runtime/executors/control_flow/if_executor.py +98 -0
- quantum/runtime/executors/control_flow/loop_executor.py +308 -0
- quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
- quantum/runtime/executors/control_flow/set_executor.py +464 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +156 -0
- quantum/runtime/executors/data/invoke_executor.py +228 -0
- quantum/runtime/executors/data/query_executor.py +290 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +546 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/local_server.py +28 -0
- quantum/runtime/logging_setup.py +141 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +218 -0
- quantum/runtime/pypy_compat.py +317 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +734 -0
- quantum/runtime/service_container.py +470 -0
- quantum/runtime/table_params.py +163 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +222 -0
- quantum/runtime/ui_console.py +405 -0
- quantum/runtime/ui_desktop.py +56 -0
- quantum/runtime/ui_form_rules.py +193 -0
- quantum/runtime/ui_html_adapter.py +2567 -0
- quantum/runtime/ui_html_templates.py +2106 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_page_renderer.py +392 -0
- quantum/runtime/ui_pager.py +73 -0
- quantum/runtime/ui_table_edit.py +139 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/ui_view.py +221 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +2131 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/services.py +109 -0
- quantum/utils/__init__.py +1 -0
- quantum_admin/__init__.py +4 -0
- quantum_admin/assets/quantum-admin-connectors.js +123 -0
- quantum_admin/assets/quantum-admin.css +2058 -0
- quantum_admin/assets/quantum-source-highlight.js +106 -0
- quantum_admin/backend/__init__.py +4 -0
- quantum_admin/backend/audit_service.py +346 -0
- quantum_admin/backend/auth_service.py +394 -0
- quantum_admin/backend/component_discovery.py +766 -0
- quantum_admin/backend/config_generator.py +586 -0
- quantum_admin/backend/connector_service.py +1016 -0
- quantum_admin/backend/crud.py +557 -0
- quantum_admin/backend/database.py +226 -0
- quantum_admin/backend/db_setup_service.py +193 -0
- quantum_admin/backend/deploy_service.py +698 -0
- quantum_admin/backend/docker_service.py +422 -0
- quantum_admin/backend/environment_service.py +418 -0
- quantum_admin/backend/git_service.py +370 -0
- quantum_admin/backend/health_service.py +390 -0
- quantum_admin/backend/job_service.py +347 -0
- quantum_admin/backend/log_service.py +331 -0
- quantum_admin/backend/main.py +14426 -0
- quantum_admin/backend/models.py +986 -0
- quantum_admin/backend/pipeline_service.py +812 -0
- quantum_admin/backend/resource_manager.py +1118 -0
- quantum_admin/backend/schemas.py +419 -0
- quantum_admin/backend/secret_manager.py +277 -0
- quantum_admin/backend/settings_service.py +488 -0
- quantum_admin/backend/template_service.py +710 -0
- quantum_admin/backend/test_execution_service.py +364 -0
- quantum_admin/backend/test_generator.py +782 -0
- quantum_admin/backend/webhook_service.py +454 -0
- quantum_admin/backend/websocket_manager.py +360 -0
- quantum_admin/components/admin/_layout/AdminShell.q +121 -0
- quantum_admin/components/admin/agents.q +107 -0
- quantum_admin/components/admin/app/[name].q +558 -0
- quantum_admin/components/admin/applications.q +160 -0
- quantum_admin/components/admin/component/[...path].q +205 -0
- quantum_admin/components/admin/components.q +59 -0
- quantum_admin/components/admin/connectors.q +283 -0
- quantum_admin/components/admin/dashboard.q +86 -0
- quantum_admin/components/admin/database.q +95 -0
- quantum_admin/components/admin/features.q +69 -0
- quantum_admin/components/admin/index.q +12 -0
- quantum_admin/components/admin/jobs.q +75 -0
- quantum_admin/components/admin/login.q +65 -0
- quantum_admin/components/admin/logout.q +11 -0
- quantum_admin/components/admin/settings.q +174 -0
- quantum_admin/components/admin/source.q +45 -0
- quantum_admin/components/admin/tests.q +73 -0
- quantum_admin/run.py +13 -0
- quantum_admin/services/__init__.py +16 -0
- quantum_admin/services/_base.py +84 -0
- quantum_admin/services/_component_test_generator.py +205 -0
- quantum_admin/services/_yaml_editor.py +82 -0
- quantum_admin/services/apps.py +330 -0
- quantum_admin/services/auth.py +61 -0
- quantum_admin/services/components.py +210 -0
- quantum_admin/services/connectors.py +164 -0
- quantum_admin/services/projects.py +189 -0
- quantum_admin/services/repository.py +223 -0
- quantum_admin/services/settings.py +150 -0
- quantum_admin/services/yaml_import.py +118 -0
- quantum_framework-0.18.0.dist-info/METADATA +281 -0
- quantum_framework-0.18.0.dist-info/RECORD +333 -0
- quantum_framework-0.18.0.dist-info/WHEEL +5 -0
- quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test Generator Service - Automatically generate test cases for Quantum components
|
|
3
|
+
|
|
4
|
+
Features:
|
|
5
|
+
- Analyze component metadata to generate test cases
|
|
6
|
+
- Generate test files for functions, endpoints, and queries
|
|
7
|
+
- Support pytest test format
|
|
8
|
+
- Generate test stubs with assertions
|
|
9
|
+
- Template-based test generation
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import logging
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Dict, List, Optional, Any, Tuple
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from sqlalchemy.orm import Session
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from .models import Project, Component, Endpoint, TestRun, TestResult, ComponentTest
|
|
23
|
+
from .component_discovery import get_discovery_service, ComponentMetadata, DiscoveredFunction, DiscoveredEndpoint, DiscoveredQuery
|
|
24
|
+
except ImportError:
|
|
25
|
+
from models import Project, Component, Endpoint, TestRun, TestResult, ComponentTest
|
|
26
|
+
from component_discovery import get_discovery_service, ComponentMetadata, DiscoveredFunction, DiscoveredEndpoint, DiscoveredQuery
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class GeneratedTest:
|
|
33
|
+
"""Represents a generated test case"""
|
|
34
|
+
name: str
|
|
35
|
+
description: str
|
|
36
|
+
test_type: str # unit, integration, api, e2e
|
|
37
|
+
file_path: str
|
|
38
|
+
content: str
|
|
39
|
+
component_name: Optional[str] = None
|
|
40
|
+
target_function: Optional[str] = None
|
|
41
|
+
target_endpoint: Optional[str] = None
|
|
42
|
+
assertions: List[str] = field(default_factory=list)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class TestSuite:
|
|
47
|
+
"""Collection of generated tests"""
|
|
48
|
+
name: str
|
|
49
|
+
component_name: str
|
|
50
|
+
tests: List[GeneratedTest] = field(default_factory=list)
|
|
51
|
+
setup_code: str = ""
|
|
52
|
+
teardown_code: str = ""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestTemplates:
|
|
56
|
+
"""Test code templates for different test types"""
|
|
57
|
+
|
|
58
|
+
# File header template
|
|
59
|
+
FILE_HEADER = '''"""
|
|
60
|
+
Auto-generated tests for {component_name}
|
|
61
|
+
Generated by Quantum Admin Test Generator
|
|
62
|
+
Date: {date}
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
import pytest
|
|
66
|
+
from pathlib import Path
|
|
67
|
+
import sys
|
|
68
|
+
|
|
69
|
+
# Add project root to path
|
|
70
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
71
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
'''
|
|
75
|
+
|
|
76
|
+
# Function test template
|
|
77
|
+
FUNCTION_TEST = '''
|
|
78
|
+
def test_{function_name}_basic():
|
|
79
|
+
"""Test {function_name} with basic inputs"""
|
|
80
|
+
# Arrange
|
|
81
|
+
{setup_code}
|
|
82
|
+
|
|
83
|
+
# Act
|
|
84
|
+
result = {function_call}
|
|
85
|
+
|
|
86
|
+
# Assert
|
|
87
|
+
assert result is not None
|
|
88
|
+
{assertions}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
'''
|
|
92
|
+
|
|
93
|
+
# Function with params test template
|
|
94
|
+
FUNCTION_PARAMS_TEST = '''
|
|
95
|
+
@pytest.mark.parametrize("input_value,expected", [
|
|
96
|
+
{param_cases}
|
|
97
|
+
])
|
|
98
|
+
def test_{function_name}_params(input_value, expected):
|
|
99
|
+
"""Test {function_name} with various inputs"""
|
|
100
|
+
# Arrange
|
|
101
|
+
{setup_code}
|
|
102
|
+
|
|
103
|
+
# Act
|
|
104
|
+
result = {function_call}
|
|
105
|
+
|
|
106
|
+
# Assert
|
|
107
|
+
assert result == expected
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
'''
|
|
111
|
+
|
|
112
|
+
# Endpoint test template
|
|
113
|
+
ENDPOINT_TEST = '''
|
|
114
|
+
def test_{endpoint_name}_{method}_basic(client):
|
|
115
|
+
"""Test {method} {path}"""
|
|
116
|
+
# Arrange
|
|
117
|
+
{setup_code}
|
|
118
|
+
|
|
119
|
+
# Act
|
|
120
|
+
response = client.{method_lower}("{path}")
|
|
121
|
+
|
|
122
|
+
# Assert
|
|
123
|
+
assert response.status_code == 200
|
|
124
|
+
{assertions}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
'''
|
|
128
|
+
|
|
129
|
+
# Endpoint with body template
|
|
130
|
+
ENDPOINT_POST_TEST = '''
|
|
131
|
+
def test_{endpoint_name}_{method}_with_data(client):
|
|
132
|
+
"""Test {method} {path} with request body"""
|
|
133
|
+
# Arrange
|
|
134
|
+
data = {{
|
|
135
|
+
{request_body}
|
|
136
|
+
}}
|
|
137
|
+
|
|
138
|
+
# Act
|
|
139
|
+
response = client.{method_lower}("{path}", json=data)
|
|
140
|
+
|
|
141
|
+
# Assert
|
|
142
|
+
assert response.status_code in [200, 201]
|
|
143
|
+
{assertions}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
'''
|
|
147
|
+
|
|
148
|
+
# Query test template
|
|
149
|
+
QUERY_TEST = '''
|
|
150
|
+
def test_{query_name}_query(db_session):
|
|
151
|
+
"""Test {query_name} database query"""
|
|
152
|
+
# Arrange
|
|
153
|
+
{setup_code}
|
|
154
|
+
|
|
155
|
+
# Act
|
|
156
|
+
result = execute_query("{query_name}", db_session)
|
|
157
|
+
|
|
158
|
+
# Assert
|
|
159
|
+
assert result is not None
|
|
160
|
+
assert len(result) >= 0
|
|
161
|
+
{assertions}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
'''
|
|
165
|
+
|
|
166
|
+
# Fixtures template
|
|
167
|
+
FIXTURES = '''
|
|
168
|
+
@pytest.fixture
|
|
169
|
+
def client():
|
|
170
|
+
"""Create test client"""
|
|
171
|
+
from quantum.runtime.web_server import create_app
|
|
172
|
+
app = create_app()
|
|
173
|
+
app.config['TESTING'] = True
|
|
174
|
+
with app.test_client() as client:
|
|
175
|
+
yield client
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@pytest.fixture
|
|
179
|
+
def db_session():
|
|
180
|
+
"""Create database session for testing"""
|
|
181
|
+
from quantum.runtime.database_service import get_db_session
|
|
182
|
+
session = get_db_session()
|
|
183
|
+
yield session
|
|
184
|
+
session.rollback()
|
|
185
|
+
session.close()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
'''
|
|
189
|
+
|
|
190
|
+
# Conftest template
|
|
191
|
+
CONFTEST = '''"""
|
|
192
|
+
Pytest configuration and fixtures for {project_name}
|
|
193
|
+
Auto-generated by Quantum Admin Test Generator
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
import pytest
|
|
197
|
+
import sys
|
|
198
|
+
from pathlib import Path
|
|
199
|
+
|
|
200
|
+
# Add project root to path
|
|
201
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
202
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@pytest.fixture(scope="session")
|
|
206
|
+
def project_root():
|
|
207
|
+
"""Return project root path"""
|
|
208
|
+
return PROJECT_ROOT
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@pytest.fixture
|
|
212
|
+
def sample_data():
|
|
213
|
+
"""Provide sample test data"""
|
|
214
|
+
return {{
|
|
215
|
+
"string": "test_value",
|
|
216
|
+
"number": 42,
|
|
217
|
+
"boolean": True,
|
|
218
|
+
"list": [1, 2, 3],
|
|
219
|
+
"dict": {{"key": "value"}}
|
|
220
|
+
}}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
'''
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class TestGeneratorService:
|
|
227
|
+
"""Service for generating tests for Quantum components"""
|
|
228
|
+
|
|
229
|
+
def __init__(self, db: Session):
|
|
230
|
+
self.db = db
|
|
231
|
+
self.discovery = get_discovery_service(db)
|
|
232
|
+
self.templates = TestTemplates()
|
|
233
|
+
|
|
234
|
+
def generate_tests_for_component(
|
|
235
|
+
self,
|
|
236
|
+
project_id: int,
|
|
237
|
+
component_id: int,
|
|
238
|
+
output_dir: Optional[str] = None,
|
|
239
|
+
test_types: Optional[List[str]] = None,
|
|
240
|
+
save_to_db: bool = True
|
|
241
|
+
) -> TestSuite:
|
|
242
|
+
"""
|
|
243
|
+
Generate tests for a specific component
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
project_id: Project ID
|
|
247
|
+
component_id: Component ID
|
|
248
|
+
output_dir: Directory to write test files
|
|
249
|
+
test_types: List of test types to generate (unit, integration, api)
|
|
250
|
+
save_to_db: Whether to save test records to database
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
TestSuite with generated tests
|
|
254
|
+
"""
|
|
255
|
+
# Get component metadata
|
|
256
|
+
metadata = self.discovery.get_component_details(project_id, component_id)
|
|
257
|
+
if not metadata:
|
|
258
|
+
raise ValueError(f"Component {component_id} not found")
|
|
259
|
+
|
|
260
|
+
if test_types is None:
|
|
261
|
+
test_types = ["unit", "integration"]
|
|
262
|
+
|
|
263
|
+
suite = TestSuite(
|
|
264
|
+
name=f"test_{metadata.name.lower()}",
|
|
265
|
+
component_name=metadata.name
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Generate function tests
|
|
269
|
+
if "unit" in test_types:
|
|
270
|
+
for func in metadata.functions:
|
|
271
|
+
test = self._generate_function_test(metadata, func)
|
|
272
|
+
suite.tests.append(test)
|
|
273
|
+
|
|
274
|
+
# Generate endpoint tests
|
|
275
|
+
if "api" in test_types or "integration" in test_types:
|
|
276
|
+
for endpoint in metadata.endpoints:
|
|
277
|
+
test = self._generate_endpoint_test(metadata, endpoint)
|
|
278
|
+
suite.tests.append(test)
|
|
279
|
+
|
|
280
|
+
# Generate query tests
|
|
281
|
+
if "integration" in test_types:
|
|
282
|
+
for query in metadata.queries:
|
|
283
|
+
test = self._generate_query_test(metadata, query)
|
|
284
|
+
suite.tests.append(test)
|
|
285
|
+
|
|
286
|
+
# Write to files if output_dir specified
|
|
287
|
+
if output_dir:
|
|
288
|
+
self._write_test_files(suite, output_dir)
|
|
289
|
+
|
|
290
|
+
# Save test records to database
|
|
291
|
+
if save_to_db and suite.tests:
|
|
292
|
+
self._save_component_tests(component_id, suite, output_dir)
|
|
293
|
+
|
|
294
|
+
return suite
|
|
295
|
+
|
|
296
|
+
def _save_component_tests(
|
|
297
|
+
self,
|
|
298
|
+
component_id: int,
|
|
299
|
+
suite: TestSuite,
|
|
300
|
+
output_dir: Optional[str] = None
|
|
301
|
+
):
|
|
302
|
+
"""Save generated tests to database"""
|
|
303
|
+
# Get the component
|
|
304
|
+
component = self.db.query(Component).filter(
|
|
305
|
+
Component.id == component_id
|
|
306
|
+
).first()
|
|
307
|
+
|
|
308
|
+
if not component:
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
# Get existing tests for this component
|
|
312
|
+
existing = self.db.query(ComponentTest).filter(
|
|
313
|
+
ComponentTest.component_id == component_id
|
|
314
|
+
).all()
|
|
315
|
+
existing_names = {t.test_name for t in existing}
|
|
316
|
+
|
|
317
|
+
tests_created = 0
|
|
318
|
+
for test in suite.tests:
|
|
319
|
+
if test.name not in existing_names:
|
|
320
|
+
# Build the full file path
|
|
321
|
+
file_path = test.file_path
|
|
322
|
+
if output_dir:
|
|
323
|
+
file_path = str(Path(output_dir) / test.file_path)
|
|
324
|
+
|
|
325
|
+
component_test = ComponentTest(
|
|
326
|
+
component_id=component_id,
|
|
327
|
+
test_file=file_path,
|
|
328
|
+
test_name=test.name,
|
|
329
|
+
test_type=test.test_type,
|
|
330
|
+
generated_at=datetime.now(),
|
|
331
|
+
generated_by="auto",
|
|
332
|
+
last_status="pending"
|
|
333
|
+
)
|
|
334
|
+
self.db.add(component_test)
|
|
335
|
+
tests_created += 1
|
|
336
|
+
|
|
337
|
+
# Update component test count
|
|
338
|
+
component.test_count = len(existing) + tests_created
|
|
339
|
+
|
|
340
|
+
self.db.commit()
|
|
341
|
+
logger.info(f"Created {tests_created} test records for component {component.name}")
|
|
342
|
+
|
|
343
|
+
def generate_tests_for_project(
|
|
344
|
+
self,
|
|
345
|
+
project_id: int,
|
|
346
|
+
base_path: Optional[str] = None,
|
|
347
|
+
output_dir: Optional[str] = None,
|
|
348
|
+
test_types: Optional[List[str]] = None
|
|
349
|
+
) -> List[TestSuite]:
|
|
350
|
+
"""
|
|
351
|
+
Generate tests for all components in a project
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
project_id: Project ID
|
|
355
|
+
base_path: Base path to scan for components
|
|
356
|
+
output_dir: Directory to write test files
|
|
357
|
+
test_types: List of test types to generate
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
List of TestSuites for each component
|
|
361
|
+
"""
|
|
362
|
+
# Discover components
|
|
363
|
+
components = self.discovery.discover_components(project_id, base_path)
|
|
364
|
+
|
|
365
|
+
suites = []
|
|
366
|
+
for metadata in components:
|
|
367
|
+
if not metadata.is_valid:
|
|
368
|
+
logger.warning(f"Skipping invalid component: {metadata.name}")
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
suite = self._generate_suite_from_metadata(metadata, test_types)
|
|
372
|
+
if suite.tests:
|
|
373
|
+
suites.append(suite)
|
|
374
|
+
|
|
375
|
+
# Write to files if output_dir specified
|
|
376
|
+
if output_dir:
|
|
377
|
+
self._write_project_tests(suites, output_dir)
|
|
378
|
+
|
|
379
|
+
return suites
|
|
380
|
+
|
|
381
|
+
def generate_conftest(
|
|
382
|
+
self,
|
|
383
|
+
project_id: int,
|
|
384
|
+
output_dir: Optional[str] = None
|
|
385
|
+
) -> str:
|
|
386
|
+
"""Generate conftest.py with common fixtures"""
|
|
387
|
+
project = self.db.query(Project).filter(Project.id == project_id).first()
|
|
388
|
+
if not project:
|
|
389
|
+
raise ValueError(f"Project {project_id} not found")
|
|
390
|
+
|
|
391
|
+
content = self.templates.CONFTEST.format(
|
|
392
|
+
project_name=project.name
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
if output_dir:
|
|
396
|
+
path = Path(output_dir) / "conftest.py"
|
|
397
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
398
|
+
path.write_text(content, encoding='utf-8')
|
|
399
|
+
|
|
400
|
+
return content
|
|
401
|
+
|
|
402
|
+
def _generate_suite_from_metadata(
|
|
403
|
+
self,
|
|
404
|
+
metadata: ComponentMetadata,
|
|
405
|
+
test_types: Optional[List[str]] = None
|
|
406
|
+
) -> TestSuite:
|
|
407
|
+
"""Generate test suite from component metadata"""
|
|
408
|
+
if test_types is None:
|
|
409
|
+
test_types = ["unit", "integration"]
|
|
410
|
+
|
|
411
|
+
suite = TestSuite(
|
|
412
|
+
name=f"test_{self._sanitize_name(metadata.name)}",
|
|
413
|
+
component_name=metadata.name
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
# Generate function tests
|
|
417
|
+
if "unit" in test_types:
|
|
418
|
+
for func in metadata.functions:
|
|
419
|
+
test = self._generate_function_test(metadata, func)
|
|
420
|
+
suite.tests.append(test)
|
|
421
|
+
|
|
422
|
+
# Generate endpoint tests
|
|
423
|
+
if "api" in test_types or "integration" in test_types:
|
|
424
|
+
for endpoint in metadata.endpoints:
|
|
425
|
+
test = self._generate_endpoint_test(metadata, endpoint)
|
|
426
|
+
suite.tests.append(test)
|
|
427
|
+
|
|
428
|
+
# Generate query tests
|
|
429
|
+
if "integration" in test_types:
|
|
430
|
+
for query in metadata.queries:
|
|
431
|
+
test = self._generate_query_test(metadata, query)
|
|
432
|
+
suite.tests.append(test)
|
|
433
|
+
|
|
434
|
+
return suite
|
|
435
|
+
|
|
436
|
+
def _generate_function_test(
|
|
437
|
+
self,
|
|
438
|
+
metadata: ComponentMetadata,
|
|
439
|
+
func: DiscoveredFunction
|
|
440
|
+
) -> GeneratedTest:
|
|
441
|
+
"""Generate test for a function"""
|
|
442
|
+
func_name = self._sanitize_name(func.name)
|
|
443
|
+
|
|
444
|
+
# Build function call
|
|
445
|
+
if func.params:
|
|
446
|
+
param_values = ", ".join([f"{p}=sample_value" for p in func.params])
|
|
447
|
+
function_call = f"{func.name}({param_values})"
|
|
448
|
+
else:
|
|
449
|
+
function_call = f"{func.name}()"
|
|
450
|
+
|
|
451
|
+
# Generate assertions based on return type
|
|
452
|
+
assertions = []
|
|
453
|
+
if func.return_type:
|
|
454
|
+
if func.return_type.lower() in ['string', 'str']:
|
|
455
|
+
assertions.append("assert isinstance(result, str)")
|
|
456
|
+
elif func.return_type.lower() in ['int', 'integer', 'number']:
|
|
457
|
+
assertions.append("assert isinstance(result, (int, float))")
|
|
458
|
+
elif func.return_type.lower() in ['list', 'array']:
|
|
459
|
+
assertions.append("assert isinstance(result, list)")
|
|
460
|
+
elif func.return_type.lower() in ['dict', 'object']:
|
|
461
|
+
assertions.append("assert isinstance(result, dict)")
|
|
462
|
+
elif func.return_type.lower() in ['bool', 'boolean']:
|
|
463
|
+
assertions.append("assert isinstance(result, bool)")
|
|
464
|
+
|
|
465
|
+
content = self.templates.FUNCTION_TEST.format(
|
|
466
|
+
function_name=func_name,
|
|
467
|
+
setup_code="sample_value = 'test'",
|
|
468
|
+
function_call=function_call,
|
|
469
|
+
assertions="\n ".join(assertions) if assertions else "# Add specific assertions"
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
return GeneratedTest(
|
|
473
|
+
name=f"test_{func_name}_basic",
|
|
474
|
+
description=f"Test {func.name} function",
|
|
475
|
+
test_type="unit",
|
|
476
|
+
file_path=f"test_{self._sanitize_name(metadata.name)}.py",
|
|
477
|
+
content=content,
|
|
478
|
+
component_name=metadata.name,
|
|
479
|
+
target_function=func.name,
|
|
480
|
+
assertions=assertions
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
def _generate_endpoint_test(
|
|
484
|
+
self,
|
|
485
|
+
metadata: ComponentMetadata,
|
|
486
|
+
endpoint: DiscoveredEndpoint
|
|
487
|
+
) -> GeneratedTest:
|
|
488
|
+
"""Generate test for an endpoint"""
|
|
489
|
+
# Sanitize endpoint name for test function
|
|
490
|
+
endpoint_name = self._sanitize_name(endpoint.path.replace('/', '_'))
|
|
491
|
+
method = endpoint.method.upper()
|
|
492
|
+
method_lower = method.lower()
|
|
493
|
+
|
|
494
|
+
# Choose template based on method
|
|
495
|
+
if method in ['POST', 'PUT', 'PATCH']:
|
|
496
|
+
content = self.templates.ENDPOINT_POST_TEST.format(
|
|
497
|
+
endpoint_name=endpoint_name,
|
|
498
|
+
method=method,
|
|
499
|
+
method_lower=method_lower,
|
|
500
|
+
path=endpoint.path,
|
|
501
|
+
setup_code="",
|
|
502
|
+
request_body='"key": "value"',
|
|
503
|
+
assertions=""
|
|
504
|
+
)
|
|
505
|
+
else:
|
|
506
|
+
content = self.templates.ENDPOINT_TEST.format(
|
|
507
|
+
endpoint_name=endpoint_name,
|
|
508
|
+
method=method,
|
|
509
|
+
method_lower=method_lower,
|
|
510
|
+
path=endpoint.path,
|
|
511
|
+
setup_code="",
|
|
512
|
+
assertions=""
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
return GeneratedTest(
|
|
516
|
+
name=f"test_{endpoint_name}_{method_lower}",
|
|
517
|
+
description=f"Test {method} {endpoint.path}",
|
|
518
|
+
test_type="api",
|
|
519
|
+
file_path=f"test_{self._sanitize_name(metadata.name)}.py",
|
|
520
|
+
content=content,
|
|
521
|
+
component_name=metadata.name,
|
|
522
|
+
target_endpoint=endpoint.path,
|
|
523
|
+
assertions=[]
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
def _generate_query_test(
|
|
527
|
+
self,
|
|
528
|
+
metadata: ComponentMetadata,
|
|
529
|
+
query: DiscoveredQuery
|
|
530
|
+
) -> GeneratedTest:
|
|
531
|
+
"""Generate test for a database query"""
|
|
532
|
+
query_name = self._sanitize_name(query.name)
|
|
533
|
+
|
|
534
|
+
content = self.templates.QUERY_TEST.format(
|
|
535
|
+
query_name=query_name,
|
|
536
|
+
setup_code="# Set up test data" if query.query_type != "select" else "",
|
|
537
|
+
assertions=""
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
return GeneratedTest(
|
|
541
|
+
name=f"test_{query_name}_query",
|
|
542
|
+
description=f"Test {query.name} query",
|
|
543
|
+
test_type="integration",
|
|
544
|
+
file_path=f"test_{self._sanitize_name(metadata.name)}.py",
|
|
545
|
+
content=content,
|
|
546
|
+
component_name=metadata.name,
|
|
547
|
+
assertions=[]
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
def _write_test_files(self, suite: TestSuite, output_dir: str):
|
|
551
|
+
"""Write test suite to files"""
|
|
552
|
+
output_path = Path(output_dir)
|
|
553
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
554
|
+
|
|
555
|
+
# Group tests by file
|
|
556
|
+
file_tests: Dict[str, List[GeneratedTest]] = {}
|
|
557
|
+
for test in suite.tests:
|
|
558
|
+
if test.file_path not in file_tests:
|
|
559
|
+
file_tests[test.file_path] = []
|
|
560
|
+
file_tests[test.file_path].append(test)
|
|
561
|
+
|
|
562
|
+
# Write each file
|
|
563
|
+
for file_path, tests in file_tests.items():
|
|
564
|
+
full_path = output_path / file_path
|
|
565
|
+
content = self.templates.FILE_HEADER.format(
|
|
566
|
+
component_name=suite.component_name,
|
|
567
|
+
date=datetime.now().isoformat()
|
|
568
|
+
)
|
|
569
|
+
content += self.templates.FIXTURES
|
|
570
|
+
|
|
571
|
+
for test in tests:
|
|
572
|
+
content += test.content
|
|
573
|
+
|
|
574
|
+
full_path.write_text(content, encoding='utf-8')
|
|
575
|
+
logger.info(f"Generated test file: {full_path}")
|
|
576
|
+
|
|
577
|
+
def _write_project_tests(self, suites: List[TestSuite], output_dir: str):
|
|
578
|
+
"""Write all test suites for a project"""
|
|
579
|
+
output_path = Path(output_dir)
|
|
580
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
581
|
+
|
|
582
|
+
for suite in suites:
|
|
583
|
+
suite_path = output_path / f"test_{self._sanitize_name(suite.component_name)}.py"
|
|
584
|
+
|
|
585
|
+
content = self.templates.FILE_HEADER.format(
|
|
586
|
+
component_name=suite.component_name,
|
|
587
|
+
date=datetime.now().isoformat()
|
|
588
|
+
)
|
|
589
|
+
content += self.templates.FIXTURES
|
|
590
|
+
|
|
591
|
+
for test in suite.tests:
|
|
592
|
+
content += test.content
|
|
593
|
+
|
|
594
|
+
suite_path.write_text(content, encoding='utf-8')
|
|
595
|
+
logger.info(f"Generated test file: {suite_path}")
|
|
596
|
+
|
|
597
|
+
def _sanitize_name(self, name: str) -> str:
|
|
598
|
+
"""Sanitize a name for use in test function names"""
|
|
599
|
+
# Remove invalid characters
|
|
600
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_]', '_', name)
|
|
601
|
+
# Remove leading digits
|
|
602
|
+
sanitized = re.sub(r'^[0-9]+', '', sanitized)
|
|
603
|
+
# Convert to lowercase
|
|
604
|
+
sanitized = sanitized.lower()
|
|
605
|
+
# Remove consecutive underscores
|
|
606
|
+
sanitized = re.sub(r'_+', '_', sanitized)
|
|
607
|
+
# Remove trailing underscores
|
|
608
|
+
sanitized = sanitized.strip('_')
|
|
609
|
+
return sanitized or 'unnamed'
|
|
610
|
+
|
|
611
|
+
def get_test_coverage_estimate(
|
|
612
|
+
self,
|
|
613
|
+
project_id: int,
|
|
614
|
+
base_path: Optional[str] = None
|
|
615
|
+
) -> Dict[str, Any]:
|
|
616
|
+
"""
|
|
617
|
+
Estimate test coverage based on component analysis
|
|
618
|
+
|
|
619
|
+
Returns:
|
|
620
|
+
Dict with coverage estimates
|
|
621
|
+
"""
|
|
622
|
+
components = self.discovery.discover_components(project_id, base_path)
|
|
623
|
+
|
|
624
|
+
total_functions = 0
|
|
625
|
+
total_endpoints = 0
|
|
626
|
+
total_queries = 0
|
|
627
|
+
total_components = 0
|
|
628
|
+
|
|
629
|
+
for metadata in components:
|
|
630
|
+
if metadata.is_valid:
|
|
631
|
+
total_components += 1
|
|
632
|
+
total_functions += len(metadata.functions)
|
|
633
|
+
total_endpoints += len(metadata.endpoints)
|
|
634
|
+
total_queries += len(metadata.queries)
|
|
635
|
+
|
|
636
|
+
# Calculate potential test counts
|
|
637
|
+
potential_function_tests = total_functions
|
|
638
|
+
potential_endpoint_tests = total_endpoints
|
|
639
|
+
potential_query_tests = total_queries
|
|
640
|
+
potential_total = potential_function_tests + potential_endpoint_tests + potential_query_tests
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
"total_components": total_components,
|
|
644
|
+
"total_functions": total_functions,
|
|
645
|
+
"total_endpoints": total_endpoints,
|
|
646
|
+
"total_queries": total_queries,
|
|
647
|
+
"potential_tests": {
|
|
648
|
+
"function_tests": potential_function_tests,
|
|
649
|
+
"endpoint_tests": potential_endpoint_tests,
|
|
650
|
+
"query_tests": potential_query_tests,
|
|
651
|
+
"total": potential_total
|
|
652
|
+
},
|
|
653
|
+
"recommendation": self._get_test_recommendation(
|
|
654
|
+
total_functions, total_endpoints, total_queries
|
|
655
|
+
)
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
def _get_test_recommendation(
|
|
659
|
+
self,
|
|
660
|
+
functions: int,
|
|
661
|
+
endpoints: int,
|
|
662
|
+
queries: int
|
|
663
|
+
) -> str:
|
|
664
|
+
"""Get testing recommendation based on component analysis"""
|
|
665
|
+
if endpoints > 0:
|
|
666
|
+
return "Focus on API/integration tests for endpoints first"
|
|
667
|
+
elif functions > 0:
|
|
668
|
+
return "Focus on unit tests for functions"
|
|
669
|
+
elif queries > 0:
|
|
670
|
+
return "Focus on database integration tests"
|
|
671
|
+
else:
|
|
672
|
+
return "Add q:function or q:endpoint tags to components for better test coverage"
|
|
673
|
+
|
|
674
|
+
def update_test_results(
|
|
675
|
+
self,
|
|
676
|
+
component_id: int,
|
|
677
|
+
test_run_id: int,
|
|
678
|
+
test_results: List[Dict[str, Any]]
|
|
679
|
+
):
|
|
680
|
+
"""
|
|
681
|
+
Update component test records with results from a test run
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
component_id: Component ID
|
|
685
|
+
test_run_id: TestRun ID
|
|
686
|
+
test_results: List of test result dicts with name, status, duration, error
|
|
687
|
+
"""
|
|
688
|
+
# Get the component
|
|
689
|
+
component = self.db.query(Component).filter(
|
|
690
|
+
Component.id == component_id
|
|
691
|
+
).first()
|
|
692
|
+
|
|
693
|
+
if not component:
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
# Get all tests for this component
|
|
697
|
+
tests = self.db.query(ComponentTest).filter(
|
|
698
|
+
ComponentTest.component_id == component_id
|
|
699
|
+
).all()
|
|
700
|
+
|
|
701
|
+
tests_map = {t.test_name: t for t in tests}
|
|
702
|
+
|
|
703
|
+
passed = 0
|
|
704
|
+
failed = 0
|
|
705
|
+
|
|
706
|
+
for result in test_results:
|
|
707
|
+
test_name = result.get("name")
|
|
708
|
+
if test_name and test_name in tests_map:
|
|
709
|
+
test = tests_map[test_name]
|
|
710
|
+
test.last_run_id = test_run_id
|
|
711
|
+
test.last_status = result.get("status", "unknown")
|
|
712
|
+
test.last_run_at = datetime.now()
|
|
713
|
+
test.last_duration_seconds = result.get("duration")
|
|
714
|
+
test.last_error_message = result.get("error")
|
|
715
|
+
|
|
716
|
+
if test.last_status == "passed":
|
|
717
|
+
passed += 1
|
|
718
|
+
elif test.last_status in ["failed", "error"]:
|
|
719
|
+
failed += 1
|
|
720
|
+
|
|
721
|
+
# Update component counters
|
|
722
|
+
component.tests_passing = passed
|
|
723
|
+
component.tests_failing = failed
|
|
724
|
+
component.last_test_run_id = test_run_id
|
|
725
|
+
component.last_test_run_at = datetime.now()
|
|
726
|
+
|
|
727
|
+
self.db.commit()
|
|
728
|
+
logger.info(f"Updated test results for component {component.name}: {passed} passed, {failed} failed")
|
|
729
|
+
|
|
730
|
+
def get_component_test_summary(
|
|
731
|
+
self,
|
|
732
|
+
component_id: int
|
|
733
|
+
) -> Dict[str, Any]:
|
|
734
|
+
"""Get test summary for a component"""
|
|
735
|
+
component = self.db.query(Component).filter(
|
|
736
|
+
Component.id == component_id
|
|
737
|
+
).first()
|
|
738
|
+
|
|
739
|
+
if not component:
|
|
740
|
+
return {}
|
|
741
|
+
|
|
742
|
+
tests = self.db.query(ComponentTest).filter(
|
|
743
|
+
ComponentTest.component_id == component_id
|
|
744
|
+
).all()
|
|
745
|
+
|
|
746
|
+
by_status = {"passed": 0, "failed": 0, "pending": 0, "skipped": 0, "error": 0}
|
|
747
|
+
by_type = {"unit": 0, "api": 0, "integration": 0, "e2e": 0}
|
|
748
|
+
|
|
749
|
+
for test in tests:
|
|
750
|
+
status = test.last_status or "pending"
|
|
751
|
+
if status in by_status:
|
|
752
|
+
by_status[status] += 1
|
|
753
|
+
if test.test_type in by_type:
|
|
754
|
+
by_type[test.test_type] += 1
|
|
755
|
+
|
|
756
|
+
return {
|
|
757
|
+
"component_id": component_id,
|
|
758
|
+
"component_name": component.name,
|
|
759
|
+
"total_tests": len(tests),
|
|
760
|
+
"tests_passing": component.tests_passing,
|
|
761
|
+
"tests_failing": component.tests_failing,
|
|
762
|
+
"by_status": by_status,
|
|
763
|
+
"by_type": by_type,
|
|
764
|
+
"last_run_at": component.last_test_run_at.isoformat() if component.last_test_run_at else None,
|
|
765
|
+
"coverage": component.test_coverage if hasattr(component, 'test_coverage') else 0
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
# Singleton instance
|
|
770
|
+
_test_generator = None
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
def get_test_generator(db: Session = None) -> TestGeneratorService:
|
|
774
|
+
"""Get test generator service instance"""
|
|
775
|
+
if db:
|
|
776
|
+
return TestGeneratorService(db)
|
|
777
|
+
|
|
778
|
+
global _test_generator
|
|
779
|
+
if _test_generator is None:
|
|
780
|
+
from .database import SessionLocal
|
|
781
|
+
_test_generator = TestGeneratorService(SessionLocal())
|
|
782
|
+
return _test_generator
|