stoobly-agent 1.8.5__py3-none-any.whl → 1.9.1__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/configs_controller.py +3 -3
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +12 -12
- stoobly_agent/app/cli/intercept_cli.py +30 -7
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +6 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/.overwrite +13 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +16 -0
- stoobly_agent/app/models/adapters/joined_request_adapter.py +1 -1
- stoobly_agent/app/models/factories/resource/local_db/helpers/log.py +52 -2
- stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py +0 -1
- stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py +2 -3
- stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_service.py +9 -8
- stoobly_agent/app/models/helpers/apply.py +10 -5
- stoobly_agent/app/proxy/handle_mock_service.py +19 -6
- stoobly_agent/app/proxy/handle_record_service.py +16 -7
- stoobly_agent/app/proxy/intercept_settings.py +12 -1
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
- stoobly_agent/app/proxy/record/request_string.py +15 -9
- stoobly_agent/app/proxy/replay/body_parser_service.py +15 -4
- stoobly_agent/app/proxy/replay/replay_request_service.py +1 -0
- stoobly_agent/app/settings/data_rules.py +16 -1
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/lifecycle_hooks.py +1 -0
- stoobly_agent/config/constants/record_order.py +6 -0
- stoobly_agent/config/constants/record_policy.py +2 -2
- stoobly_agent/public/18-es2015.beb31fe4a4dee3007cb2.js +1 -0
- stoobly_agent/public/18-es5.beb31fe4a4dee3007cb2.js +1 -0
- stoobly_agent/public/index.html +2 -2
- stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +1 -0
- stoobly_agent/public/main-es5.089b46f303768fbe864f.js +1 -0
- stoobly_agent/public/{runtime-es2015.b13c22b834b51724d30a.js → runtime-es2015.f8c814b38b27708e91c1.js} +1 -1
- stoobly_agent/public/{runtime-es5.b13c22b834b51724d30a.js → runtime-es5.f8c814b38b27708e91c1.js} +1 -1
- stoobly_agent/public/{styles.ab281309cf423b2cdcb0.css → styles.817f011ab81b18b0e5c2.css} +1 -1
- stoobly_agent/test/app/cli/config/scenario/config_scenario_set_test.py +3 -3
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +5 -7
- stoobly_agent/test/app/cli/intercept/intercept_enable_test.py +3 -5
- stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py +143 -1
- stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py +72 -3
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/cli/record_test.py +3 -3
- {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/METADATA +1 -1
- {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/RECORD +46 -44
- stoobly_agent/public/18-es2015.d07dd29def7e2574c5b7.js +0 -1
- stoobly_agent/public/18-es5.d07dd29def7e2574c5b7.js +0 -1
- stoobly_agent/public/main-es2015.ce00115b0520fa030f01.js +0 -1
- stoobly_agent/public/main-es5.ce00115b0520fa030f01.js +0 -1
- {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/entry_points.txt +0 -0
@@ -9,7 +9,7 @@ from stoobly_agent.lib.utils.decode import decode
|
|
9
9
|
from .proxy_request import ProxyRequest
|
10
10
|
from .request_string_control import RequestStringControl
|
11
11
|
|
12
|
-
CLRF = "\r\n"
|
12
|
+
CLRF = b"\r\n"
|
13
13
|
|
14
14
|
class RequestString:
|
15
15
|
ENCODING = 'utf-8'
|
@@ -36,20 +36,19 @@ class RequestString:
|
|
36
36
|
|
37
37
|
def get(self, **kwargs):
|
38
38
|
if kwargs.get('control'):
|
39
|
-
return CLRF.join([self.control] + self.lines)
|
39
|
+
return CLRF.join([self.control] + self.lines)
|
40
40
|
else:
|
41
|
-
return CLRF.join(self.lines)
|
41
|
+
return CLRF.join(self.lines)
|
42
42
|
|
43
43
|
def set(self, s: bytes):
|
44
|
-
|
45
|
-
self.lines = decoded_s.split(CLRF)
|
44
|
+
self.lines = s.split(CLRF)
|
46
45
|
|
47
46
|
@property
|
48
47
|
def control(self):
|
49
48
|
control = RequestStringControl()
|
50
49
|
control.id = self.request_id
|
51
50
|
control.timestamp = self.__current_time
|
52
|
-
return control.serialize()
|
51
|
+
return control.serialize().encode(self.ENCODING)
|
53
52
|
|
54
53
|
@control.setter
|
55
54
|
def control(self, c: str):
|
@@ -58,20 +57,27 @@ class RequestString:
|
|
58
57
|
self.__current_time = control.timestamp
|
59
58
|
|
60
59
|
def __request_line(self):
|
61
|
-
|
60
|
+
line = "{} {} HTTP/1.1".format(self.request.method, self.proxy_request.url())
|
61
|
+
self.lines.append(line.encode(self.ENCODING))
|
62
62
|
|
63
63
|
def __headers(self):
|
64
64
|
headers = self.request.headers
|
65
65
|
|
66
66
|
for name, val in headers.items():
|
67
|
+
if 'content-encoding' in headers:
|
68
|
+
# self.request.body will decode content-encoding
|
69
|
+
# to maintain internal consistency, remove this header since it will be decoded
|
70
|
+
print(self.request.body)
|
71
|
+
pass
|
72
|
+
|
67
73
|
line = ' '.join([
|
68
74
|
"{}:".format(self.__to_header_case(self.__to_str(name))),
|
69
75
|
self.__to_str(val)
|
70
76
|
])
|
71
|
-
self.lines.append(line)
|
77
|
+
self.lines.append(line.encode(self.ENCODING))
|
72
78
|
|
73
79
|
def __body(self):
|
74
|
-
self.lines.append(
|
80
|
+
self.lines.append(CLRF + self.request.body)
|
75
81
|
|
76
82
|
def __to_header_case(self, header: str) -> str:
|
77
83
|
toks = header.split('_')
|
@@ -14,6 +14,7 @@ from .multipart import decode as multipart_decode, encode as multipart_encode
|
|
14
14
|
JSON = 'application/json'
|
15
15
|
MULTIPART_FORM = 'multipart/form-data'
|
16
16
|
WWW_FORM_URLENCODED = 'application/x-www-form-urlencoded'
|
17
|
+
XML = 'application/xml'
|
17
18
|
|
18
19
|
def compress(body: Union[bytes, str], content_encoding: Union[None, str]) -> Union[bytes, str]:
|
19
20
|
if content_encoding:
|
@@ -113,14 +114,24 @@ def normalize_header(header):
|
|
113
114
|
def is_traversable(content):
|
114
115
|
return isinstance(content, list) or isinstance(content, dict) or isinstance(content, MultiDict)
|
115
116
|
|
116
|
-
def is_json(content_type):
|
117
|
-
|
118
|
-
|
117
|
+
def is_json(content_type: str):
|
118
|
+
if not content_type:
|
119
|
+
return False
|
120
|
+
|
121
|
+
_content_type = content_type.lower().split(';')[0]
|
122
|
+
# e.g. custom json content-type: application/x-amz-json
|
123
|
+
return _content_type == JSON or (_content_type.startswith('application/') and _content_type.endswith('json'))
|
119
124
|
|
125
|
+
def is_xml(content_type: str):
|
126
|
+
if not content_type:
|
127
|
+
return False
|
128
|
+
|
129
|
+
_content_type = content_type.lower().split(';')[0]
|
130
|
+
# e.g. custom json content-type: application/x-amz-json
|
131
|
+
return _content_type == XML or (_content_type.startswith('application/') and _content_type.endswith('xml'))
|
120
132
|
|
121
133
|
def __parse_separated_header(header: str):
|
122
134
|
# Adapted from https://peps.python.org/pep-0594/#cgi
|
123
135
|
message = Message()
|
124
136
|
message['content-type'] = header
|
125
137
|
return message.get_content_type()
|
126
|
-
|
@@ -150,6 +150,7 @@ def replay(context: ReplayContext, options: ReplayRequestOptions) -> requests.Re
|
|
150
150
|
if options.get('save') or options.get('overwrite'):
|
151
151
|
replayed_response, status = __create_replayed_response(context.request.id, res, latency)
|
152
152
|
|
153
|
+
# TODO: allow overwriting if status is the same
|
153
154
|
if status < 400 and options.get('overwrite'):
|
154
155
|
__overwrite_response(replayed_response.get('id'))
|
155
156
|
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from stoobly_agent.config.constants import mock_policy, record_policy, replay_policy, test_strategy
|
1
|
+
from stoobly_agent.config.constants import mock_policy, record_order, record_policy, replay_policy, test_strategy
|
2
2
|
|
3
3
|
from .types.proxy_settings import DataRules as IDataRules
|
4
4
|
|
@@ -8,6 +8,7 @@ class DataRules:
|
|
8
8
|
self.__data_rules = data_rules or {}
|
9
9
|
|
10
10
|
self.__mock_policy = self.__data_rules.get('mock_policy') or mock_policy.FOUND
|
11
|
+
self.__record_order = self.__data_rules.get('record_order') or record_order.APPEND
|
11
12
|
self.__record_policy = self.__data_rules.get('record_policy') or record_policy.ALL
|
12
13
|
self.__replay_policy = self.__data_rules.get('replay_policy') or replay_policy.ALL
|
13
14
|
self.__scenario_key = self.__data_rules.get('scenario_key')
|
@@ -32,6 +33,19 @@ class DataRules:
|
|
32
33
|
self.__record_policy = v
|
33
34
|
self.__data_rules['record_policy'] = v
|
34
35
|
|
36
|
+
@property
|
37
|
+
def record_order(self):
|
38
|
+
return self.__record_order
|
39
|
+
|
40
|
+
@record_order.setter
|
41
|
+
def record_order(self, v):
|
42
|
+
valid_orders = [record_order.APPEND, record_order.OVERWRITE]
|
43
|
+
if v not in valid_orders:
|
44
|
+
raise TypeError(f"record_order has to be one of {valid_orders}, got {v}")
|
45
|
+
|
46
|
+
self.__record_order = v
|
47
|
+
self.__data_rules['record_order'] = v
|
48
|
+
|
35
49
|
@property
|
36
50
|
def replay_policy(self):
|
37
51
|
return self.__replay_policy
|
@@ -68,6 +82,7 @@ class DataRules:
|
|
68
82
|
def to_dict(self) -> IDataRules:
|
69
83
|
return {
|
70
84
|
'mock_policy': self.__mock_policy,
|
85
|
+
'record_order': self.__record_order,
|
71
86
|
'record_policy': self.__record_policy,
|
72
87
|
'replay_policy': self.__replay_policy,
|
73
88
|
'scenario_key': self.__scenario_key,
|
@@ -23,6 +23,7 @@ SESSION_ID = 'X-Stoobly-Session-Id'
|
|
23
23
|
SERVICE_URL = 'X-Stoobly-Service-Url'
|
24
24
|
TEST_FILTER = 'X-Stoobly-Test-Filter'
|
25
25
|
TEST_ID = 'X-Stoobly-Test-Id'
|
26
|
+
TEST_POLICY = 'X-Stoobly-Test-Policy'
|
26
27
|
TEST_SAVE_RESULTS = 'X-Stoobly-Test-Save-Results'
|
27
28
|
TEST_SKIP = 'X-Stoobly-Test-Skip'
|
28
29
|
TEST_STRATEGY = 'X-Stoobly-Test-Strategy'
|