stoobly-agent 1.10.2__py3-none-any.whl → 1.10.3__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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/application_http_request_handler.py +1 -1
- stoobly_agent/app/cli/helpers/validations.py +1 -1
- stoobly_agent/app/cli/scaffold/app.py +1 -1
- stoobly_agent/app/cli/scaffold/service_dependency.py +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml +1 -1
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
- stoobly_agent/app/proxy/mock/eval_fixtures_service.py +34 -11
- stoobly_agent/app/proxy/mock/eval_request_service.py +1 -1
- stoobly_agent/app/proxy/utils/allowed_request_service.py +2 -2
- stoobly_agent/lib/cache.py +1 -1
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/mitmproxy/request_facade_test.py +48 -8
- stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py +467 -21
- {stoobly_agent-1.10.2.dist-info → stoobly_agent-1.10.3.dist-info}/METADATA +1 -1
- {stoobly_agent-1.10.2.dist-info → stoobly_agent-1.10.3.dist-info}/RECORD +19 -19
- {stoobly_agent-1.10.2.dist-info → stoobly_agent-1.10.3.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.10.2.dist-info → stoobly_agent-1.10.3.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-1.10.2.dist-info → stoobly_agent-1.10.3.dist-info}/licenses/LICENSE +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.10.
|
2
|
+
VERSION = '1.10.3'
|
@@ -134,7 +134,7 @@ class ApplicationHTTPRequestHandler(SimpleHTTPRequestHandler):
|
|
134
134
|
for endpoint_handler in ROUTES[method]:
|
135
135
|
path = endpoint_handler[0]
|
136
136
|
|
137
|
-
matches = self.path == path if isinstance(path, str) else bool(re.
|
137
|
+
matches = self.path == path if isinstance(path, str) else bool(re.fullmatch(path, self.path))
|
138
138
|
|
139
139
|
if matches:
|
140
140
|
handler = endpoint_handler[1]
|
@@ -126,7 +126,7 @@ def validate_aliases(validations, **kwargs) -> Union[Alias, None]:
|
|
126
126
|
handle_missing_alias(parsed_validation, kwargs.get('format'))
|
127
127
|
|
128
128
|
if parsed_validation['value'] != None:
|
129
|
-
if not re.
|
129
|
+
if not re.fullmatch(parsed_validation['value'], aliases_map[name]):
|
130
130
|
handle_invalid_alias(parsed_validation, aliases_map[name], kwargs.get('format'))
|
131
131
|
|
132
132
|
def filter_response(res, status: int):
|
@@ -111,7 +111,7 @@ class App():
|
|
111
111
|
|
112
112
|
# Skip files that match the ignore list pattern, use regex
|
113
113
|
for ignore_pattern in ignore:
|
114
|
-
if re.
|
114
|
+
if re.fullmatch(os.path.join(src, ignore_pattern), src_file_path):
|
115
115
|
ignored = True
|
116
116
|
break
|
117
117
|
|
@@ -37,7 +37,7 @@ class DockerImage:
|
|
37
37
|
(?::(?P<tag>[^@]+))? # optional :tag
|
38
38
|
(?:@(?P<digest>.+))? # optional @digest
|
39
39
|
"""
|
40
|
-
match = re.
|
40
|
+
match = re.fullmatch(pattern, ref, re.VERBOSE)
|
41
41
|
if not match:
|
42
42
|
raise ValueError(f"Invalid Docker image reference: {ref}")
|
43
43
|
|
@@ -170,7 +170,7 @@ class MitmproxyRequestFacade(Request):
|
|
170
170
|
pattern = rewrite_rule.pattern
|
171
171
|
|
172
172
|
try:
|
173
|
-
url_matches = re.
|
173
|
+
url_matches = re.fullmatch(pattern, self.url)
|
174
174
|
except re.error as e:
|
175
175
|
Logger.instance().error(f"RegExp error '{e}' for {pattern}")
|
176
176
|
return False
|
@@ -58,12 +58,9 @@ def eval_fixtures(request: MitmproxyRequest, **options: MockOptions) -> Union[Re
|
|
58
58
|
continue
|
59
59
|
|
60
60
|
# Try to find the file in this directory
|
61
|
-
|
61
|
+
fixture_path = os.path.join(dir_path_config['path'], request_path.lstrip('/'))
|
62
62
|
if request.headers.get('accept'):
|
63
|
-
fixture_path = __guess_file_path(
|
64
|
-
|
65
|
-
if not fixture_path:
|
66
|
-
fixture_path = _fixture_path
|
63
|
+
fixture_path = __guess_file_path(fixture_path, request.headers['accept'])
|
67
64
|
|
68
65
|
if os.path.isfile(fixture_path):
|
69
66
|
break
|
@@ -74,15 +71,31 @@ def eval_fixtures(request: MitmproxyRequest, **options: MockOptions) -> Union[Re
|
|
74
71
|
return
|
75
72
|
else:
|
76
73
|
fixture_path = fixture.get('path')
|
77
|
-
if not fixture_path
|
74
|
+
if not fixture_path:
|
78
75
|
return
|
79
76
|
|
77
|
+
if os.path.isdir(fixture_path):
|
78
|
+
request_path = request.path
|
79
|
+
match = re.match(fixture.get('path_pattern', request_path), request_path)
|
80
|
+
|
81
|
+
if not match or match.end() == len(request_path):
|
82
|
+
sub_path = 'index'
|
83
|
+
else:
|
84
|
+
sub_path = request_path[match.end():]
|
85
|
+
|
86
|
+
fixture_path = os.path.join(fixture_path, sub_path.lstrip('/'))
|
87
|
+
if request.headers.get('accept'):
|
88
|
+
fixture_path = __guess_file_path(fixture_path, request.headers['accept'])
|
89
|
+
|
90
|
+
if not os.path.isfile(fixture_path):
|
91
|
+
return
|
92
|
+
|
80
93
|
_headers = fixture.get('headers')
|
81
94
|
headers = CaseInsensitiveDict(_headers if isinstance(_headers, dict) else {})
|
82
95
|
|
83
96
|
if fixture.get('status_code'):
|
84
97
|
status_code = fixture.get('status_code')
|
85
|
-
|
98
|
+
|
86
99
|
with open(fixture_path, 'rb') as fp:
|
87
100
|
response = Response()
|
88
101
|
|
@@ -158,13 +171,13 @@ def __guess_content_type(file_path):
|
|
158
171
|
return
|
159
172
|
return mimetypes.types_map.get(file_extension)
|
160
173
|
|
161
|
-
def __guess_file_path(file_path, content_type):
|
174
|
+
def __guess_file_path(file_path: str, content_type):
|
162
175
|
file_extension = os.path.splitext(file_path)[1]
|
163
176
|
if file_extension:
|
164
177
|
return file_path
|
165
178
|
|
166
179
|
if not content_type:
|
167
|
-
return
|
180
|
+
return file_path
|
168
181
|
|
169
182
|
content_types = __parse_accept_header(content_type)
|
170
183
|
|
@@ -178,6 +191,8 @@ def __guess_file_path(file_path, content_type):
|
|
178
191
|
if os.path.isfile(_file_path):
|
179
192
|
return _file_path
|
180
193
|
|
194
|
+
return file_path
|
195
|
+
|
181
196
|
def __find_fixture_for_request(request: MitmproxyRequest, fixtures: dict, method: str):
|
182
197
|
"""Find a fixture for the given request in the provided fixtures."""
|
183
198
|
if not fixtures:
|
@@ -193,7 +208,7 @@ def __find_fixture_in_routes(fixtures: dict, method: str, request_path: str):
|
|
193
208
|
return None
|
194
209
|
|
195
210
|
for path_pattern in routes:
|
196
|
-
if not re.
|
211
|
+
if not re.fullmatch(path_pattern, request_path):
|
197
212
|
continue
|
198
213
|
|
199
214
|
fixture = routes[path_pattern]
|
@@ -203,6 +218,7 @@ def __find_fixture_in_routes(fixtures: dict, method: str, request_path: str):
|
|
203
218
|
path = fixture.get('path')
|
204
219
|
|
205
220
|
if path:
|
221
|
+
fixture['path_pattern'] = path_pattern
|
206
222
|
return fixture
|
207
223
|
|
208
224
|
return None
|
@@ -211,6 +227,9 @@ def __choose_highest_priority_content_type(accept_header: str) -> Optional[str]:
|
|
211
227
|
if not accept_header:
|
212
228
|
return None
|
213
229
|
|
230
|
+
if accept_header == '*/*':
|
231
|
+
return 'text/plain'
|
232
|
+
|
214
233
|
types = []
|
215
234
|
for part in accept_header.split(","):
|
216
235
|
media_range = part.strip()
|
@@ -234,9 +253,13 @@ def __choose_highest_priority_content_type(accept_header: str) -> Optional[str]:
|
|
234
253
|
return types[0][0] if types else None
|
235
254
|
|
236
255
|
def __origin_matches(pattern: str, request_origin: str) -> bool:
|
237
|
-
return bool(re.
|
256
|
+
return bool(re.fullmatch(pattern, request_origin))
|
238
257
|
|
239
258
|
def __parse_accept_header(accept_header):
|
259
|
+
# In the case accept_header is */*, default to html and json file types
|
260
|
+
if accept_header == '*/*':
|
261
|
+
return ['text/html', 'application/json']
|
262
|
+
|
240
263
|
types = []
|
241
264
|
for item in accept_header.split(","):
|
242
265
|
parts = item.split(";")
|
@@ -202,7 +202,7 @@ def __filter_by_match_rules(request: MitmproxyRequest, match_rules: List[MatchRu
|
|
202
202
|
|
203
203
|
def __matches(url: str, pattern: str):
|
204
204
|
try:
|
205
|
-
return re.
|
205
|
+
return re.fullmatch(pattern, url)
|
206
206
|
except re.error as e:
|
207
207
|
Logger.instance().error(f"RegExp error '{e}' for {pattern}")
|
208
208
|
return False
|
@@ -80,7 +80,7 @@ def __include(request: MitmproxyRequest, patterns: List[str]) -> bool:
|
|
80
80
|
|
81
81
|
for pattern in patterns:
|
82
82
|
try:
|
83
|
-
if re.
|
83
|
+
if re.fullmatch(pattern, request.url):
|
84
84
|
return True
|
85
85
|
except re.error as e:
|
86
86
|
Logger.instance(LOG_ID).error(f"RegExp error '{e}' for {pattern}")
|
@@ -94,7 +94,7 @@ def __exclude(request: MitmproxyRequest, patterns: List[str]) -> bool:
|
|
94
94
|
|
95
95
|
for pattern in patterns:
|
96
96
|
try:
|
97
|
-
if re.
|
97
|
+
if re.fullmatch(pattern, request.url):
|
98
98
|
return True
|
99
99
|
except re.error as e:
|
100
100
|
Logger.instance(LOG_ID).error(f"RegExp error '{e}' for {pattern}")
|
stoobly_agent/lib/cache.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.10.
|
1
|
+
1.10.2
|
@@ -27,6 +27,24 @@ def mitmproxy_get_request():
|
|
27
27
|
now + 1,
|
28
28
|
)
|
29
29
|
|
30
|
+
@pytest.fixture
|
31
|
+
def mitmproxy_get_request_details():
|
32
|
+
now = time()
|
33
|
+
return MitmproxyRequest(
|
34
|
+
'stoobly.com',
|
35
|
+
443,
|
36
|
+
b'GET',
|
37
|
+
b'https',
|
38
|
+
b'stoobly.com:443',
|
39
|
+
b'/requests/1?project_id=1',
|
40
|
+
b'HTTP/1.1',
|
41
|
+
Headers(**{'access-token': b'123'}),
|
42
|
+
b'',
|
43
|
+
Headers(), # Trailers
|
44
|
+
now,
|
45
|
+
now + 1,
|
46
|
+
)
|
47
|
+
|
30
48
|
@pytest.fixture
|
31
49
|
def mitmproxy_post_request():
|
32
50
|
now = time()
|
@@ -45,6 +63,28 @@ def mitmproxy_post_request():
|
|
45
63
|
now + 1,
|
46
64
|
)
|
47
65
|
|
66
|
+
class TestDoesNotRewrite():
|
67
|
+
|
68
|
+
def test_does_not_rewrite_header(self, mitmproxy_get_request_details: MitmproxyRequest):
|
69
|
+
parameter_rule = {
|
70
|
+
'modes': [intercept_mode.RECORD],
|
71
|
+
'name': 'access-token',
|
72
|
+
'type': request_component.HEADER,
|
73
|
+
'value': '',
|
74
|
+
}
|
75
|
+
rewrite_rule = RewriteRule({
|
76
|
+
'methods': ['GET'],
|
77
|
+
'pattern': '.*?/requests',
|
78
|
+
'parameter_rules': [parameter_rule]
|
79
|
+
})
|
80
|
+
|
81
|
+
facade = MitmproxyRequestFacade(mitmproxy_get_request_details)
|
82
|
+
facade.with_parameter_rules([rewrite_rule])
|
83
|
+
facade.rewrite()
|
84
|
+
|
85
|
+
headers = facade.headers
|
86
|
+
assert headers['access-token'] != ''
|
87
|
+
|
48
88
|
class TestRewriteParams():
|
49
89
|
|
50
90
|
def test_rewrites_header(self, mitmproxy_get_request: MitmproxyRequest):
|
@@ -56,7 +96,7 @@ class TestRewriteParams():
|
|
56
96
|
}
|
57
97
|
rewrite_rule = RewriteRule({
|
58
98
|
'methods': ['GET'],
|
59
|
-
'pattern': '.*?/requests',
|
99
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
60
100
|
'parameter_rules': [parameter_rule]
|
61
101
|
})
|
62
102
|
|
@@ -76,7 +116,7 @@ class TestRewriteParams():
|
|
76
116
|
}
|
77
117
|
rewrite_rule = RewriteRule({
|
78
118
|
'methods': ['GET'],
|
79
|
-
'pattern': '.*?/requests',
|
119
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
80
120
|
'parameter_rules': [parameter_rule]
|
81
121
|
})
|
82
122
|
|
@@ -96,7 +136,7 @@ class TestRewriteParams():
|
|
96
136
|
}
|
97
137
|
rewrite_rule = RewriteRule({
|
98
138
|
'methods': ['POST'],
|
99
|
-
'pattern': '.*?/users',
|
139
|
+
'pattern': '.*?/users(?:\?.*)?',
|
100
140
|
'parameter_rules': [parameter_rule]
|
101
141
|
})
|
102
142
|
|
@@ -118,7 +158,7 @@ class TestRewriteUrl():
|
|
118
158
|
}
|
119
159
|
rewrite_rule = RewriteRule({
|
120
160
|
'methods': ['GET'],
|
121
|
-
'pattern': '.*?/requests',
|
161
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
122
162
|
'url_rules': [url_rule]
|
123
163
|
})
|
124
164
|
|
@@ -139,7 +179,7 @@ class TestRewriteUrl():
|
|
139
179
|
}
|
140
180
|
rewrite_rule = RewriteRule({
|
141
181
|
'methods': ['GET'],
|
142
|
-
'pattern': '.*?/requests',
|
182
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
143
183
|
'url_rules': [url_rule]
|
144
184
|
})
|
145
185
|
|
@@ -162,7 +202,7 @@ class TestRewriteUrl():
|
|
162
202
|
}
|
163
203
|
rewrite_rule = RewriteRule({
|
164
204
|
'methods': ['GET'],
|
165
|
-
'pattern': '.*?/requests',
|
205
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
166
206
|
'url_rules': [url_rule]
|
167
207
|
})
|
168
208
|
|
@@ -185,7 +225,7 @@ class TestRewriteUrl():
|
|
185
225
|
}
|
186
226
|
rewrite_rule = RewriteRule({
|
187
227
|
'methods': ['GET'],
|
188
|
-
'pattern': '.*?/requests',
|
228
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
189
229
|
'url_rules': [url_rule]
|
190
230
|
})
|
191
231
|
|
@@ -215,7 +255,7 @@ class TestRewriteUrl():
|
|
215
255
|
}
|
216
256
|
rewrite_rule = RewriteRule({
|
217
257
|
'methods': ['GET'],
|
218
|
-
'pattern': '.*?/requests',
|
258
|
+
'pattern': '.*?/requests(?:\?.*)?',
|
219
259
|
'url_rules': [url_rule1, url_rule2]
|
220
260
|
})
|
221
261
|
|
@@ -51,10 +51,18 @@ class TestEvalFixturesService():
|
|
51
51
|
|
52
52
|
return Request.last()
|
53
53
|
|
54
|
+
@pytest.fixture(scope='class')
|
55
|
+
def default_file_contents(self):
|
56
|
+
return b'Default'
|
57
|
+
|
54
58
|
@pytest.fixture(scope='class')
|
55
59
|
def not_found_file_contents(self):
|
56
60
|
return b'Not Found'
|
57
61
|
|
62
|
+
@pytest.fixture(scope='class')
|
63
|
+
def user_file_contents(self):
|
64
|
+
return b'{"id": 1, "name": "John Doe"}'
|
65
|
+
|
58
66
|
@pytest.fixture(scope='class')
|
59
67
|
def public_directory(self):
|
60
68
|
tmp_dir_path = DataDir.instance().tmp_dir_path
|
@@ -64,14 +72,28 @@ class TestEvalFixturesService():
|
|
64
72
|
return public_dir_path
|
65
73
|
|
66
74
|
@pytest.fixture(autouse=True, scope='class')
|
67
|
-
def not_found_file_path(self, public_directory: str, not_found_file_contents:
|
75
|
+
def not_found_file_path(self, public_directory: str, not_found_file_contents: bytes):
|
68
76
|
path = os.path.join(public_directory, '404.html')
|
69
77
|
with open(path, 'wb') as fp:
|
70
78
|
fp.write(not_found_file_contents)
|
71
79
|
return path
|
72
80
|
|
81
|
+
@pytest.fixture(autouse=True, scope='class')
|
82
|
+
def user_file_path(self, public_directory: str, user_file_contents: bytes):
|
83
|
+
path = os.path.join(public_directory, 'user.json')
|
84
|
+
with open(path, 'wb') as fp:
|
85
|
+
fp.write(user_file_contents)
|
86
|
+
return path
|
87
|
+
|
88
|
+
@pytest.fixture(autouse=True, scope='class')
|
89
|
+
def default_file_path(self, public_directory: str, default_file_contents: bytes):
|
90
|
+
path = os.path.join(public_directory, 'index.html')
|
91
|
+
with open(path, 'wb') as fp:
|
92
|
+
fp.write(default_file_contents)
|
93
|
+
return path
|
94
|
+
|
73
95
|
@pytest.fixture(scope='class')
|
74
|
-
def response_fixtures(self, request_method: str, not_found_file_path: str) -> Fixtures:
|
96
|
+
def response_fixtures(self, public_directory: str, request_method: str, not_found_file_path: str) -> Fixtures:
|
75
97
|
fixtures = {}
|
76
98
|
fixtures[request_method] = {
|
77
99
|
'/404.html': {
|
@@ -80,6 +102,9 @@ class TestEvalFixturesService():
|
|
80
102
|
},
|
81
103
|
'path': not_found_file_path,
|
82
104
|
'status_code': 404,
|
105
|
+
},
|
106
|
+
'/.*?': {
|
107
|
+
'path': public_directory
|
83
108
|
}
|
84
109
|
}
|
85
110
|
return fixtures
|
@@ -95,14 +120,31 @@ class TestEvalFixturesService():
|
|
95
120
|
fixtures_file = os.path.join(tmp_dir_path, 'test_response_fixtures.yml')
|
96
121
|
|
97
122
|
with open(fixtures_file, 'w') as f:
|
98
|
-
yaml.dump(response_fixtures, f)
|
99
|
-
|
123
|
+
yaml.dump(response_fixtures, f, sort_keys=False)
|
124
|
+
|
100
125
|
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures_path=fixtures_file)
|
101
126
|
assert res != None
|
102
127
|
return res
|
103
128
|
|
129
|
+
@pytest.fixture()
|
130
|
+
def default_fixtures_response(self, mitmproxy_request: MitmproxyRequest, response_fixtures: Fixtures):
|
131
|
+
mitmproxy_request.path = '/'
|
132
|
+
mitmproxy_request.headers['accept'] = '*/*'
|
133
|
+
|
134
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures=response_fixtures)
|
135
|
+
assert res != None
|
136
|
+
return res
|
137
|
+
|
138
|
+
@pytest.fixture()
|
139
|
+
def user_fixtures_response(self, mitmproxy_request: MitmproxyRequest, response_fixtures: Fixtures):
|
140
|
+
mitmproxy_request.path = '/user.json'
|
141
|
+
mitmproxy_request.headers['accept'] = 'application/json'
|
142
|
+
res: requests.Response = eval_fixtures(mitmproxy_request, response_fixtures=response_fixtures)
|
143
|
+
assert res != None
|
144
|
+
return res
|
145
|
+
|
104
146
|
def test_it_sets_response(
|
105
|
-
self, fixtures_response: requests.Response, not_found_file_contents:
|
147
|
+
self, fixtures_response: requests.Response, not_found_file_contents: bytes
|
106
148
|
):
|
107
149
|
assert fixtures_response.raw.read() == not_found_file_contents
|
108
150
|
|
@@ -113,6 +155,12 @@ class TestEvalFixturesService():
|
|
113
155
|
def test_it_sets_status_code(self, fixtures_response: requests.Response):
|
114
156
|
assert fixtures_response.status_code == 404
|
115
157
|
|
158
|
+
def test_fixture_directory(self, default_fixtures_response: requests.Response, default_file_contents: bytes):
|
159
|
+
assert default_fixtures_response.raw.read() == default_file_contents
|
160
|
+
|
161
|
+
def test_it_sets_user_response(self, user_fixtures_response: requests.Response, user_file_contents: bytes):
|
162
|
+
assert user_fixtures_response.raw.read() == user_file_contents
|
163
|
+
|
116
164
|
class TestPublicDirectory():
|
117
165
|
@pytest.fixture(scope='class')
|
118
166
|
def request_method(self):
|
@@ -460,6 +508,404 @@ class TestEvalFixturesService():
|
|
460
508
|
assert res is not None
|
461
509
|
assert res.raw.read() == b'Hostname Port Content'
|
462
510
|
|
511
|
+
class TestErrorHandling():
|
512
|
+
"""Test error handling and edge cases."""
|
513
|
+
|
514
|
+
@pytest.fixture(scope='class')
|
515
|
+
def request_method(self):
|
516
|
+
return 'GET'
|
517
|
+
|
518
|
+
@pytest.fixture(scope='class')
|
519
|
+
def request_url(self):
|
520
|
+
return 'https://example.com/test'
|
521
|
+
|
522
|
+
@pytest.fixture(scope='class')
|
523
|
+
def created_request(self, settings: Settings, request_method: str, request_url: str):
|
524
|
+
status = RequestBuilder(
|
525
|
+
method=request_method,
|
526
|
+
request_headers={'accept': 'application/json'},
|
527
|
+
response_body='',
|
528
|
+
status_code=200,
|
529
|
+
url=request_url,
|
530
|
+
).with_settings(settings).build()[1]
|
531
|
+
assert status == 200
|
532
|
+
return Request.last()
|
533
|
+
|
534
|
+
@pytest.fixture()
|
535
|
+
def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
|
536
|
+
return MitmproxyRequestAdapter(created_request).adapt()
|
537
|
+
|
538
|
+
def test_missing_path_pattern_in_fixture(self, mitmproxy_request: MitmproxyRequest):
|
539
|
+
"""Test fixture without path_pattern key - should handle KeyError gracefully."""
|
540
|
+
# Create fixture without path_pattern
|
541
|
+
fixture_without_pattern = {
|
542
|
+
'GET': {
|
543
|
+
'/test': {
|
544
|
+
'path': '/tmp/nonexistent_dir' # Directory that doesn't exist
|
545
|
+
}
|
546
|
+
}
|
547
|
+
}
|
548
|
+
|
549
|
+
# This should not raise a KeyError when accessing fixture['path_pattern']
|
550
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture_without_pattern)
|
551
|
+
# Should return None since directory doesn't exist
|
552
|
+
assert res is None
|
553
|
+
|
554
|
+
def test_nonexistent_fixture_file(self, mitmproxy_request: MitmproxyRequest):
|
555
|
+
"""Test fixture pointing to nonexistent file."""
|
556
|
+
fixture = {
|
557
|
+
'GET': {
|
558
|
+
'/test': {
|
559
|
+
'path': '/tmp/nonexistent_file.json'
|
560
|
+
}
|
561
|
+
}
|
562
|
+
}
|
563
|
+
|
564
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
565
|
+
assert res is None
|
566
|
+
|
567
|
+
def test_nonexistent_public_directory(self, mitmproxy_request: MitmproxyRequest):
|
568
|
+
"""Test public directory that doesn't exist."""
|
569
|
+
res = eval_fixtures(mitmproxy_request, public_directory_path='/tmp/nonexistent_directory')
|
570
|
+
assert res is None
|
571
|
+
|
572
|
+
def test_invalid_yaml_in_response_fixtures_path(self, mitmproxy_request: MitmproxyRequest):
|
573
|
+
"""Test handling of invalid YAML in response fixtures file."""
|
574
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
575
|
+
invalid_yaml_file = os.path.join(tmp_dir_path, 'invalid.yml')
|
576
|
+
|
577
|
+
# Create file with invalid YAML
|
578
|
+
with open(invalid_yaml_file, 'w') as f:
|
579
|
+
f.write('invalid: yaml: content: [')
|
580
|
+
|
581
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures_path=invalid_yaml_file)
|
582
|
+
assert res is None
|
583
|
+
|
584
|
+
def test_empty_response_fixtures(self, mitmproxy_request: MitmproxyRequest):
|
585
|
+
"""Test with empty response fixtures."""
|
586
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures={})
|
587
|
+
assert res is None
|
588
|
+
|
589
|
+
def test_none_response_fixtures(self, mitmproxy_request: MitmproxyRequest):
|
590
|
+
"""Test with None response fixtures."""
|
591
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=None)
|
592
|
+
assert res is None
|
593
|
+
|
594
|
+
def test_malformed_fixture_structure(self, mitmproxy_request: MitmproxyRequest):
|
595
|
+
"""Test fixture with malformed structure."""
|
596
|
+
malformed_fixture = {
|
597
|
+
'GET': 'not_a_dict' # Should be a dict
|
598
|
+
}
|
599
|
+
|
600
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=malformed_fixture)
|
601
|
+
assert res is None
|
602
|
+
|
603
|
+
def test_fixture_without_path(self, mitmproxy_request: MitmproxyRequest):
|
604
|
+
"""Test fixture without path key."""
|
605
|
+
fixture_without_path = {
|
606
|
+
'GET': {
|
607
|
+
'/test': {
|
608
|
+
'status_code': 200
|
609
|
+
# Missing 'path' key
|
610
|
+
}
|
611
|
+
}
|
612
|
+
}
|
613
|
+
|
614
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture_without_path)
|
615
|
+
assert res is None
|
616
|
+
|
617
|
+
def test_directory_fixture_without_matching_subpath(self, mitmproxy_request: MitmproxyRequest):
|
618
|
+
"""Test directory fixture when no matching subpath file exists."""
|
619
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
620
|
+
test_dir = os.path.join(tmp_dir_path, 'test_dir')
|
621
|
+
os.makedirs(test_dir, exist_ok=True)
|
622
|
+
|
623
|
+
# Don't create any files in the directory
|
624
|
+
|
625
|
+
fixture = {
|
626
|
+
'GET': {
|
627
|
+
'/test': {
|
628
|
+
'path': test_dir
|
629
|
+
}
|
630
|
+
}
|
631
|
+
}
|
632
|
+
|
633
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
634
|
+
assert res is None
|
635
|
+
|
636
|
+
class TestCustomHeaders():
|
637
|
+
"""Test custom header handling."""
|
638
|
+
|
639
|
+
@pytest.fixture(scope='class')
|
640
|
+
def request_method(self):
|
641
|
+
return 'GET'
|
642
|
+
|
643
|
+
@pytest.fixture(scope='class')
|
644
|
+
def request_url(self):
|
645
|
+
return 'https://example.com/test'
|
646
|
+
|
647
|
+
@pytest.fixture(scope='class')
|
648
|
+
def test_file_contents(self):
|
649
|
+
return b'Test content'
|
650
|
+
|
651
|
+
@pytest.fixture(scope='class')
|
652
|
+
def test_file_path(self, test_file_contents: bytes):
|
653
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
654
|
+
file_path = os.path.join(tmp_dir_path, 'custom_headers_test.txt')
|
655
|
+
with open(file_path, 'wb') as f:
|
656
|
+
f.write(test_file_contents)
|
657
|
+
return file_path
|
658
|
+
|
659
|
+
@pytest.fixture(scope='class')
|
660
|
+
def created_request(self, settings: Settings, request_method: str, request_url: str):
|
661
|
+
from stoobly_agent.config.constants.custom_headers import MOCK_FIXTURE_PATH
|
662
|
+
status = RequestBuilder(
|
663
|
+
method=request_method,
|
664
|
+
request_headers={MOCK_FIXTURE_PATH: '/custom/path'},
|
665
|
+
response_body='',
|
666
|
+
status_code=200,
|
667
|
+
url=request_url,
|
668
|
+
).with_settings(settings).build()[1]
|
669
|
+
assert status == 200
|
670
|
+
return Request.last()
|
671
|
+
|
672
|
+
@pytest.fixture()
|
673
|
+
def mitmproxy_request(self, created_request: Request, test_file_path: str) -> MitmproxyRequest:
|
674
|
+
from stoobly_agent.config.constants.custom_headers import MOCK_FIXTURE_PATH
|
675
|
+
request = MitmproxyRequestAdapter(created_request).adapt()
|
676
|
+
# Override the custom header to point to our test file
|
677
|
+
request.headers[MOCK_FIXTURE_PATH] = test_file_path
|
678
|
+
return request
|
679
|
+
|
680
|
+
def test_custom_fixture_path_header(self, mitmproxy_request: MitmproxyRequest, test_file_contents: bytes):
|
681
|
+
"""Test fixture path specified via custom header."""
|
682
|
+
res = eval_fixtures(mitmproxy_request)
|
683
|
+
assert res is not None
|
684
|
+
assert res.raw.read() == test_file_contents
|
685
|
+
|
686
|
+
def test_custom_fixture_path_header_nonexistent_file(self, mitmproxy_request: MitmproxyRequest):
|
687
|
+
"""Test custom fixture path header pointing to nonexistent file."""
|
688
|
+
from stoobly_agent.config.constants.custom_headers import MOCK_FIXTURE_PATH
|
689
|
+
mitmproxy_request.headers[MOCK_FIXTURE_PATH] = '/tmp/nonexistent_file.txt'
|
690
|
+
|
691
|
+
res = eval_fixtures(mitmproxy_request)
|
692
|
+
assert res is None
|
693
|
+
|
694
|
+
class TestContentTypeGuessing():
|
695
|
+
"""Test content type guessing and file extension handling."""
|
696
|
+
|
697
|
+
@pytest.fixture(scope='class')
|
698
|
+
def request_method(self):
|
699
|
+
return 'GET'
|
700
|
+
|
701
|
+
@pytest.fixture(scope='class')
|
702
|
+
def request_url(self):
|
703
|
+
return 'https://example.com/api/data'
|
704
|
+
|
705
|
+
@pytest.fixture(scope='class')
|
706
|
+
def created_request(self, settings: Settings, request_method: str, request_url: str):
|
707
|
+
status = RequestBuilder(
|
708
|
+
method=request_method,
|
709
|
+
request_headers={'accept': 'application/json,text/html;q=0.9,*/*;q=0.8'},
|
710
|
+
response_body='',
|
711
|
+
status_code=200,
|
712
|
+
url=request_url,
|
713
|
+
).with_settings(settings).build()[1]
|
714
|
+
assert status == 200
|
715
|
+
return Request.last()
|
716
|
+
|
717
|
+
@pytest.fixture()
|
718
|
+
def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
|
719
|
+
return MitmproxyRequestAdapter(created_request).adapt()
|
720
|
+
|
721
|
+
def test_json_file_content_type(self, mitmproxy_request: MitmproxyRequest):
|
722
|
+
"""Test that JSON files get correct content type."""
|
723
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
724
|
+
json_file = os.path.join(tmp_dir_path, 'test.json')
|
725
|
+
|
726
|
+
with open(json_file, 'w') as f:
|
727
|
+
json.dump({'test': 'data'}, f)
|
728
|
+
|
729
|
+
fixture = {
|
730
|
+
'GET': {
|
731
|
+
'/api/data': {
|
732
|
+
'path': json_file
|
733
|
+
}
|
734
|
+
}
|
735
|
+
}
|
736
|
+
|
737
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
738
|
+
assert res is not None
|
739
|
+
assert res.headers.get('content-type') == 'application/json'
|
740
|
+
|
741
|
+
def test_html_file_content_type(self, mitmproxy_request: MitmproxyRequest):
|
742
|
+
"""Test that HTML files get correct content type."""
|
743
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
744
|
+
html_file = os.path.join(tmp_dir_path, 'test.html')
|
745
|
+
|
746
|
+
with open(html_file, 'w') as f:
|
747
|
+
f.write('<html><body>Test</body></html>')
|
748
|
+
|
749
|
+
fixture = {
|
750
|
+
'GET': {
|
751
|
+
'/api/data': {
|
752
|
+
'path': html_file
|
753
|
+
}
|
754
|
+
}
|
755
|
+
}
|
756
|
+
|
757
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
758
|
+
assert res is not None
|
759
|
+
assert res.headers.get('content-type') == 'text/html'
|
760
|
+
|
761
|
+
def test_unknown_extension_uses_accept_header(self, mitmproxy_request: MitmproxyRequest):
|
762
|
+
"""Test that files with unknown extensions use accept header for content type."""
|
763
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
764
|
+
unknown_file = os.path.join(tmp_dir_path, 'test.unknown')
|
765
|
+
|
766
|
+
with open(unknown_file, 'w') as f:
|
767
|
+
f.write('test content')
|
768
|
+
|
769
|
+
fixture = {
|
770
|
+
'GET': {
|
771
|
+
'/api/data': {
|
772
|
+
'path': unknown_file
|
773
|
+
}
|
774
|
+
}
|
775
|
+
}
|
776
|
+
|
777
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
778
|
+
assert res is not None
|
779
|
+
# Should use highest priority from accept header (application/json)
|
780
|
+
assert res.headers.get('content-type') == 'application/json'
|
781
|
+
|
782
|
+
def test_wildcard_accept_header_defaults(self, mitmproxy_request: MitmproxyRequest):
|
783
|
+
"""Test that wildcard accept header gets reasonable defaults."""
|
784
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
785
|
+
test_file = os.path.join(tmp_dir_path, 'test_wildcard')
|
786
|
+
|
787
|
+
with open(test_file, 'w') as f:
|
788
|
+
f.write('test content')
|
789
|
+
|
790
|
+
# Set accept header to */*
|
791
|
+
mitmproxy_request.headers['accept'] = '*/*'
|
792
|
+
|
793
|
+
fixture = {
|
794
|
+
'GET': {
|
795
|
+
'/api/data': {
|
796
|
+
'path': test_file
|
797
|
+
}
|
798
|
+
}
|
799
|
+
}
|
800
|
+
|
801
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
802
|
+
assert res is not None
|
803
|
+
# Should default to text/html based on the wildcard handling
|
804
|
+
assert res.headers.get('content-type') == 'text/plain'
|
805
|
+
|
806
|
+
class TestRegexPatternMatching():
|
807
|
+
"""Test regex pattern matching for fixture routes."""
|
808
|
+
|
809
|
+
@pytest.fixture(scope='class')
|
810
|
+
def request_method(self):
|
811
|
+
return 'GET'
|
812
|
+
|
813
|
+
@pytest.fixture(scope='class')
|
814
|
+
def request_url(self):
|
815
|
+
return 'https://example.com/api/users/123'
|
816
|
+
|
817
|
+
@pytest.fixture(scope='class')
|
818
|
+
def test_file_contents(self):
|
819
|
+
return b'User 123 data'
|
820
|
+
|
821
|
+
@pytest.fixture(scope='class')
|
822
|
+
def test_file_path(self, test_file_contents: bytes):
|
823
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
824
|
+
file_path = os.path.join(tmp_dir_path, 'user_data.json')
|
825
|
+
with open(file_path, 'wb') as f:
|
826
|
+
f.write(test_file_contents)
|
827
|
+
return file_path
|
828
|
+
|
829
|
+
@pytest.fixture(scope='class')
|
830
|
+
def created_request(self, settings: Settings, request_method: str, request_url: str):
|
831
|
+
status = RequestBuilder(
|
832
|
+
method=request_method,
|
833
|
+
request_headers={'accept': 'application/json'},
|
834
|
+
response_body='',
|
835
|
+
status_code=200,
|
836
|
+
url=request_url,
|
837
|
+
).with_settings(settings).build()[1]
|
838
|
+
assert status == 200
|
839
|
+
return Request.last()
|
840
|
+
|
841
|
+
@pytest.fixture()
|
842
|
+
def mitmproxy_request(self, created_request: Request) -> MitmproxyRequest:
|
843
|
+
return MitmproxyRequestAdapter(created_request).adapt()
|
844
|
+
|
845
|
+
def test_regex_pattern_matching(self, mitmproxy_request: MitmproxyRequest, test_file_path: str, test_file_contents: bytes):
|
846
|
+
"""Test that regex patterns work for matching routes."""
|
847
|
+
fixture = {
|
848
|
+
'GET': {
|
849
|
+
r'/api/users/\d+': { # Regex pattern to match user IDs
|
850
|
+
'path': test_file_path
|
851
|
+
}
|
852
|
+
}
|
853
|
+
}
|
854
|
+
|
855
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
856
|
+
assert res is not None
|
857
|
+
assert res.raw.read() == test_file_contents
|
858
|
+
|
859
|
+
def test_exact_path_matching(self, mitmproxy_request: MitmproxyRequest, test_file_path: str, test_file_contents: bytes):
|
860
|
+
"""Test exact path matching."""
|
861
|
+
fixture = {
|
862
|
+
'GET': {
|
863
|
+
'/api/users/123': { # Exact path match
|
864
|
+
'path': test_file_path
|
865
|
+
}
|
866
|
+
}
|
867
|
+
}
|
868
|
+
|
869
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
870
|
+
assert res is not None
|
871
|
+
assert res.raw.read() == test_file_contents
|
872
|
+
|
873
|
+
def test_non_matching_pattern(self, mitmproxy_request: MitmproxyRequest, test_file_path: str):
|
874
|
+
"""Test that non-matching patterns return None."""
|
875
|
+
fixture = {
|
876
|
+
'GET': {
|
877
|
+
r'/api/posts/\d+': { # Different pattern that shouldn't match
|
878
|
+
'path': test_file_path
|
879
|
+
}
|
880
|
+
}
|
881
|
+
}
|
882
|
+
|
883
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
884
|
+
assert res is None
|
885
|
+
|
886
|
+
def test_multiple_patterns_first_match_wins(self, mitmproxy_request: MitmproxyRequest, test_file_path: str):
|
887
|
+
"""Test that first matching pattern is used."""
|
888
|
+
tmp_dir_path = DataDir.instance().tmp_dir_path
|
889
|
+
second_file = os.path.join(tmp_dir_path, 'second_file.json')
|
890
|
+
with open(second_file, 'w') as f:
|
891
|
+
f.write('Second file content')
|
892
|
+
|
893
|
+
fixture = {
|
894
|
+
'GET': {
|
895
|
+
r'/api/users/\d+': { # First pattern - should match
|
896
|
+
'path': test_file_path
|
897
|
+
},
|
898
|
+
r'/api/.*': { # Second pattern - also matches but shouldn't be used
|
899
|
+
'path': second_file
|
900
|
+
}
|
901
|
+
}
|
902
|
+
}
|
903
|
+
|
904
|
+
res = eval_fixtures(mitmproxy_request, response_fixtures=fixture)
|
905
|
+
assert res is not None
|
906
|
+
# Should use first matching pattern
|
907
|
+
assert res.raw.read() == b'User 123 data'
|
908
|
+
|
463
909
|
class TestMultipleResponseFixtures():
|
464
910
|
@pytest.fixture(scope='class')
|
465
911
|
def request_method(self):
|
@@ -649,10 +1095,10 @@ class TestEvalFixturesService():
|
|
649
1095
|
}
|
650
1096
|
|
651
1097
|
with open(api_fixtures_file, 'w') as f:
|
652
|
-
yaml.dump(api_fixtures_content, f)
|
1098
|
+
yaml.dump(api_fixtures_content, f, sort_keys=False)
|
653
1099
|
|
654
1100
|
with open(main_fixtures_file, 'w') as f:
|
655
|
-
yaml.dump(main_fixtures_content, f)
|
1101
|
+
yaml.dump(main_fixtures_content, f, sort_keys=False)
|
656
1102
|
|
657
1103
|
# Use response_fixtures_path with origin specification
|
658
1104
|
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{main_fixtures_file}:https://petstore\\.swagger\\.io"
|
@@ -681,7 +1127,7 @@ class TestEvalFixturesService():
|
|
681
1127
|
}
|
682
1128
|
|
683
1129
|
with open(fallback_fixtures_file, 'w') as f:
|
684
|
-
yaml.dump(fallback_fixtures_content, f)
|
1130
|
+
yaml.dump(fallback_fixtures_content, f, sort_keys=False)
|
685
1131
|
|
686
1132
|
# Use a fixture file without origin specification (fallback)
|
687
1133
|
res: requests.Response = eval_fixtures(main_mitmproxy_request, response_fixtures_path=fallback_fixtures_file)
|
@@ -721,10 +1167,10 @@ class TestEvalFixturesService():
|
|
721
1167
|
}
|
722
1168
|
|
723
1169
|
with open(api_fixtures_file, 'w') as f:
|
724
|
-
yaml.dump(api_fixtures_content, f)
|
1170
|
+
yaml.dump(api_fixtures_content, f, sort_keys=False)
|
725
1171
|
|
726
1172
|
with open(fallback_fixtures_file, 'w') as f:
|
727
|
-
yaml.dump(fallback_fixtures_content, f)
|
1173
|
+
yaml.dump(fallback_fixtures_content, f, sort_keys=False)
|
728
1174
|
|
729
1175
|
# Use response_fixtures_path with origin-specific first, fallback second
|
730
1176
|
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{fallback_fixtures_file}"
|
@@ -766,7 +1212,7 @@ class TestEvalFixturesService():
|
|
766
1212
|
}
|
767
1213
|
|
768
1214
|
with open(wildcard_fixtures_file, 'w') as f:
|
769
|
-
yaml.dump(wildcard_fixtures_content, f)
|
1215
|
+
yaml.dump(wildcard_fixtures_content, f, sort_keys=False)
|
770
1216
|
|
771
1217
|
# Use regex wildcard pattern in response_fixtures_path
|
772
1218
|
fixtures_paths = f"{wildcard_fixtures_file}:https://.*\\.example\\.com"
|
@@ -795,7 +1241,7 @@ class TestEvalFixturesService():
|
|
795
1241
|
}
|
796
1242
|
|
797
1243
|
with open(fixtures_file_path, 'w') as fp:
|
798
|
-
yaml.dump(fixtures_content, fp)
|
1244
|
+
yaml.dump(fixtures_content, fp, sort_keys=False)
|
799
1245
|
|
800
1246
|
# Test loading fixtures via response_fixtures_path
|
801
1247
|
res: requests.Response = eval_fixtures(api_mitmproxy_request, response_fixtures_path=fixtures_file_path)
|
@@ -834,10 +1280,10 @@ class TestEvalFixturesService():
|
|
834
1280
|
}
|
835
1281
|
|
836
1282
|
with open(api_fixtures_file, 'w') as fp:
|
837
|
-
yaml.dump(api_fixtures_content, fp)
|
1283
|
+
yaml.dump(api_fixtures_content, fp, sort_keys=False)
|
838
1284
|
|
839
1285
|
with open(main_fixtures_file, 'w') as fp:
|
840
|
-
yaml.dump(main_fixtures_content, fp)
|
1286
|
+
yaml.dump(main_fixtures_content, fp, sort_keys=False)
|
841
1287
|
|
842
1288
|
# Test with comma-separated paths with origins
|
843
1289
|
fixtures_paths = f"{api_fixtures_file}:https://api\\.example\\.com,{main_fixtures_file}:https://petstore\\.swagger\\.io"
|
@@ -885,10 +1331,10 @@ class TestEvalFixturesService():
|
|
885
1331
|
}
|
886
1332
|
|
887
1333
|
with open(first_fixtures_file, 'w') as fp:
|
888
|
-
yaml.dump(first_fixtures_content, fp)
|
1334
|
+
yaml.dump(first_fixtures_content, fp, sort_keys=False)
|
889
1335
|
|
890
1336
|
with open(second_fixtures_file, 'w') as fp:
|
891
|
-
yaml.dump(second_fixtures_content, fp)
|
1337
|
+
yaml.dump(second_fixtures_content, fp, sort_keys=False)
|
892
1338
|
|
893
1339
|
# Create an invalid YAML file that would cause an error if processed
|
894
1340
|
with open(invalid_fixtures_file, 'w') as fp:
|
@@ -936,10 +1382,10 @@ class TestEvalFixturesService():
|
|
936
1382
|
}
|
937
1383
|
|
938
1384
|
with open(wrong_origin_file, 'w') as fp:
|
939
|
-
yaml.dump(wrong_origin_content, fp)
|
1385
|
+
yaml.dump(wrong_origin_content, fp, sort_keys=False)
|
940
1386
|
|
941
1387
|
with open(correct_origin_file, 'w') as fp:
|
942
|
-
yaml.dump(correct_origin_content, fp)
|
1388
|
+
yaml.dump(correct_origin_content, fp, sort_keys=False)
|
943
1389
|
|
944
1390
|
# Test with API request - wrong origin should be skipped, correct origin should be used
|
945
1391
|
fixtures_paths = f"{wrong_origin_file}:https://petstore\\.swagger\\.io,{correct_origin_file}:https://api\\.example\\.com"
|
@@ -974,7 +1420,7 @@ class TestEvalFixturesService():
|
|
974
1420
|
}
|
975
1421
|
}
|
976
1422
|
with open(api_fixtures_file, 'w') as f:
|
977
|
-
yaml.dump(fixtures_content, f)
|
1423
|
+
yaml.dump(fixtures_content, f, sort_keys=False)
|
978
1424
|
|
979
1425
|
# Test full URL with port
|
980
1426
|
status = RequestBuilder(
|
@@ -1015,7 +1461,7 @@ class TestEvalFixturesService():
|
|
1015
1461
|
}
|
1016
1462
|
}
|
1017
1463
|
with open(api_fixtures_file, 'w') as f:
|
1018
|
-
yaml.dump(fixtures_content, f)
|
1464
|
+
yaml.dump(fixtures_content, f, sort_keys=False)
|
1019
1465
|
|
1020
1466
|
# Test HTTP request
|
1021
1467
|
status = RequestBuilder(
|
@@ -1056,7 +1502,7 @@ class TestEvalFixturesService():
|
|
1056
1502
|
}
|
1057
1503
|
}
|
1058
1504
|
with open(wildcard_fixtures_file, 'w') as f:
|
1059
|
-
yaml.dump(fixtures_content, f)
|
1505
|
+
yaml.dump(fixtures_content, f, sort_keys=False)
|
1060
1506
|
|
1061
1507
|
# Test subdomain request
|
1062
1508
|
status = RequestBuilder(
|
@@ -1,8 +1,8 @@
|
|
1
|
-
stoobly_agent/__init__.py,sha256=
|
1
|
+
stoobly_agent/__init__.py,sha256=G6RHX8ESNEXsGSpOUuCoZooxoXtbFgnLwqa8NHC683M,45
|
2
2
|
stoobly_agent/__main__.py,sha256=tefOkFZeCFU4l3C-Y4R_lR9Yt-FETISiXGUnbh6Os54,146
|
3
3
|
stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
stoobly_agent/app/api/__init__.py,sha256=NIgcbX7iiWrApsCITXlmhr4SYbWS0fwb01x-F3jTFdo,666
|
5
|
-
stoobly_agent/app/api/application_http_request_handler.py,sha256=
|
5
|
+
stoobly_agent/app/api/application_http_request_handler.py,sha256=h8y3gjPOgbFK3z0UIX3t6aGtRMAiQt3QH9qZ_Gduq_M,5363
|
6
6
|
stoobly_agent/app/api/bodies_controller.py,sha256=tiuBIcLsRIUrVlStFwhGj_8AzsURSmW0E_MDQb09WXY,2267
|
7
7
|
stoobly_agent/app/api/configs_controller.py,sha256=S8BYn5PHMlc_vdnJTU0qE6w0lL7A7swEiV5_LJJvMOU,4736
|
8
8
|
stoobly_agent/app/api/headers_controller.py,sha256=qUM0U0WNn_87outx1y4r8Mfr6RqHO0UR91JP0-LNajc,2781
|
@@ -59,7 +59,7 @@ stoobly_agent/app/cli/helpers/test_facade.py,sha256=gRnbjYNxnzPmtvFp2J6TZgxaMSzD
|
|
59
59
|
stoobly_agent/app/cli/helpers/test_replay_context.py,sha256=Tx5fenr14mXqHvCpl7o9c2qTJw8Kl9XgxzqqDhqFj1I,4754
|
60
60
|
stoobly_agent/app/cli/helpers/trace_aliases.py,sha256=8N8pnsnONwVv-fabCTvDlXec-WbSu1zfCNHTuzl8Tzc,874
|
61
61
|
stoobly_agent/app/cli/helpers/trace_context_facade.py,sha256=3MDjY_bdhvE2dad_B_w9gemCZZoiVjotbE8o9hrmVYo,1027
|
62
|
-
stoobly_agent/app/cli/helpers/validations.py,sha256=
|
62
|
+
stoobly_agent/app/cli/helpers/validations.py,sha256=o9_DqlAIw98QcMNXGsAnELQ6DeXdSRa1TLiTZu3V7ns,6236
|
63
63
|
stoobly_agent/app/cli/helpers/verify_raw_request_service.py,sha256=tmLeRBYhNgAQeJD-rF6Nj22F_TfVktVSb1wjRPFblBQ,658
|
64
64
|
stoobly_agent/app/cli/intercept_cli.py,sha256=tgGtl1EZerIdcuMR8KKHdrrn_7-XWSg0D6XdTuSq8k0,6698
|
65
65
|
stoobly_agent/app/cli/main_group.py,sha256=3UzBxin2f2p5KNqo6Oayo_I1VI2pHpqOoRexUnJ74a4,2187
|
@@ -67,7 +67,7 @@ stoobly_agent/app/cli/project_cli.py,sha256=EXjeLjbnq9PhfCjvyfZ0UnJ2tejeCS0SIAo3
|
|
67
67
|
stoobly_agent/app/cli/report_cli.py,sha256=ZxJw0Xkx7KFZJn9e45BSKRKon8AD0Msrwy1fbPfbv0c,2543
|
68
68
|
stoobly_agent/app/cli/request_cli.py,sha256=NVO6alj2acwaYLvU2445yB5qAQIK32Ll19tOErxzNR8,7863
|
69
69
|
stoobly_agent/app/cli/scaffold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
70
|
-
stoobly_agent/app/cli/scaffold/app.py,sha256=
|
70
|
+
stoobly_agent/app/cli/scaffold/app.py,sha256=7d5nHnQwyBxEwSC2Jrvbk2U3dj9OnhaHrE2zmC37oOs,3922
|
71
71
|
stoobly_agent/app/cli/scaffold/app_command.py,sha256=x--ejtVSBL0Jz8OiakBtxfI2IZFAWJWCwuSJo7TEU9Y,2386
|
72
72
|
stoobly_agent/app/cli/scaffold/app_config.py,sha256=oG06y9yuAo05ROpOhDC_LFPJ0KKNrhfvYLOgHx5nE4Y,2788
|
73
73
|
stoobly_agent/app/cli/scaffold/app_create_command.py,sha256=G4C-XSp4hN0FvaSKLpGGvTp8iXHqKYdqoxxRPHgO0a0,8253
|
@@ -110,7 +110,7 @@ stoobly_agent/app/cli/scaffold/service_command.py,sha256=j-lkG5Zth_CBHa6Z9Kv3dJw
|
|
110
110
|
stoobly_agent/app/cli/scaffold/service_config.py,sha256=A8kQU3n3IhOn64PAQo-O_WHGYdjz8AAER1UF4z0KKPw,6977
|
111
111
|
stoobly_agent/app/cli/scaffold/service_create_command.py,sha256=PwydEKBNodrGZVmfWifNjrYRDHifAnYR7gpeYMoTU3s,3726
|
112
112
|
stoobly_agent/app/cli/scaffold/service_delete_command.py,sha256=_nBDQjm8eL62MQpzSCxgUHlW04ZXKG8MDlN1BXxlqww,986
|
113
|
-
stoobly_agent/app/cli/scaffold/service_dependency.py,sha256=
|
113
|
+
stoobly_agent/app/cli/scaffold/service_dependency.py,sha256=olr_s_cfn51Pz5FlIihlydEl7g4CHKn-YJT0EP2h8eA,1855
|
114
114
|
stoobly_agent/app/cli/scaffold/service_docker_compose.py,sha256=fVUZ-oo-bn5GVZp8JgGq7AkiQQ6-JkxwK_OMlinS9WM,915
|
115
115
|
stoobly_agent/app/cli/scaffold/service_update_command.py,sha256=oWusBKfvjt4RnK03_V3CJYWrfsCI4_LcR7W12eLXMR4,2579
|
116
116
|
stoobly_agent/app/cli/scaffold/service_workflow.py,sha256=sQ_Edy_wGHKMXpD0DmhnOWkGEKz7gSgEGNI8f7aXOdg,444
|
@@ -118,7 +118,7 @@ stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py,sha256=80pX8
|
|
118
118
|
stoobly_agent/app/cli/scaffold/templates/__init__.py,sha256=x8C_a0VoO_vUbosp4_6IC1U7Ge9NnUdVKDPpVMtMkeY,171
|
119
119
|
stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context,sha256=9DQK-OXnRKjjKWsUSIRAio6dkR4eGxD1vizPT7Q5sp8,159
|
120
120
|
stoobly_agent/app/cli/scaffold/templates/app/.Makefile,sha256=OnY_3D9nxl3HxfUxvCgk7PY3XntEbgO9j1myx_ETK7w,9161
|
121
|
-
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=
|
121
|
+
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.base.yml,sha256=SABod6_InBFyOm-uulK7r27SR0sWRUiL0h69g_jvNJA,249
|
122
122
|
stoobly_agent/app/cli/scaffold/templates/app/.docker-compose.networks.yml,sha256=I4PbJpQjFHb5IbAUWNvYM6okDEtmwtKFDQg-yog05WM,141
|
123
123
|
stoobly_agent/app/cli/scaffold/templates/app/Makefile,sha256=TEmPG7Bf0KZOnmfsgdzza3UdwcVMmM5Lj1YdLc4cgjA,79
|
124
124
|
stoobly_agent/app/cli/scaffold/templates/app/build/.config.yml,sha256=8Wt8ZZ5irvBYYS44xGrR_EWlZDuXH9kyWmquzsh7s8g,19
|
@@ -361,15 +361,15 @@ stoobly_agent/app/proxy/mitmproxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeR
|
|
361
361
|
stoobly_agent/app/proxy/mitmproxy/flow_mock.py,sha256=YxQV3pXIzicvLOCFn7zmnSoAstAmR8fzONYOXAkYbT4,1153
|
362
362
|
stoobly_agent/app/proxy/mitmproxy/request.py,sha256=niy38718CAe4Li4k40GXnWDDmUTCPkXesDZTaC8BeLc,878
|
363
363
|
stoobly_agent/app/proxy/mitmproxy/request_body_facade.py,sha256=kMJ8YH2FKW9qV68QcUbOCLfiGgKrFV5krzt7usGf0as,649
|
364
|
-
stoobly_agent/app/proxy/mitmproxy/request_facade.py,sha256=
|
364
|
+
stoobly_agent/app/proxy/mitmproxy/request_facade.py,sha256=9yvTOPWQw7epg-wxvlT66lKR0-ZgLHEOVfX-xeumpJM,8537
|
365
365
|
stoobly_agent/app/proxy/mitmproxy/response.py,sha256=BFaJlB1D98EOjrc-kUmunUCTUuiZsPDqq2QgAHDocQI,339
|
366
366
|
stoobly_agent/app/proxy/mitmproxy/response_body_facade.py,sha256=vIu6cuCSiZDocvnMNyi0Zjf0qsC0enm2Glp671eWnb4,659
|
367
367
|
stoobly_agent/app/proxy/mitmproxy/response_facade.py,sha256=0wCSzUULUhDDV93QXUgzMNxiiSZwcu-kUB2C5z1Ck0Y,4311
|
368
368
|
stoobly_agent/app/proxy/mock/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
369
369
|
stoobly_agent/app/proxy/mock/context.py,sha256=vDo5_3WBL73mVFnsmQWvcxvPg5nWtRJbigSrE3zGc-o,794
|
370
370
|
stoobly_agent/app/proxy/mock/custom_not_found_response_builder.py,sha256=0KWB3KFxVrnJOKDaYxm5eoJEccw7IpJZRyUvBX61-8k,697
|
371
|
-
stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=
|
372
|
-
stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=
|
371
|
+
stoobly_agent/app/proxy/mock/eval_fixtures_service.py,sha256=YNe1NnpmTvhtjfAXL2Vs3cmYmd4fcNCSVYeun4ANdoc,12068
|
372
|
+
stoobly_agent/app/proxy/mock/eval_request_service.py,sha256=JLmFzMrtbphPGPfUznaGW-_cc6sMZDehhdqY3tXrgzI,8150
|
373
373
|
stoobly_agent/app/proxy/mock/hashed_request_decorator.py,sha256=h1ma90fdaYI9LBWpMWMqWBz-RjNwI628O4VuS_uUBX4,5061
|
374
374
|
stoobly_agent/app/proxy/mock/ignored_components_response_builder.py,sha256=E32_E1eSdmPn2SeM_e1jWnqu4xh5w_SnmOs32Shx99E,501
|
375
375
|
stoobly_agent/app/proxy/mock/request_hasher.py,sha256=WeOeksmW_b5Ql0Xb0fL2WMH3MVHrm-9GYWYoKYS1teg,3755
|
@@ -422,7 +422,7 @@ stoobly_agent/app/proxy/test/matchers/fuzzy.py,sha256=tTnsPDaK0HFJ5ST5ThZIlHkZNz
|
|
422
422
|
stoobly_agent/app/proxy/test/matchers/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
423
423
|
stoobly_agent/app/proxy/test/test_service.py,sha256=EWDdrcoewgCzsuWmbra-dDEZLfuWBiX6X9LF_xwqglw,4314
|
424
424
|
stoobly_agent/app/proxy/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
425
|
-
stoobly_agent/app/proxy/utils/allowed_request_service.py,sha256=
|
425
|
+
stoobly_agent/app/proxy/utils/allowed_request_service.py,sha256=V977CB7Ee6kc566PX0V7rGk4pJsy0SgRmXm8CC78d6c,3717
|
426
426
|
stoobly_agent/app/proxy/utils/minimize_headers.py,sha256=hL5-cpNgqilOXY0s7sjUL76Ixym4I1bUws9yDlrsXGs,1175
|
427
427
|
stoobly_agent/app/proxy/utils/publish_change_service.py,sha256=ljKaVz1i3ClUNk0_UHvwpoJdUhapZhwQXs4UB13LsM8,1292
|
428
428
|
stoobly_agent/app/proxy/utils/request_handler.py,sha256=VscAXf2_F1C1yrt2gNImPkatosuGj356pARTd_WnTUE,846
|
@@ -545,7 +545,7 @@ stoobly_agent/lib/api/stoobly_api.py,sha256=PacuNx3-RzqXHJSXwYXoKl9JAVhefd5A7Qqy
|
|
545
545
|
stoobly_agent/lib/api/test_responses_resource.py,sha256=RdNj7N6xC7jW5zUoKfgNg8Nenk7wdSMaL4dv_DIXih0,541
|
546
546
|
stoobly_agent/lib/api/tests_resource.py,sha256=EKyI0xTgzRSPyGdepgteVH3EHUkXIzrt3HXahBau6CY,855
|
547
547
|
stoobly_agent/lib/api/users_resource.py,sha256=xR0ug5xifuvRw4jdF4cVjrWBdkFccmL9_fy8ZxOPX74,477
|
548
|
-
stoobly_agent/lib/cache.py,sha256=
|
548
|
+
stoobly_agent/lib/cache.py,sha256=7sYq2wh6I5LmtKqvJYoY89h_PvZ21zvS9pCBCEutuCM,2266
|
549
549
|
stoobly_agent/lib/logger.py,sha256=6s_8UxXLJt1jor-Ez0xCBWhuAD932sTEBQBnNXpFTtQ,1971
|
550
550
|
stoobly_agent/lib/orm/__init__.py,sha256=UCAL40_L9qf1sUqvUWKzc551jKLR4HKqRNLPdOuXl2w,1199
|
551
551
|
stoobly_agent/lib/orm/base.py,sha256=3PZx0OWK241vrr8pLLQL0GFhKxvYVDFyVMMdYzhxMPM,358
|
@@ -755,13 +755,13 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
|
|
755
755
|
stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
|
756
756
|
stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
|
757
757
|
stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
|
758
|
-
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=
|
758
|
+
stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=NNWX20PKU7L9csy90a96D-I4wsC4Mh2tj0OhYTFD_GI,6
|
759
759
|
stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
|
760
760
|
stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
|
761
761
|
stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
|
762
762
|
stoobly_agent/test/app/models/schemas/request_test.py,sha256=9SF43KXbjO-vMr2uObPJlyLeop_JQstl6Jrh0M1A70c,1116
|
763
|
-
stoobly_agent/test/app/proxy/mitmproxy/request_facade_test.py,sha256=
|
764
|
-
stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py,sha256=
|
763
|
+
stoobly_agent/test/app/proxy/mitmproxy/request_facade_test.py,sha256=z5_4VdSBVtcox5gZsiit-9EGgHfvqaPRHHsXSBXaVUc,6791
|
764
|
+
stoobly_agent/test/app/proxy/mock/eval_fixtures_service_test.py,sha256=BMn1EmBSnkEUlFCbcz5BsQuBM_qCgLmAUPdnL5E7cL8,58429
|
765
765
|
stoobly_agent/test/app/proxy/replay/body_parser_service_test.py,sha256=5clzjjkdve0Ctggnw-ZLmZSUtEfWK-Ejtf1x7zCu7z4,4994
|
766
766
|
stoobly_agent/test/app/proxy/replay/rewrite_params_service_test.py,sha256=4KVaP48KjCeoZKqY3IdrFAP5Pnb3jO86k8L7ffvz2ZI,3770
|
767
767
|
stoobly_agent/test/app/proxy/replay/trace_context_test.py,sha256=y0oBNC89sp3BG8biOBTiaTopk1LtqjThlA4d6BwAHzM,14462
|
@@ -797,8 +797,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
|
|
797
797
|
stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
|
798
798
|
stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
|
799
799
|
stoobly_agent/test/test_helper.py,sha256=6v4AHeqYPw7vtRoxET_ubmRWPJoSmTR_DVHay3FxNbQ,1299
|
800
|
-
stoobly_agent-1.10.
|
801
|
-
stoobly_agent-1.10.
|
802
|
-
stoobly_agent-1.10.
|
803
|
-
stoobly_agent-1.10.
|
804
|
-
stoobly_agent-1.10.
|
800
|
+
stoobly_agent-1.10.3.dist-info/METADATA,sha256=WOtXuRZ-PH8eZ38X2Z63_zddgLbLMbtLJ6Mbq3-vo6I,3203
|
801
|
+
stoobly_agent-1.10.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
802
|
+
stoobly_agent-1.10.3.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
|
803
|
+
stoobly_agent-1.10.3.dist-info/licenses/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
|
804
|
+
stoobly_agent-1.10.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|