coding-client 1.21.2__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.
- coding_client/__init__.py +1 -0
- coding_client/api/__init__.py +19 -0
- coding_client/api/exception_handlers.py +158 -0
- coding_client/api/logging_setup.py +44 -0
- coding_client/api/metrics.py +40 -0
- coding_client/api/openapi_config.py +124 -0
- coding_client/api/request_logging.py +60 -0
- coding_client/api/validation.py +150 -0
- coding_client/auth/__init__.py +78 -0
- coding_client/auth/_default_tenant.py +132 -0
- coding_client/auth/_header_trust.py +98 -0
- coding_client/auth/_onbehalf.py +315 -0
- coding_client/auth/_trusted_networks.py +94 -0
- coding_client/auth/dependencies.py +61 -0
- coding_client/auth/middleware.py +445 -0
- coding_client/auth/models.py +83 -0
- coding_client/auth/outbound.py +137 -0
- coding_client/auth/rbac.py +21 -0
- coding_client/auth/rbac_coding.py +5 -0
- coding_client/auth/tenant.py +650 -0
- coding_client/auth/websocket.py +148 -0
- coding_client/clients/__init__.py +24 -0
- coding_client/clients/base.py +131 -0
- coding_client/clients/coding_ai.py +33 -0
- coding_client/clients/coding_analysis.py +36 -0
- coding_client/clients/coding_containers.py +45 -0
- coding_client/clients/coding_github.py +127 -0
- coding_client/clients/coding_realtime.py +127 -0
- coding_client/clients/coding_security.py +51 -0
- coding_client/clients/coding_testing.py +31 -0
- coding_client/clients/coding_workspaces.py +265 -0
- coding_client/config/__init__.py +12 -0
- coding_client/config/base_settings.py +61 -0
- coding_client/config/yaml_config_loader.py +172 -0
- coding_client/db/__init__.py +42 -0
- coding_client/db/db_types.py +122 -0
- coding_client/db/exceptions.py +59 -0
- coding_client/db/postgres_connection.py +572 -0
- coding_client/db/tenant_query_helper.py +417 -0
- coding_client/db_init.py +49 -0
- coding_client/exceptions/__init__.py +67 -0
- coding_client/exceptions/api_exceptions.py +395 -0
- coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
- coding_client/interfaces/__init__.py +22 -0
- coding_client/interfaces/cache_interface.py +179 -0
- coding_client/interfaces/container_interface.py +49 -0
- coding_client/interfaces/docker_interface.py +283 -0
- coding_client/interfaces/graph_interface.py +28 -0
- coding_client/interfaces/security_interface.py +72 -0
- coding_client/interfaces/webhook_interface.py +67 -0
- coding_client/llm/__init__.py +54 -0
- coding_client/llm/base_agent.py +195 -0
- coding_client/llm/context.py +121 -0
- coding_client/llm/responses.py +102 -0
- coding_client/llm/routing.py +82 -0
- coding_client/llm/settings.py +102 -0
- coding_client/models/__init__.py +62 -0
- coding_client/models/agent.py +38 -0
- coding_client/models/ai_test_failure.py +745 -0
- coding_client/models/analysis.py +330 -0
- coding_client/models/architecture.py +685 -0
- coding_client/models/bug.py +128 -0
- coding_client/models/code_graph.py +317 -0
- coding_client/models/container.py +241 -0
- coding_client/models/core.py +878 -0
- coding_client/models/docker_container.py +104 -0
- coding_client/models/github.py +293 -0
- coding_client/models/logging.py +29 -0
- coding_client/models/package_manager.py +257 -0
- coding_client/models/pr_validation.py +440 -0
- coding_client/models/pydantic_ai.py +395 -0
- coding_client/models/repo_scanner.py +249 -0
- coding_client/models/responses.py +431 -0
- coding_client/models/security.py +185 -0
- coding_client/models/static_analysis.py +508 -0
- coding_client/models/system.py +48 -0
- coding_client/models/test_executor.py +204 -0
- coding_client/models/testing.py +673 -0
- coding_client/models/testing_config.py +200 -0
- coding_client/models/validation.py +127 -0
- coding_client/models_init.py +272 -0
- coding_client/telemetry.py +195 -0
- coding_client/testing.py +180 -0
- coding_client/types/__init__.py +130 -0
- coding_client/types/constraint_types.py +276 -0
- coding_client/types/core_types.py +112 -0
- coding_client/types/state_machine.py +443 -0
- coding_client/utils/__init__.py +86 -0
- coding_client/utils/archive_utils.py +387 -0
- coding_client/utils/ast_debug.py +45 -0
- coding_client/utils/async_timeout.py +364 -0
- coding_client/utils/constants.py +518 -0
- coding_client/utils/content_processing.py +326 -0
- coding_client/utils/database.py +916 -0
- coding_client/utils/encoding_helper.py +222 -0
- coding_client/utils/enum_helpers.py +166 -0
- coding_client/utils/file_operations.py +177 -0
- coding_client/utils/filesystem_ops.py +528 -0
- coding_client/utils/http_client.py +562 -0
- coding_client/utils/path_utils.py +471 -0
- coding_client/utils/profiling.py +421 -0
- coding_client/utils/response_builder.py +240 -0
- coding_client/utils/retry_helper.py +712 -0
- coding_client/utils/security_validation.py +423 -0
- coding_client-1.21.2.dist-info/METADATA +234 -0
- coding_client-1.21.2.dist-info/RECORD +109 -0
- coding_client-1.21.2.dist-info/WHEEL +5 -0
- coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
- coding_client-1.21.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"""Testing models for container-based test execution and quality analysis.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive Pydantic models for the testing system that supports
|
|
4
|
+
multi-language test execution in containerized environments. It includes models for
|
|
5
|
+
test requests, results, quality analysis, and container-based execution with security
|
|
6
|
+
controls and resource management.
|
|
7
|
+
|
|
8
|
+
The testing system supports:
|
|
9
|
+
- Multi-language test execution (Python, JavaScript, TypeScript, Go, Rust, Java, C#, C++)
|
|
10
|
+
- Quality analysis with automated tool integration
|
|
11
|
+
- Resource limits and security controls
|
|
12
|
+
- Container-based isolation and execution
|
|
13
|
+
- Comprehensive result reporting with metadata
|
|
14
|
+
|
|
15
|
+
Key Model Categories:
|
|
16
|
+
Request Models: TestRequest, QualityRequest, QualityScanRequest
|
|
17
|
+
Response Models: TestResult, QualityResult, ContainerTestResult
|
|
18
|
+
Configuration Models: ResourceLimits, TestSecurityLevel, TestType
|
|
19
|
+
Factory Functions: Helper functions for creating model instances
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
```python
|
|
23
|
+
from testing_models import TestRequest, TestResult, TestSecurityLevel, TestType
|
|
24
|
+
|
|
25
|
+
# Create a test request
|
|
26
|
+
request = TestRequest(
|
|
27
|
+
code='def add(a, b): return a + b',
|
|
28
|
+
test_code='def test_add(): assert add(2, 3) == 5',
|
|
29
|
+
language="python",
|
|
30
|
+
test_type=TestType.UNIT,
|
|
31
|
+
security_level=TestSecurityLevel.HIGH,
|
|
32
|
+
run_quality_check=True,
|
|
33
|
+
timeout=30
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Process test result
|
|
37
|
+
result = TestResult(
|
|
38
|
+
test_id="test-123",
|
|
39
|
+
success=True,
|
|
40
|
+
stdout="Test passed",
|
|
41
|
+
stderr="",
|
|
42
|
+
exit_code=0,
|
|
43
|
+
execution_time=2.5,
|
|
44
|
+
created_at=datetime.now(timezone.utc),
|
|
45
|
+
language="python",
|
|
46
|
+
security_level=TestSecurityLevel.HIGH
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
from __future__ import annotations
|
|
52
|
+
|
|
53
|
+
from datetime import UTC, datetime
|
|
54
|
+
from enum import StrEnum
|
|
55
|
+
from typing import Any
|
|
56
|
+
from uuid import uuid7
|
|
57
|
+
|
|
58
|
+
from pydantic import (
|
|
59
|
+
BaseModel,
|
|
60
|
+
ConfigDict,
|
|
61
|
+
Field,
|
|
62
|
+
computed_field,
|
|
63
|
+
field_serializer,
|
|
64
|
+
field_validator,
|
|
65
|
+
model_validator,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class TestType(StrEnum):
|
|
70
|
+
"""Test execution types."""
|
|
71
|
+
|
|
72
|
+
UNIT = "unit"
|
|
73
|
+
INTEGRATION = "integration"
|
|
74
|
+
BENCHMARK = "benchmark"
|
|
75
|
+
COVERAGE = "coverage"
|
|
76
|
+
SECURITY = "security"
|
|
77
|
+
DOC = "doc"
|
|
78
|
+
CLIPPY = "clippy"
|
|
79
|
+
FMT = "fmt"
|
|
80
|
+
VET = "vet"
|
|
81
|
+
RACE = "race"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class ResourceLimits(BaseModel):
|
|
85
|
+
"""Resource limits for test execution."""
|
|
86
|
+
|
|
87
|
+
memory_mb: int | None = Field(None, ge=128, le=4096, description="Memory limit in MB")
|
|
88
|
+
cpu_cores: float | None = Field(None, ge=0.1, le=4.0, description="CPU cores limit")
|
|
89
|
+
timeout_seconds: int | None = Field(None, ge=1, le=3600, description="Timeout in seconds")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestSecurityLevel(StrEnum):
|
|
93
|
+
"""Security levels for test execution."""
|
|
94
|
+
|
|
95
|
+
LOW = "low" # Minimal security, maximum resources
|
|
96
|
+
MEDIUM = "medium" # Balanced security and performance
|
|
97
|
+
HIGH = "high" # Maximum security, minimal resources
|
|
98
|
+
CRITICAL = "critical" # Critical security level
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Backward compatibility alias
|
|
102
|
+
SecurityLevel = TestSecurityLevel
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestRequest(BaseModel):
|
|
106
|
+
"""Request model for test execution."""
|
|
107
|
+
|
|
108
|
+
__test__ = False # Not a pytest test class
|
|
109
|
+
|
|
110
|
+
model_config = ConfigDict(
|
|
111
|
+
validate_assignment=True,
|
|
112
|
+
extra="forbid",
|
|
113
|
+
str_strip_whitespace=True,
|
|
114
|
+
ignored_types=(property,), # Ignore computed fields in serialization
|
|
115
|
+
json_schema_extra={
|
|
116
|
+
"example": {
|
|
117
|
+
"code": "def add(a, b):\n return a + b",
|
|
118
|
+
"test_code": "def test_add():\n assert add(2, 3) == 5\n assert add(-1, 1) == 0",
|
|
119
|
+
"language": "python",
|
|
120
|
+
"container_name": "my-project-tests",
|
|
121
|
+
"test_type": "unit",
|
|
122
|
+
"security_level": "high",
|
|
123
|
+
"timeout": 30,
|
|
124
|
+
"run_quality_check": True,
|
|
125
|
+
"dependencies": {"pytest": "^7.0.0"},
|
|
126
|
+
"resource_limits": {"memory_mb": 512, "cpu_cores": 1.0},
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Core test data
|
|
132
|
+
code: str | None = Field(None, description="Code to test")
|
|
133
|
+
test_code: str | None = Field(None, description="Test code to execute")
|
|
134
|
+
language: str = Field(description="Programming language (python, javascript, typescript)")
|
|
135
|
+
|
|
136
|
+
# Execution parameters
|
|
137
|
+
timeout: int | None = Field(None, ge=1, le=300, description="Test timeout in seconds")
|
|
138
|
+
security_level: TestSecurityLevel = Field(
|
|
139
|
+
default=TestSecurityLevel.HIGH,
|
|
140
|
+
description="Security level (defaults to HIGH for security)",
|
|
141
|
+
)
|
|
142
|
+
with_hypothesis: bool = Field(default=False, description="Enable hypothesis testing (Python)")
|
|
143
|
+
|
|
144
|
+
# File-based testing
|
|
145
|
+
code_path: str | None = Field(None, description="Path to code file")
|
|
146
|
+
code_file_path: str | None = Field(None, description="Alias for code file path")
|
|
147
|
+
test_path: str | None = Field(None, description="Path to test file")
|
|
148
|
+
test_file_path: str | None = Field(None, description="Alias for test file path")
|
|
149
|
+
|
|
150
|
+
# Container preferences
|
|
151
|
+
container_name: str | None = Field(None, description="Container name for test execution")
|
|
152
|
+
|
|
153
|
+
# New fields for unification
|
|
154
|
+
test_type: TestType | None = Field(TestType.UNIT, description="Type of test to run")
|
|
155
|
+
dependencies: dict[str, str] = Field(default_factory=dict, description="Package dependencies")
|
|
156
|
+
resource_limits: ResourceLimits | None = Field(None, description="Resource limits")
|
|
157
|
+
|
|
158
|
+
# Quality integration
|
|
159
|
+
run_quality_check: bool = Field(False, description="Run quality analysis after test")
|
|
160
|
+
quality_tools: list[str] | None = Field(None, description="Specific quality tools to run")
|
|
161
|
+
|
|
162
|
+
@field_validator("language")
|
|
163
|
+
@classmethod
|
|
164
|
+
def validate_language(cls, v: str) -> str:
|
|
165
|
+
"""Validate supported languages."""
|
|
166
|
+
# Handle empty string
|
|
167
|
+
if not v or v.strip() == "":
|
|
168
|
+
raise ValueError("Language cannot be empty")
|
|
169
|
+
|
|
170
|
+
# Extended to support all 9 languages
|
|
171
|
+
supported = [
|
|
172
|
+
"python",
|
|
173
|
+
"javascript",
|
|
174
|
+
"typescript",
|
|
175
|
+
"nodejs",
|
|
176
|
+
"go",
|
|
177
|
+
"rust",
|
|
178
|
+
"java",
|
|
179
|
+
"csharp",
|
|
180
|
+
"cpp",
|
|
181
|
+
]
|
|
182
|
+
if v.lower() not in supported:
|
|
183
|
+
raise ValueError(f"Language must be one of: {supported}")
|
|
184
|
+
return v.lower()
|
|
185
|
+
|
|
186
|
+
@model_validator(mode="after")
|
|
187
|
+
def validate_test_request(self) -> TestRequest:
|
|
188
|
+
"""Validate cross-field dependencies."""
|
|
189
|
+
if not self.code and not self.code_path and not self.code_file_path:
|
|
190
|
+
raise ValueError("Either code or code_path/code_file_path must be provided")
|
|
191
|
+
if not self.test_code and not self.test_path and not self.test_file_path:
|
|
192
|
+
raise ValueError("Either test_code or test_path/test_file_path must be provided")
|
|
193
|
+
return self
|
|
194
|
+
|
|
195
|
+
# Computed fields using Pydantic v2
|
|
196
|
+
@computed_field
|
|
197
|
+
def code_file_path_resolved(self) -> str | None:
|
|
198
|
+
"""Get code file path from either field."""
|
|
199
|
+
return self.code_file_path or self.code_path
|
|
200
|
+
|
|
201
|
+
@computed_field
|
|
202
|
+
def test_file_path_resolved(self) -> str | None:
|
|
203
|
+
"""Get test file path from either field."""
|
|
204
|
+
return self.test_file_path or self.test_path
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class TestResult(BaseModel):
|
|
208
|
+
"""Result model for test execution."""
|
|
209
|
+
|
|
210
|
+
__test__ = False # Not a pytest test class
|
|
211
|
+
|
|
212
|
+
model_config = ConfigDict(
|
|
213
|
+
validate_assignment=True,
|
|
214
|
+
extra="forbid",
|
|
215
|
+
json_schema_serialization_defaults_required=True,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
# Execution metadata
|
|
219
|
+
test_id: str = Field(description="Unique test execution ID")
|
|
220
|
+
success: bool = Field(description="Whether tests passed")
|
|
221
|
+
|
|
222
|
+
# Output data
|
|
223
|
+
stdout: str = Field(default="", description="Standard output")
|
|
224
|
+
stderr: str = Field(default="", description="Standard error output")
|
|
225
|
+
exit_code: int = Field(description="Process exit code")
|
|
226
|
+
|
|
227
|
+
# Timing and context
|
|
228
|
+
execution_time: float = Field(description="Execution time in seconds")
|
|
229
|
+
created_at: datetime = Field(description="When test was executed")
|
|
230
|
+
|
|
231
|
+
# Request context
|
|
232
|
+
language: str = Field(description="Programming language used")
|
|
233
|
+
security_level: TestSecurityLevel = Field(description="Security level used")
|
|
234
|
+
|
|
235
|
+
# Optional additional data
|
|
236
|
+
coverage_data: dict[str, Any] | None = Field(None, description="Code coverage data")
|
|
237
|
+
test_metadata: dict[str, Any] | None = Field(None, description="Additional test metadata")
|
|
238
|
+
|
|
239
|
+
# New fields for unification
|
|
240
|
+
test_type: str | None = Field(None, description="Type of test executed")
|
|
241
|
+
container_name: str | None = Field(None, description="Container used for execution")
|
|
242
|
+
quality_report: QualityResult | None = Field(None, description="Quality analysis report")
|
|
243
|
+
error: str | None = Field(None, description="Error message if test failed")
|
|
244
|
+
|
|
245
|
+
@field_serializer("created_at")
|
|
246
|
+
def serialize_datetime(self, v: datetime | None) -> str | None:
|
|
247
|
+
if v is None:
|
|
248
|
+
return None
|
|
249
|
+
if v.tzinfo:
|
|
250
|
+
return v.isoformat()
|
|
251
|
+
return v.replace(tzinfo=UTC).isoformat()
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class QualityRequest(BaseModel):
|
|
255
|
+
"""Request model for quality analysis."""
|
|
256
|
+
|
|
257
|
+
model_config = ConfigDict(
|
|
258
|
+
validate_assignment=True,
|
|
259
|
+
extra="forbid",
|
|
260
|
+
str_strip_whitespace=True,
|
|
261
|
+
ignored_types=(property,), # Ignore computed fields in serialization
|
|
262
|
+
json_schema_extra={
|
|
263
|
+
"example": {
|
|
264
|
+
"code": "import os\nimport sys\n\ndef process_data(data):\n # TODO: implement processing\n pass",
|
|
265
|
+
"container_name": "my-python-project",
|
|
266
|
+
"language": "python",
|
|
267
|
+
"tools": ["ruff", "ty"],
|
|
268
|
+
"fix_issues": True,
|
|
269
|
+
"fail_on_issues": False,
|
|
270
|
+
"custom_rules": {"max_line_length": 100, "allow_todos": False},
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Container scoping - REQUIRED for container-based operations
|
|
276
|
+
container_name: str = Field(description="Container name for scoped quality analysis")
|
|
277
|
+
|
|
278
|
+
# Code to analyze
|
|
279
|
+
code: str | None = Field(None, description="Code content to analyze")
|
|
280
|
+
code_content: str | None = Field(None, description="Alias for code content")
|
|
281
|
+
code_path: str | None = Field(None, description="Path to code file")
|
|
282
|
+
language: str | None = Field(None, description="Programming language")
|
|
283
|
+
|
|
284
|
+
# Tool selection
|
|
285
|
+
tools: list[str] | None = Field(None, description="Specific tools to run")
|
|
286
|
+
|
|
287
|
+
# Analysis options
|
|
288
|
+
fix_issues: bool = Field(default=False, description="Automatically apply resolves")
|
|
289
|
+
auto_resolve: bool = Field(default=False, description="Automatically apply resolves (alias)")
|
|
290
|
+
fail_on_issues: bool = Field(default=False, description="Fail if issues found")
|
|
291
|
+
custom_rules: dict[str, Any] | None = Field(None, description="Custom rules configuration")
|
|
292
|
+
|
|
293
|
+
@field_validator("language")
|
|
294
|
+
@classmethod
|
|
295
|
+
def validate_language(cls, v: str | None) -> str | None:
|
|
296
|
+
"""Validate supported languages."""
|
|
297
|
+
if v is None:
|
|
298
|
+
return v
|
|
299
|
+
# Handle empty string
|
|
300
|
+
if v.strip() == "":
|
|
301
|
+
raise ValueError("Language cannot be empty")
|
|
302
|
+
supported = ["python", "javascript", "typescript", "nodejs"]
|
|
303
|
+
if v.lower() not in supported:
|
|
304
|
+
raise ValueError(f"Language must be one of: {supported}")
|
|
305
|
+
return v.lower()
|
|
306
|
+
|
|
307
|
+
@model_validator(mode="after")
|
|
308
|
+
def validate_quality_request(self) -> QualityRequest:
|
|
309
|
+
"""Validate that we have code to analyze."""
|
|
310
|
+
if not self.code and not self.code_content and not self.code_path:
|
|
311
|
+
raise ValueError("Either code, code_content, or code_path must be provided")
|
|
312
|
+
return self
|
|
313
|
+
|
|
314
|
+
# Computed field using Pydantic v2
|
|
315
|
+
@computed_field
|
|
316
|
+
def code_content_resolved(self) -> str | None:
|
|
317
|
+
"""Get code content from either field."""
|
|
318
|
+
return self.code_content or self.code
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
class QualityScanRequest(BaseModel):
|
|
322
|
+
"""Request model for container-wide quality scanning."""
|
|
323
|
+
|
|
324
|
+
model_config = ConfigDict(
|
|
325
|
+
validate_assignment=True,
|
|
326
|
+
extra="forbid",
|
|
327
|
+
str_strip_whitespace=True,
|
|
328
|
+
json_schema_extra={
|
|
329
|
+
"example": {
|
|
330
|
+
"container_name": "my-fastapi-backend",
|
|
331
|
+
"scan_path": "/app/data",
|
|
332
|
+
"recursive": True,
|
|
333
|
+
"include_patterns": ["*.py", "*.js", "*.ts"],
|
|
334
|
+
"exclude_patterns": [
|
|
335
|
+
"__pycache__",
|
|
336
|
+
"node_modules",
|
|
337
|
+
"*.min.js",
|
|
338
|
+
],
|
|
339
|
+
"max_files": 500,
|
|
340
|
+
"language": "python",
|
|
341
|
+
"tools": ["ruff", "ty", "bandit"],
|
|
342
|
+
"fix_issues": False,
|
|
343
|
+
"parallel_analysis": True,
|
|
344
|
+
"deep_analysis": True,
|
|
345
|
+
"fail_on_critical": True,
|
|
346
|
+
"min_quality_score": 80.0,
|
|
347
|
+
"timeout": 1800,
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Container to scan
|
|
353
|
+
container_name: str = Field(description="Container name to scan")
|
|
354
|
+
|
|
355
|
+
# Scan parameters
|
|
356
|
+
scan_path: str | None = Field(
|
|
357
|
+
default="/app/data", description="Base path to scan within container"
|
|
358
|
+
)
|
|
359
|
+
recursive: bool = Field(default=True, description="Scan directories recursively")
|
|
360
|
+
|
|
361
|
+
# File filtering
|
|
362
|
+
include_patterns: list[str] = Field(
|
|
363
|
+
default_factory=lambda: ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx"],
|
|
364
|
+
description="File patterns to include in scan",
|
|
365
|
+
)
|
|
366
|
+
exclude_patterns: list[str] = Field(
|
|
367
|
+
default_factory=lambda: [
|
|
368
|
+
"__pycache__",
|
|
369
|
+
"*.pyc",
|
|
370
|
+
".git",
|
|
371
|
+
"node_modules",
|
|
372
|
+
"*.min.js",
|
|
373
|
+
],
|
|
374
|
+
description="Patterns to exclude from scan",
|
|
375
|
+
)
|
|
376
|
+
max_files: int = Field(default=500, ge=1, le=2000, description="Maximum files to scan")
|
|
377
|
+
|
|
378
|
+
# Language and tool selection
|
|
379
|
+
language: str | None = Field(None, description="Primary language (auto-detect if None)")
|
|
380
|
+
tools: list[str] | None = Field(
|
|
381
|
+
None, description="Specific tools to run (all available if None)"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Analysis options
|
|
385
|
+
fix_issues: bool = Field(default=False, description="Attempt to apply resolves automatically")
|
|
386
|
+
parallel_analysis: bool = Field(
|
|
387
|
+
default=True, description="Run tools in parallel for performance"
|
|
388
|
+
)
|
|
389
|
+
deep_analysis: bool = Field(default=False, description="Enable comprehensive analysis mode")
|
|
390
|
+
|
|
391
|
+
# Quality thresholds
|
|
392
|
+
fail_on_critical: bool = Field(default=False, description="Fail scan if critical issues found")
|
|
393
|
+
min_quality_score: float | None = Field(
|
|
394
|
+
None, ge=0.0, le=100.0, description="Minimum acceptable quality score"
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
# Custom configuration
|
|
398
|
+
custom_rules: dict[str, Any] | None = Field(None, description="Custom rules configuration")
|
|
399
|
+
timeout: int | None = Field(default=3600, ge=30, le=7200, description="Scan timeout in seconds")
|
|
400
|
+
|
|
401
|
+
@field_validator("language")
|
|
402
|
+
@classmethod
|
|
403
|
+
def validate_language(cls, v: str | None) -> str | None:
|
|
404
|
+
"""Validate supported languages."""
|
|
405
|
+
if v is None:
|
|
406
|
+
return v
|
|
407
|
+
# Handle empty string
|
|
408
|
+
if v.strip() == "":
|
|
409
|
+
raise ValueError("Language cannot be empty")
|
|
410
|
+
supported = ["python", "javascript", "typescript", "nodejs"]
|
|
411
|
+
if v.lower() not in supported:
|
|
412
|
+
raise ValueError(f"Language must be one of: {supported}")
|
|
413
|
+
return v.lower()
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class QualityIssue(BaseModel):
|
|
417
|
+
"""Individual quality issue found by analysis tools."""
|
|
418
|
+
|
|
419
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid", str_strip_whitespace=True)
|
|
420
|
+
|
|
421
|
+
tool: str = Field(description="Tool that found the issue")
|
|
422
|
+
severity: str = Field(description="Issue severity (critical, error, warning, info)")
|
|
423
|
+
message: str = Field(description="Issue description")
|
|
424
|
+
file_path: str = Field(description="File path where issue was found")
|
|
425
|
+
line_number: int | None = Field(None, description="Line number (if applicable)")
|
|
426
|
+
column_number: int | None = Field(None, description="Column number (if applicable)")
|
|
427
|
+
rule_id: str | None = Field(None, description="Rule ID that triggered issue")
|
|
428
|
+
|
|
429
|
+
# Optional resolve suggestions
|
|
430
|
+
suggested_fix: str | None = Field(None, description="Suggested resolve for the issue")
|
|
431
|
+
fix_confidence: float | None = Field(None, ge=0.0, le=1.0, description="Confidence in resolve")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class QualityResult(BaseModel):
|
|
435
|
+
"""Result model for quality analysis."""
|
|
436
|
+
|
|
437
|
+
model_config = ConfigDict(
|
|
438
|
+
validate_assignment=True,
|
|
439
|
+
extra="forbid",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# Analysis metadata
|
|
443
|
+
analysis_id: str = Field(description="Unique analysis ID")
|
|
444
|
+
success: bool = Field(default=True, description="Whether analysis completed successfully")
|
|
445
|
+
overall_score: float = Field(ge=0.0, le=100.0, description="Overall quality score (0-100)")
|
|
446
|
+
total_issues: int = Field(ge=0, description="Total number of issues found")
|
|
447
|
+
|
|
448
|
+
# Detailed results
|
|
449
|
+
issues: list[QualityIssue] = Field(default=[], description="List of issues found")
|
|
450
|
+
tools_results: dict[str, dict[str, Any]] = Field(default={}, description="Results by tool")
|
|
451
|
+
|
|
452
|
+
# Execution context
|
|
453
|
+
execution_time: float = Field(description="Analysis time in seconds")
|
|
454
|
+
created_at: datetime = Field(description="When analysis was performed")
|
|
455
|
+
language: str = Field(description="Programming language analyzed")
|
|
456
|
+
|
|
457
|
+
# Error handling
|
|
458
|
+
error: str | None = Field(None, description="Error message if analysis failed")
|
|
459
|
+
|
|
460
|
+
@field_serializer("created_at")
|
|
461
|
+
def serialize_datetime(self, v: datetime | None) -> str | None:
|
|
462
|
+
if v is None:
|
|
463
|
+
return None
|
|
464
|
+
if v.tzinfo:
|
|
465
|
+
return v.isoformat()
|
|
466
|
+
return v.replace(tzinfo=UTC).isoformat()
|
|
467
|
+
|
|
468
|
+
@model_validator(mode="after")
|
|
469
|
+
def validate_quality_result(self) -> QualityResult:
|
|
470
|
+
"""Validate quality result consistency."""
|
|
471
|
+
# Auto-correct missing error message for failed analysis
|
|
472
|
+
if not self.success and (not self.error or self.error.strip() == ""):
|
|
473
|
+
self.error = "Analysis failed without specific error details"
|
|
474
|
+
|
|
475
|
+
if self.total_issues != len(self.issues):
|
|
476
|
+
# Auto-correct for consistency
|
|
477
|
+
self.total_issues = len(self.issues)
|
|
478
|
+
return self
|
|
479
|
+
|
|
480
|
+
@classmethod
|
|
481
|
+
def create_failed_result(
|
|
482
|
+
cls,
|
|
483
|
+
analysis_id: str | None = None,
|
|
484
|
+
error_message: str | None = None,
|
|
485
|
+
language: str = "unknown",
|
|
486
|
+
execution_time: float = 0.0,
|
|
487
|
+
) -> QualityResult:
|
|
488
|
+
"""Factory method to create failed QualityResult with safe error handling."""
|
|
489
|
+
# Ensure analysis_id is provided
|
|
490
|
+
if not analysis_id:
|
|
491
|
+
analysis_id = str(uuid7())
|
|
492
|
+
|
|
493
|
+
# Ensure error message is never None or empty
|
|
494
|
+
if not error_message or error_message.strip() == "":
|
|
495
|
+
error_message = "Quality analysis failed - no specific error details available"
|
|
496
|
+
|
|
497
|
+
return cls(
|
|
498
|
+
analysis_id=analysis_id,
|
|
499
|
+
success=False,
|
|
500
|
+
overall_score=0.0,
|
|
501
|
+
total_issues=0,
|
|
502
|
+
issues=[],
|
|
503
|
+
tools_results={},
|
|
504
|
+
execution_time=execution_time,
|
|
505
|
+
created_at=datetime.now(UTC),
|
|
506
|
+
language=language,
|
|
507
|
+
error=error_message,
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class CommandResult(BaseModel):
|
|
512
|
+
"""Result of command execution in container."""
|
|
513
|
+
|
|
514
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
515
|
+
|
|
516
|
+
exit_code: int = Field(description="Command exit code")
|
|
517
|
+
stdout: str = Field(default="", description="Standard output")
|
|
518
|
+
stderr: str = Field(default="", description="Standard error output")
|
|
519
|
+
execution_time: float = Field(description="Execution time in seconds")
|
|
520
|
+
timeout: bool = Field(default=False, description="Whether command timed out")
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class ContainerTestResult(BaseModel):
|
|
524
|
+
"""Extended test result with container-specific information."""
|
|
525
|
+
|
|
526
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
527
|
+
|
|
528
|
+
# Base test result
|
|
529
|
+
test_result: TestResult = Field(description="Core test execution result")
|
|
530
|
+
|
|
531
|
+
# Container context
|
|
532
|
+
container_name: str = Field(description="Container name used for execution")
|
|
533
|
+
container_image: str = Field(description="Container image used")
|
|
534
|
+
|
|
535
|
+
# Resource usage
|
|
536
|
+
memory_used: int | None = Field(None, description="Peak memory usage in MB")
|
|
537
|
+
cpu_time: float | None = Field(None, description="CPU time used in seconds")
|
|
538
|
+
|
|
539
|
+
# Security context
|
|
540
|
+
security_violations: list[str] = Field(default=[], description="Security violations detected")
|
|
541
|
+
sandbox_effective: bool = Field(default=True, description="Whether sandbox was effective")
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class TestExecutionStatus(StrEnum):
|
|
545
|
+
"""Status of test execution."""
|
|
546
|
+
|
|
547
|
+
PENDING = "pending"
|
|
548
|
+
RUNNING = "running"
|
|
549
|
+
COMPLETED = "completed"
|
|
550
|
+
FAILED = "failed"
|
|
551
|
+
TIMEOUT = "timeout"
|
|
552
|
+
CANCELLED = "cancelled"
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class TestExecutionInfo(BaseModel):
|
|
556
|
+
"""Information about a test execution in progress."""
|
|
557
|
+
|
|
558
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
559
|
+
|
|
560
|
+
execution_id: str = Field(description="Unique execution ID")
|
|
561
|
+
status: TestExecutionStatus = Field(description="Current execution status")
|
|
562
|
+
started_at: datetime = Field(description="When execution started")
|
|
563
|
+
updated_at: datetime = Field(description="Last status update")
|
|
564
|
+
|
|
565
|
+
# Progress information
|
|
566
|
+
progress_percentage: float | None = Field(
|
|
567
|
+
None, ge=0.0, le=100.0, description="Execution progress"
|
|
568
|
+
)
|
|
569
|
+
current_step: str | None = Field(None, description="Current execution step")
|
|
570
|
+
|
|
571
|
+
# Results (available when completed)
|
|
572
|
+
result: TestResult | None = Field(None, description="Test result when completed")
|
|
573
|
+
error_message: str | None = Field(None, description="Error message if failed")
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
# Factory functions for easy model creation
|
|
577
|
+
def create_test_request(
|
|
578
|
+
code: str,
|
|
579
|
+
language: str,
|
|
580
|
+
container_name: str,
|
|
581
|
+
test_code: str | None = None,
|
|
582
|
+
security_level: TestSecurityLevel = TestSecurityLevel.MEDIUM,
|
|
583
|
+
timeout: int | None = None,
|
|
584
|
+
code_path: str | None = None,
|
|
585
|
+
code_file_path: str | None = None,
|
|
586
|
+
test_path: str | None = None,
|
|
587
|
+
test_file_path: str | None = None,
|
|
588
|
+
test_type: TestType | None = None,
|
|
589
|
+
dependencies: dict[str, str] | None = None,
|
|
590
|
+
resource_limits: ResourceLimits | None = None,
|
|
591
|
+
run_quality_check: bool = False,
|
|
592
|
+
quality_tools: list[str] | None = None,
|
|
593
|
+
) -> TestRequest:
|
|
594
|
+
"""Factory function to create test request."""
|
|
595
|
+
return TestRequest(
|
|
596
|
+
code=code,
|
|
597
|
+
test_code=test_code,
|
|
598
|
+
language=language,
|
|
599
|
+
security_level=security_level,
|
|
600
|
+
timeout=timeout,
|
|
601
|
+
code_path=code_path,
|
|
602
|
+
code_file_path=code_file_path,
|
|
603
|
+
test_path=test_path,
|
|
604
|
+
test_file_path=test_file_path,
|
|
605
|
+
container_name=container_name,
|
|
606
|
+
test_type=test_type,
|
|
607
|
+
dependencies=dependencies or {},
|
|
608
|
+
resource_limits=resource_limits,
|
|
609
|
+
run_quality_check=run_quality_check,
|
|
610
|
+
quality_tools=quality_tools,
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def create_quality_request(
|
|
615
|
+
container_name: str,
|
|
616
|
+
code: str | None = None,
|
|
617
|
+
code_path: str | None = None,
|
|
618
|
+
language: str | None = None,
|
|
619
|
+
tools: list[str] | None = None,
|
|
620
|
+
) -> QualityRequest:
|
|
621
|
+
"""Factory function to create quality request.
|
|
622
|
+
|
|
623
|
+
Note: This creates a QualityRequest (for single file analysis),
|
|
624
|
+
not a QualityScanRequest (for container-wide scanning).
|
|
625
|
+
"""
|
|
626
|
+
return QualityRequest(
|
|
627
|
+
container_name=container_name,
|
|
628
|
+
code=code,
|
|
629
|
+
code_path=code_path,
|
|
630
|
+
language=language,
|
|
631
|
+
tools=tools,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def create_test_result(
|
|
636
|
+
success: bool,
|
|
637
|
+
container_name: str,
|
|
638
|
+
stdout: str = "",
|
|
639
|
+
stderr: str = "",
|
|
640
|
+
exit_code: int = 0,
|
|
641
|
+
execution_time: float = 0.0,
|
|
642
|
+
language: str = "python",
|
|
643
|
+
security_level: TestSecurityLevel = TestSecurityLevel.MEDIUM,
|
|
644
|
+
test_id: str | None = None,
|
|
645
|
+
created_at: datetime | None = None,
|
|
646
|
+
coverage_data: dict[str, Any] | None = None,
|
|
647
|
+
test_metadata: dict[str, Any] | None = None,
|
|
648
|
+
test_type: str | None = None,
|
|
649
|
+
quality_report: QualityResult | None = None,
|
|
650
|
+
error: str | None = None,
|
|
651
|
+
) -> TestResult:
|
|
652
|
+
"""Factory function to create test result."""
|
|
653
|
+
return TestResult(
|
|
654
|
+
test_id=test_id or str(uuid7()),
|
|
655
|
+
success=success,
|
|
656
|
+
stdout=stdout,
|
|
657
|
+
stderr=stderr,
|
|
658
|
+
exit_code=exit_code,
|
|
659
|
+
execution_time=execution_time,
|
|
660
|
+
created_at=created_at or datetime.now(UTC),
|
|
661
|
+
coverage_data=coverage_data,
|
|
662
|
+
test_metadata=test_metadata,
|
|
663
|
+
language=language,
|
|
664
|
+
security_level=security_level,
|
|
665
|
+
test_type=test_type,
|
|
666
|
+
container_name=container_name,
|
|
667
|
+
quality_report=quality_report,
|
|
668
|
+
error=error,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
# Rebuild model to ensure proper forward reference resolution
|
|
673
|
+
QualityResult.model_rebuild()
|