ai-testing-swarm 0.1.11__tar.gz → 0.1.13__tar.gz
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.
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/PKG-INFO +1 -1
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/pyproject.toml +1 -1
- ai_testing_swarm-0.1.13/src/ai_testing_swarm/__init__.py +1 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/llm_reasoning_agent.py +66 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/release_gate_agent.py +9 -1
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/orchestrator.py +15 -1
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/PKG-INFO +1 -1
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/SOURCES.txt +1 -0
- ai_testing_swarm-0.1.13/tests/test_policy_expected_negatives.py +34 -0
- ai_testing_swarm-0.1.11/src/ai_testing_swarm/__init__.py +0 -1
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/README.md +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/setup.cfg +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/__init__.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/execution_agent.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/learning_agent.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/test_planner_agent.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/test_writer_agent.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/ui_agent.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/cli.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/__init__.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/api_client.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/config.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/curl_parser.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/openai_client.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/openapi_loader.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/safety.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/reporting/__init__.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/reporting/report_writer.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/dependency_links.txt +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/entry_points.txt +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/top_level.txt +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/tests/test_openapi_loader.py +0 -0
- {ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/tests/test_swarm_api.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.13"
|
|
@@ -73,13 +73,33 @@ class LLMReasoningAgent:
|
|
|
73
73
|
# =========================================================
|
|
74
74
|
# 3️⃣ HTTP-LEVEL HARD RULES (NO AI)
|
|
75
75
|
# =========================================================
|
|
76
|
+
# Method misuse tests: 404/405 are expected (endpoint not available for that method)
|
|
77
|
+
if status_code in (404, 405):
|
|
78
|
+
if (
|
|
79
|
+
isinstance(test_name, str)
|
|
80
|
+
and test_name.startswith("wrong_method_")
|
|
81
|
+
) or (mutation and mutation.get("strategy") == "method_misuse"):
|
|
82
|
+
return {
|
|
83
|
+
"type": "method_not_allowed" if status_code == 405 else "not_found",
|
|
84
|
+
"confidence": 1.0,
|
|
85
|
+
"explanation": f"HTTP {status_code} for wrong-method negative test (expected)"
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
if status_code == 405:
|
|
89
|
+
# 405 outside method-misuse tests can still be informative
|
|
77
90
|
return {
|
|
78
91
|
"type": "method_not_allowed",
|
|
79
92
|
"confidence": 1.0,
|
|
80
93
|
"explanation": "HTTP 405 Method Not Allowed"
|
|
81
94
|
}
|
|
82
95
|
|
|
96
|
+
if status_code == 404:
|
|
97
|
+
return {
|
|
98
|
+
"type": "not_found",
|
|
99
|
+
"confidence": 0.9,
|
|
100
|
+
"explanation": "HTTP 404 Not Found"
|
|
101
|
+
}
|
|
102
|
+
|
|
83
103
|
if status_code in (401, 403):
|
|
84
104
|
return {
|
|
85
105
|
"type": "auth_issue",
|
|
@@ -100,6 +120,30 @@ class LLMReasoningAgent:
|
|
|
100
120
|
if mutation:
|
|
101
121
|
strategy = mutation.get("strategy")
|
|
102
122
|
|
|
123
|
+
# Header tampering / content negotiation tests
|
|
124
|
+
if status_code == 406 and strategy == "headers":
|
|
125
|
+
return {
|
|
126
|
+
"type": "content_negotiation",
|
|
127
|
+
"confidence": 1.0,
|
|
128
|
+
"explanation": "406 Not Acceptable after Accept/header mutation (expected)"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Header tampering accepted (2xx) can be risky depending on the mutation
|
|
132
|
+
if 200 <= status_code < 300 and strategy == "headers":
|
|
133
|
+
return {
|
|
134
|
+
"type": "headers_accepted",
|
|
135
|
+
"confidence": 0.8,
|
|
136
|
+
"explanation": "Header mutation still returned 2xx (review if expected)"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# Wrong-method negative test accepted => risk
|
|
140
|
+
if 200 <= status_code < 300 and strategy == "method_misuse":
|
|
141
|
+
return {
|
|
142
|
+
"type": "method_risk",
|
|
143
|
+
"confidence": 1.0,
|
|
144
|
+
"explanation": "Wrong HTTP method was accepted with 2xx (unexpected)"
|
|
145
|
+
}
|
|
146
|
+
|
|
103
147
|
if status_code == 400 and strategy == "missing_param":
|
|
104
148
|
return {
|
|
105
149
|
"type": "missing_param",
|
|
@@ -107,6 +151,14 @@ class LLMReasoningAgent:
|
|
|
107
151
|
"explanation": "400 response after required parameter removal"
|
|
108
152
|
}
|
|
109
153
|
|
|
154
|
+
# Missing param accepted (2xx) is often weak validation (risky)
|
|
155
|
+
if 200 <= status_code < 300 and strategy == "missing_param":
|
|
156
|
+
return {
|
|
157
|
+
"type": "missing_param_accepted",
|
|
158
|
+
"confidence": 1.0,
|
|
159
|
+
"explanation": "Parameter removal still returned 2xx (potential missing validation)"
|
|
160
|
+
}
|
|
161
|
+
|
|
110
162
|
if status_code == 400 and strategy == "null_param":
|
|
111
163
|
return {
|
|
112
164
|
"type": "missing_param",
|
|
@@ -114,6 +166,13 @@ class LLMReasoningAgent:
|
|
|
114
166
|
"explanation": "400 response after nullifying parameter"
|
|
115
167
|
}
|
|
116
168
|
|
|
169
|
+
if 200 <= status_code < 300 and strategy == "null_param":
|
|
170
|
+
return {
|
|
171
|
+
"type": "null_param_accepted",
|
|
172
|
+
"confidence": 1.0,
|
|
173
|
+
"explanation": "Nullified parameter still returned 2xx (potential weak validation)"
|
|
174
|
+
}
|
|
175
|
+
|
|
117
176
|
if status_code == 400 and strategy == "invalid_param":
|
|
118
177
|
return {
|
|
119
178
|
"type": "invalid_param",
|
|
@@ -121,6 +180,13 @@ class LLMReasoningAgent:
|
|
|
121
180
|
"explanation": "400 response after invalid parameter mutation"
|
|
122
181
|
}
|
|
123
182
|
|
|
183
|
+
if 200 <= status_code < 300 and strategy == "invalid_param":
|
|
184
|
+
return {
|
|
185
|
+
"type": "invalid_param_accepted",
|
|
186
|
+
"confidence": 1.0,
|
|
187
|
+
"explanation": "Invalid parameter still returned 2xx (potential weak validation)"
|
|
188
|
+
}
|
|
189
|
+
|
|
124
190
|
# ✅ FIX: security payload blocked → SAFE
|
|
125
191
|
if status_code >= 400 and status_code < 500 and strategy == "security":
|
|
126
192
|
return {
|
|
@@ -90,12 +90,17 @@ class ReleaseGateAgent:
|
|
|
90
90
|
"infra", # infra / network / 5xx
|
|
91
91
|
"security_risk", # malicious payload succeeded (2xx/3xx)
|
|
92
92
|
"server_error",
|
|
93
|
-
|
|
93
|
+
# NOTE: method_not_allowed is expected for negative tests (wrong method)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
# ⚠️ Ambiguous behavior (release with caution)
|
|
97
97
|
RISKY_FAILURES = {
|
|
98
98
|
"unknown",
|
|
99
|
+
"missing_param_accepted",
|
|
100
|
+
"null_param_accepted",
|
|
101
|
+
"invalid_param_accepted",
|
|
102
|
+
"headers_accepted",
|
|
103
|
+
"method_risk",
|
|
99
104
|
}
|
|
100
105
|
|
|
101
106
|
# ✅ EXPECTED & HEALTHY system behavior
|
|
@@ -104,6 +109,9 @@ class ReleaseGateAgent:
|
|
|
104
109
|
"missing_param",
|
|
105
110
|
"invalid_param",
|
|
106
111
|
"security", # 🔥 IMPORTANT: security blocked = SAFE
|
|
112
|
+
"method_not_allowed",
|
|
113
|
+
"not_found",
|
|
114
|
+
"content_negotiation",
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
def decide(self, results: list) -> str:
|
|
@@ -14,6 +14,18 @@ EXPECTED_FAILURES = {
|
|
|
14
14
|
"missing_param",
|
|
15
15
|
"invalid_param",
|
|
16
16
|
"security",
|
|
17
|
+
"method_not_allowed",
|
|
18
|
+
"not_found",
|
|
19
|
+
"content_negotiation",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
RISKY_FAILURES = {
|
|
23
|
+
"unknown",
|
|
24
|
+
"missing_param_accepted",
|
|
25
|
+
"null_param_accepted",
|
|
26
|
+
"invalid_param_accepted",
|
|
27
|
+
"headers_accepted",
|
|
28
|
+
"method_risk",
|
|
17
29
|
}
|
|
18
30
|
|
|
19
31
|
class SwarmOrchestrator:
|
|
@@ -61,7 +73,9 @@ class SwarmOrchestrator:
|
|
|
61
73
|
"confidence": classification.get("confidence", 1.0),
|
|
62
74
|
"failure_type": classification.get("type"),
|
|
63
75
|
"status": (
|
|
64
|
-
"PASSED" if classification.get("type") in EXPECTED_FAILURES
|
|
76
|
+
"PASSED" if classification.get("type") in EXPECTED_FAILURES
|
|
77
|
+
else "RISK" if classification.get("type") in RISKY_FAILURES
|
|
78
|
+
else "FAILED"
|
|
65
79
|
),
|
|
66
80
|
})
|
|
67
81
|
# Optional learning step
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from ai_testing_swarm.agents.llm_reasoning_agent import LLMReasoningAgent
|
|
2
|
+
from ai_testing_swarm.agents.release_gate_agent import ReleaseGateAgent
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_wrong_method_405_is_expected_not_blocking():
|
|
6
|
+
reasoner = LLMReasoningAgent()
|
|
7
|
+
|
|
8
|
+
exec_result = {
|
|
9
|
+
"name": "wrong_method_POST",
|
|
10
|
+
"mutation": {"strategy": "method_misuse"},
|
|
11
|
+
"response": {"status_code": 405, "elapsed_ms": 10, "body_snippet": ""},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
cls = reasoner.reason(exec_result)
|
|
15
|
+
assert cls["type"] == "method_not_allowed"
|
|
16
|
+
|
|
17
|
+
# Gate should not reject release if happy path passes and only expected negatives exist
|
|
18
|
+
gate = ReleaseGateAgent()
|
|
19
|
+
decision = gate.decide([
|
|
20
|
+
{"name": "happy_path", "failure_type": "success", "response": {"status_code": 200}},
|
|
21
|
+
{"name": "wrong_method_POST", "failure_type": cls["type"], "response": {"status_code": 405}},
|
|
22
|
+
])
|
|
23
|
+
assert decision in ("APPROVE_RELEASE", "APPROVE_RELEASE_WITH_RISKS")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_accept_header_406_is_expected():
|
|
27
|
+
reasoner = LLMReasoningAgent()
|
|
28
|
+
exec_result = {
|
|
29
|
+
"name": "headers_accept_text",
|
|
30
|
+
"mutation": {"strategy": "headers"},
|
|
31
|
+
"response": {"status_code": 406, "elapsed_ms": 10, "body_snippet": ""},
|
|
32
|
+
}
|
|
33
|
+
cls = reasoner.reason(exec_result)
|
|
34
|
+
assert cls["type"] == "content_negotiation"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.11"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/execution_agent.py
RENAMED
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/learning_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/agents/test_writer_agent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/curl_parser.py
RENAMED
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/openai_client.py
RENAMED
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/core/openapi_loader.py
RENAMED
|
File without changes
|
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/reporting/__init__.py
RENAMED
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm/reporting/report_writer.py
RENAMED
|
File without changes
|
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{ai_testing_swarm-0.1.11 → ai_testing_swarm-0.1.13}/src/ai_testing_swarm.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|