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.
Files changed (50) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/configs_controller.py +3 -3
  3. stoobly_agent/app/cli/helpers/handle_config_update_service.py +12 -12
  4. stoobly_agent/app/cli/intercept_cli.py +30 -7
  5. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
  6. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +6 -0
  7. stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/.overwrite +13 -0
  8. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +16 -0
  9. stoobly_agent/app/models/adapters/joined_request_adapter.py +1 -1
  10. stoobly_agent/app/models/factories/resource/local_db/helpers/log.py +52 -2
  11. stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py +0 -1
  12. stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py +2 -3
  13. stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot_service.py +9 -8
  14. stoobly_agent/app/models/helpers/apply.py +10 -5
  15. stoobly_agent/app/proxy/handle_mock_service.py +19 -6
  16. stoobly_agent/app/proxy/handle_record_service.py +16 -7
  17. stoobly_agent/app/proxy/intercept_settings.py +12 -1
  18. stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
  19. stoobly_agent/app/proxy/record/request_string.py +15 -9
  20. stoobly_agent/app/proxy/replay/body_parser_service.py +15 -4
  21. stoobly_agent/app/proxy/replay/replay_request_service.py +1 -0
  22. stoobly_agent/app/settings/data_rules.py +16 -1
  23. stoobly_agent/config/constants/custom_headers.py +1 -0
  24. stoobly_agent/config/constants/lifecycle_hooks.py +1 -0
  25. stoobly_agent/config/constants/record_order.py +6 -0
  26. stoobly_agent/config/constants/record_policy.py +2 -2
  27. stoobly_agent/public/18-es2015.beb31fe4a4dee3007cb2.js +1 -0
  28. stoobly_agent/public/18-es5.beb31fe4a4dee3007cb2.js +1 -0
  29. stoobly_agent/public/index.html +2 -2
  30. stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +1 -0
  31. stoobly_agent/public/main-es5.089b46f303768fbe864f.js +1 -0
  32. stoobly_agent/public/{runtime-es2015.b13c22b834b51724d30a.js → runtime-es2015.f8c814b38b27708e91c1.js} +1 -1
  33. stoobly_agent/public/{runtime-es5.b13c22b834b51724d30a.js → runtime-es5.f8c814b38b27708e91c1.js} +1 -1
  34. stoobly_agent/public/{styles.ab281309cf423b2cdcb0.css → styles.817f011ab81b18b0e5c2.css} +1 -1
  35. stoobly_agent/test/app/cli/config/scenario/config_scenario_set_test.py +3 -3
  36. stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +5 -7
  37. stoobly_agent/test/app/cli/intercept/intercept_enable_test.py +3 -5
  38. stoobly_agent/test/app/cli/snapshot/snapshot_apply_test.py +143 -1
  39. stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py +72 -3
  40. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  41. stoobly_agent/test/cli/record_test.py +3 -3
  42. {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/METADATA +1 -1
  43. {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/RECORD +46 -44
  44. stoobly_agent/public/18-es2015.d07dd29def7e2574c5b7.js +0 -1
  45. stoobly_agent/public/18-es5.d07dd29def7e2574c5b7.js +0 -1
  46. stoobly_agent/public/main-es2015.ce00115b0520fa030f01.js +0 -1
  47. stoobly_agent/public/main-es5.ce00115b0520fa030f01.js +0 -1
  48. {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/LICENSE +0 -0
  49. {stoobly_agent-1.8.5.dist-info → stoobly_agent-1.9.1.dist-info}/WHEEL +0 -0
  50. {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).encode(self.ENCODING)
39
+ return CLRF.join([self.control] + self.lines)
40
40
  else:
41
- return CLRF.join(self.lines).encode(self.ENCODING)
41
+ return CLRF.join(self.lines)
42
42
 
43
43
  def set(self, s: bytes):
44
- decoded_s = decode(s, self.ENCODING)
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
- self.lines.append("{} {} HTTP/1.1".format(self.request.method, self.proxy_request.url()))
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("{}{}".format(CLRF, self.request.body))
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
- _content_type = content_type.lower()
118
- return _content_type == JSON or _content_type.startswith('application/x-amz-json')
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'
@@ -1,4 +1,5 @@
1
1
  AFTER_MOCK = 'handle_after_mock'
2
+ AFTER_MOCK_NOT_FOUND = 'handle_after_mock_not_found'
2
3
  AFTER_RECORD = 'handle_after_record'
3
4
  AFTER_REPLAY = 'handle_after_replay'
4
5
  AFTER_TEST = 'handle_after_test'
@@ -0,0 +1,6 @@
1
+ from typing import Literal
2
+
3
+ APPEND = 'append'
4
+ OVERWRITE = 'overwrite'
5
+
6
+ RecordOrder = Literal[APPEND, OVERWRITE]
@@ -1,7 +1,7 @@
1
1
  from .intercept_policy import ALL as INTERCEPT_ALL, NONE as INTERCEPT_NONE
2
2
 
3
3
  ALL = INTERCEPT_ALL
4
+ API = 'api'
4
5
  FOUND = 'found'
5
6
  NONE = INTERCEPT_NONE
6
- NOT_FOUND = 'not_found'
7
- OVERWRITE = 'overwrite'
7
+ NOT_FOUND = 'not_found'