stoobly-agent 1.9.2__py3-none-any.whl → 1.9.4__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 CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '1.9.2'
2
+ VERSION = '1.9.4'
@@ -1,5 +1,5 @@
1
1
  from .config import Config
2
- from .constants import APP_NAME_ENV, APP_NETWORK_ENV
2
+ from .constants import APP_NAME_ENV, APP_NETWORK_ENV, APP_UI_PORT_ENV
3
3
 
4
4
  class AppConfig(Config):
5
5
 
@@ -27,11 +27,20 @@ class AppConfig(Config):
27
27
  def network(self, v):
28
28
  self.__network = v
29
29
 
30
+ @property
31
+ def ui_port(self):
32
+ return self.__ui_port
33
+
34
+ @ui_port.setter
35
+ def ui_port(self, v):
36
+ self.__ui_port = v
37
+
30
38
  def load(self, config = None):
31
39
  config = config or self.read()
32
40
 
33
41
  self.name = config.get(APP_NAME_ENV)
34
42
  self.network = config.get(APP_NETWORK_ENV)
43
+ self.ui_port = config.get(APP_UI_PORT_ENV)
35
44
 
36
45
  def write(self):
37
46
  config = {}
@@ -42,4 +51,7 @@ class AppConfig(Config):
42
51
  if self.network:
43
52
  config[APP_NETWORK_ENV] = self.network
44
53
 
54
+ if self.ui_port:
55
+ config[APP_UI_PORT_ENV] = self.ui_port
56
+
45
57
  super().write(config)
@@ -9,6 +9,7 @@ from .app_command import AppCommand
9
9
  class AppCreateOptions(TypedDict):
10
10
  name: str
11
11
  network: str
12
+ ui_port: int
12
13
 
13
14
  class AppCreateCommand(AppCommand):
14
15
 
@@ -21,6 +22,9 @@ class AppCreateCommand(AppCommand):
21
22
  if kwargs.get('network'):
22
23
  self.app_config.network = kwargs['network']
23
24
 
25
+ if kwargs.get('ui_port'):
26
+ self.app_config.ui_port = kwargs['ui_port']
27
+
24
28
  @property
25
29
  def app_name(self):
26
30
  return self.app_config.name
@@ -29,6 +33,10 @@ class AppCreateCommand(AppCommand):
29
33
  def app_network(self):
30
34
  return self.app_config.network
31
35
 
36
+ @property
37
+ def app_ui_port(self):
38
+ return self.app_config.ui_port
39
+
32
40
  def build(self):
33
41
  dest = self.scaffold_namespace_path
34
42
 
@@ -8,6 +8,7 @@ APP_DIR = '${APP_DIR}'
8
8
  APP_DIR_ENV = 'APP_DIR'
9
9
  APP_NETWORK_ENV = 'APP_NETWORK'
10
10
  APP_NAME_ENV = 'APP_NAME'
11
+ APP_UI_PORT_ENV = 'APP_UI_PORT'
11
12
  BIN_FOLDER_NAME = 'bin'
12
13
  CA_CERTS_DIR_ENV = 'CA_CERTS_DIR'
13
14
  CERTS_DIR_ENV = 'CERTS_DIR'
@@ -51,6 +51,8 @@ def __with_networks(config: dict, hostnames: List[str]):
51
51
  }
52
52
 
53
53
  def __with_traefik_config(service_paths: str, compose: dict, app_dir_path: str):
54
+ config_dest = '/etc/traefik/traefik.yml'
55
+
54
56
  if not compose['volumes']:
55
57
  compose['volumes'] = []
56
58
 
@@ -68,11 +70,15 @@ def __with_traefik_config(service_paths: str, compose: dict, app_dir_path: str):
68
70
  'providers': {
69
71
  'docker': {
70
72
  'exposedByDefault': False
73
+ },
74
+ 'file': {
75
+ 'fileName': config_dest,
76
+ 'watch': False
71
77
  }
72
78
  },
73
79
  'tls': {
74
- 'certificates': certificates
75
- }
80
+ 'certificates': certificates,
81
+ },
76
82
  }
77
83
 
78
84
  for path in service_paths:
@@ -88,7 +94,7 @@ def __with_traefik_config(service_paths: str, compose: dict, app_dir_path: str):
88
94
  if config.scheme == 'https':
89
95
  certificates.append({
90
96
  'certFile': f"/certs/{config.hostname}.crt",
91
- 'keyFile': f"/certs/{config.hostname}.key"
97
+ 'keyFile': f"/certs/{config.hostname}.key",
92
98
  })
93
99
 
94
100
  # Create traefik.yml in .stoobly/tmp
@@ -102,7 +108,7 @@ def __with_traefik_config(service_paths: str, compose: dict, app_dir_path: str):
102
108
  fp.write(yaml.dump(traefik_config))
103
109
 
104
110
  compose['volumes'].append(
105
- f"{os.path.join(APP_DIR, traefik_template_relative_path)}:/etc/traefik/traefik.yml:ro"
111
+ f"{os.path.join(APP_DIR, traefik_template_relative_path)}:{config_dest}:ro"
106
112
  )
107
113
 
108
114
  def __find_hosts(service_paths):
@@ -1,8 +1,5 @@
1
1
  services:
2
2
  stoobly_ui.base:
3
- environment:
4
- SERVICE_NAME: ${SERVICE_NAME}
5
- WORKFLOW_NAME: ${WORKFLOW_NAME}
6
3
  extends:
7
4
  file: ../.docker-compose.base.yml
8
5
  service: context_base
@@ -6,6 +6,6 @@ services:
6
6
  service: stoobly_ui.base
7
7
  image: stoobly.${USER_ID}
8
8
  ports:
9
- - '4200:4200'
9
+ - '${APP_UI_PORT}:4200'
10
10
  profiles:
11
11
  - mock
@@ -6,7 +6,7 @@ services:
6
6
  service: stoobly_ui.base
7
7
  image: stoobly.${USER_ID}
8
8
  ports:
9
- - '4200:4200'
9
+ - '${APP_UI_PORT}:4200'
10
10
  profiles:
11
11
  - record
12
12
 
@@ -2,4 +2,8 @@
2
2
 
3
3
  # Add custom initialization here
4
4
 
5
- app_path=$1 # Path to application source files
5
+ app_path=$1 # Path to application source files
6
+
7
+ # For example, the below commands copies files inside a dist folder into the public folder.
8
+ # The public folder is used when no recorded mocks are found.
9
+ #cp -r $app_path/dist/. public
@@ -6,6 +6,7 @@ import sys
6
6
 
7
7
  from io import TextIOWrapper
8
8
  from typing import List
9
+ from urllib.parse import urlparse
9
10
 
10
11
  from stoobly_agent.app.cli.helpers.certificate_authority import CertificateAuthority
11
12
  from stoobly_agent.app.cli.helpers.shell import exec_stream
@@ -88,6 +89,7 @@ def hostname(ctx):
88
89
  @click.option('--app-dir-path', default=current_working_dir, help='Path to create the app scaffold.')
89
90
  @click.option('--force', is_flag=True, help='Overwrite maintained scaffolded app files.')
90
91
  @click.option('--network', help='App default network name. Defaults to app name.')
92
+ @click.option('--ui-port', default=4200, type=click.IntRange(1, 65535), help='UI service port.')
91
93
  @click.argument('app_name')
92
94
  def create(**kwargs):
93
95
  __validate_app_dir(kwargs['app_dir_path'])
@@ -148,10 +150,10 @@ def create(**kwargs):
148
150
  sys.exit(1)
149
151
 
150
152
  if kwargs.get('hostname'):
151
- hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
152
- if not re.search(hostname_regex, kwargs['hostname']):
153
- print(f"Error: {kwargs['hostname']} is invalid.", file=sys.stderr)
154
- sys.exit(1)
153
+ __validate_hostname(kwargs.get('hostname'))
154
+
155
+ if kwargs.get("proxy_mode"):
156
+ __validate_proxy_mode(kwargs.get("proxy_mode"))
155
157
 
156
158
  app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
157
159
 
@@ -216,6 +218,13 @@ def delete(**kwargs):
216
218
  @click.option('--port', type=click.IntRange(1, 65535), help='Service port.')
217
219
  @click.option('--priority', default=5, type=click.FloatRange(1.0, 9.0), help='Determines the service run order. Lower values run first.')
218
220
  @click.option('--scheme', type=click.Choice(['http', 'https']), help='Defaults to https if hostname is set.')
221
+ @click.option('--name', type=click.STRING, help='New name of the service to update to.')
222
+ @click.option('--proxy-mode', help='''
223
+ Proxy mode can be "regular", "transparent", "socks5",
224
+ "reverse:SPEC", or "upstream:SPEC". For reverse and
225
+ upstream proxy modes, SPEC is host specification in
226
+ the form of "http[s]://host[:port]".
227
+ ''')
219
228
  @click.argument('service_name')
220
229
  def update(**kwargs):
221
230
  app = App(kwargs['app_dir_path'], DOCKER_NAMESPACE)
@@ -228,7 +237,20 @@ def update(**kwargs):
228
237
  service_config = ServiceConfig(service.dir_path)
229
238
 
230
239
  if kwargs['hostname']:
231
- service_config.hostname = kwargs['hostname']
240
+ __validate_hostname(kwargs['hostname'])
241
+
242
+ old_hostname = service_config.hostname
243
+
244
+ if old_hostname != kwargs['hostname']:
245
+ service_config.hostname = kwargs['hostname']
246
+
247
+ # If this is the default proxy_mode and the origin matches the original hostname, assume it is safe to update with the new hostname
248
+ if service_config.proxy_mode.startswith("reverse:"):
249
+ old_origin = service_config.proxy_mode.split("reverse:")[1]
250
+ parsed_origin_url = urlparse(old_origin)
251
+
252
+ if old_hostname == parsed_origin_url.hostname:
253
+ service_config.proxy_mode = service_config.proxy_mode.replace(old_hostname, service_config.hostname)
232
254
 
233
255
  if kwargs['priority']:
234
256
  service_config.priority = kwargs['priority']
@@ -239,6 +261,10 @@ def update(**kwargs):
239
261
  if kwargs['scheme']:
240
262
  service_config.scheme = kwargs['scheme']
241
263
 
264
+ if kwargs['proxy_mode']:
265
+ __validate_proxy_mode(kwargs['proxy_mode'])
266
+ service_config.proxy_mode = kwargs['proxy_mode']
267
+
242
268
  service_config.write()
243
269
 
244
270
  @workflow.command(
@@ -759,7 +785,42 @@ def __validate_app_dir(app_dir_path):
759
785
 
760
786
  def __validate_service_dir(service_dir_path):
761
787
  if not os.path.exists(service_dir_path):
762
- print(f"Error: {service_dir_path} does not exist, please scaffold this service", file=sys.stderr)
788
+ print(f"Error: '{service_dir_path}' does not exist, please scaffold this service", file=sys.stderr)
789
+ sys.exit(1)
790
+
791
+ def __validate_proxy_mode(proxy_mode: str) -> None:
792
+ valid_exact_matches = {
793
+ "regular": None,
794
+ "transparent": None,
795
+ "socks5": None,
796
+ }
797
+
798
+ valid_prefixes = {
799
+ "reverse": None,
800
+ "upstream": None
801
+ }
802
+
803
+ if proxy_mode in valid_exact_matches:
804
+ return
805
+
806
+ split_str = proxy_mode.split(":", 1)
807
+ if len(split_str) != 2:
808
+ print(f"Error: {proxy_mode} is invalid.", file=sys.stderr)
809
+ sys.exit(1)
810
+
811
+ prefix = split_str[0]
812
+ spec = split_str[1]
813
+
814
+ if prefix not in valid_prefixes:
815
+ print(f"Error: {proxy_mode} is invalid.", file=sys.stderr)
816
+ sys.exit(1)
817
+
818
+ # TODO: validate SPEC
819
+
820
+ def __validate_hostname(hostname: str) -> None:
821
+ hostname_regex = re.compile(r'^[a-zA-Z0-9.-]+$')
822
+ if not re.search(hostname_regex, hostname):
823
+ print(f"Error: {hostname} is invalid.", file=sys.stderr)
763
824
  sys.exit(1)
764
825
 
765
826
  def __workflow_create(app, **kwargs):
@@ -17,9 +17,7 @@ class JoinedRequestAdapter():
17
17
  if isinstance(payloads_delimitter, str):
18
18
  payloads_delimitter = payloads_delimitter.encode()
19
19
 
20
- self.__split_joined_request_string = joined_request_string.split(payloads_delimitter)
21
- if len(self.__split_joined_request_string) != 2:
22
- self.__split_joined_request_string = joined_request_string.split(payloads_delimitter.replace(b"\n", b"\r\n"))
20
+ self.__split_joined_request_string = self.raw_request_split(joined_request_string, payloads_delimitter)
23
21
 
24
22
  if len(self.__split_joined_request_string) != 2:
25
23
  raise ValueError(f"Could not split by {payloads_delimitter}")
@@ -47,7 +45,7 @@ class JoinedRequestAdapter():
47
45
  request_string = RequestString(None)
48
46
 
49
47
  delimitter = RequestStringCLRF
50
- request_string_toks = self.__split_joined_request_string[0].split(delimitter)
48
+ request_string_toks = self.repaired_string_toks(self.__split_joined_request_string[0], delimitter)
51
49
  request_string.set(self.raw_request_string or delimitter.join(request_string_toks[1:]))
52
50
  request_string.control = request_string_toks[0]
53
51
 
@@ -57,7 +55,7 @@ class JoinedRequestAdapter():
57
55
  response_string = ResponseString(None, None)
58
56
 
59
57
  delimitter = ResponseStringCLRF
60
- response_string_toks = self.__split_joined_request_string[1].split(delimitter)
58
+ response_string_toks = self.repaired_string_toks(self.__split_joined_request_string[1], delimitter)
61
59
  response_string.set(self.raw_response_string or delimitter.join(response_string_toks[1:]))
62
60
  response_string.control = response_string_toks[0]
63
61
 
@@ -68,4 +66,38 @@ class JoinedRequestAdapter():
68
66
 
69
67
  joined_request.request_string = self.build_request_string()
70
68
  joined_request.response_string = self.build_response_string()
71
- return joined_request
69
+ return joined_request
70
+
71
+ # If all CRLF characters have been replaced with LF e.g. visual studio code
72
+ # Then try to repair the raw string, see https://github.com/Stoobly/stoobly-agent/issues/415
73
+ @staticmethod
74
+ def repaired_string_toks(raw_string: bytes, delimitter: bytes):
75
+ toks = raw_string.split(delimitter)
76
+
77
+ if len(toks) == 1:
78
+ lf = b"\n"
79
+ toks = raw_string.split(lf)
80
+
81
+ # See for request: https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
82
+ # See for response: https://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html
83
+ i = 0
84
+ for line in toks:
85
+ i += 1
86
+
87
+ # On two lf characters, then the following lines are the body
88
+ if line == b'':
89
+ break
90
+
91
+ toks = toks[:i] + [lf.join(toks[i:])]
92
+
93
+ if len(toks) == 1:
94
+ raise ValueError(f"Could not split request by {delimitter}")
95
+
96
+ return toks
97
+
98
+ @staticmethod
99
+ def raw_request_split(raw_string: bytes, payloads_delimitter = REQUEST_DELIMITTER):
100
+ toks = raw_string.split(payloads_delimitter)
101
+ if len(toks) != 2:
102
+ toks = raw_string.split(payloads_delimitter.replace(b"\n", b"\r\n"))
103
+ return toks
@@ -2,10 +2,10 @@ import pdb
2
2
 
3
3
  from typing import List
4
4
 
5
+ from stoobly_agent.app.models.adapters.joined_request_adapter import JoinedRequestAdapter
5
6
  from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
6
7
  from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
7
8
  from stoobly_agent.app.models.factories.resource.local_db.helpers.scenario_snapshot import ScenarioSnapshot
8
- from stoobly_agent.app.proxy.record import REQUEST_STRING_CLRF
9
9
  from stoobly_agent.app.settings import Settings
10
10
  from stoobly_agent.lib.logger import bcolors
11
11
 
@@ -165,7 +165,12 @@ class Apply():
165
165
  self.__logger(f"{bcolors.WARNING}Skipping Request{bcolors.ENDC} {error}")
166
166
  return error, 301
167
167
 
168
- return self.__put_request(uuid, raw_request)
168
+ res, status = self.__put_request(uuid, raw_request)
169
+
170
+ if status != 200:
171
+ return f"{res} {snapshot.path}", status
172
+
173
+ return res, status
169
174
 
170
175
  def __apply_delete_scenario(self, uuid: str):
171
176
  res, status = self.scenario_model.destroy(uuid, force=self.__force)
@@ -227,8 +232,8 @@ class Apply():
227
232
 
228
233
  if not raw_request:
229
234
  return f"{request_snapshot.path} is missing", 400
230
-
231
- toks = raw_request.split(REQUEST_STRING_CLRF, 1)
235
+
236
+ toks = JoinedRequestAdapter.raw_request_split(raw_request)
232
237
  if len(toks) != 2:
233
238
  return f"{request_snapshot.path} contains an invalid request", 400
234
239
 
@@ -236,7 +241,7 @@ class Apply():
236
241
  res, status = self.__put_request(uuid, raw_request, scenario_id=scenario['id'])
237
242
 
238
243
  if status != 200:
239
- return res, status
244
+ return f"{res} {request_snapshot.path}", status
240
245
 
241
246
  snapshot_requests[uuid] = res
242
247
 
@@ -274,8 +279,6 @@ class Apply():
274
279
 
275
280
  if self.__logger and status == 200:
276
281
  self.__logger(f"{bcolors.OKGREEN}Created Request{bcolors.ENDC} {res['list'][0]['url']}")
277
- else:
278
- self.__logger(f"{bcolors.FAIL}{status}{bcolors.ENDC} {res}")
279
282
  elif status == 200:
280
283
  params = {
281
284
  'is_deleted': False,
@@ -287,7 +290,5 @@ class Apply():
287
290
  if self.__logger:
288
291
  if status == 200:
289
292
  self.__logger(f"{bcolors.OKCYAN}Updated Request{bcolors.ENDC} {res['url']}")
290
- else:
291
- self.__logger(f"{bcolors.FAIL}{status}{bcolors.ENDC} {res}")
292
293
 
293
294
  return res, status
stoobly_agent/cli.py CHANGED
@@ -115,12 +115,12 @@ def init(**kwargs):
115
115
  upstream proxy modes, SPEC is host specification in
116
116
  the form of "http[s]://host[:port]".
117
117
  ''')
118
- @click.option('--proxy-port', default=8080, help='Proxy service port.')
118
+ @click.option('--proxy-port', default=8080, type=click.IntRange(1, 65535), help='Proxy service port.')
119
119
  @click.option('--public-directory-path', help='Path to public files. Used for mocking requests.')
120
120
  @click.option('--response-fixtures-path', help='Path to response fixtures yaml. Used for mocking requests.')
121
121
  @click.option('--ssl-insecure', is_flag=True, default=False, help='Do not verify upstream server SSL/TLS certificates.')
122
122
  @click.option('--ui-host', default='0.0.0.0', help='Address to bind UI to.')
123
- @click.option('--ui-port', default=4200, help='UI service port.')
123
+ @click.option('--ui-port', default=4200, type=click.IntRange(1, 65535), help='UI service port.')
124
124
  def run(**kwargs):
125
125
  from .app.proxy.run import run as run_proxy
126
126
 
@@ -0,0 +1,38 @@
1
+ import pytest
2
+
3
+ from stoobly_agent.app.models.adapters.joined_request_adapter import RequestStringCLRF, JoinedRequestAdapter
4
+
5
+ class TestJoindRequestStringAdapter():
6
+
7
+ @pytest.fixture(scope='class', autouse=True)
8
+ def corrupted_raw_string(self):
9
+ toks = [
10
+ b"control",
11
+ b"header1",
12
+ b"header2",
13
+ b'',
14
+ b'body1',
15
+ b'body2'
16
+ ]
17
+ return b"\n".join(toks)
18
+
19
+ @pytest.fixture(scope='class', autouse=True)
20
+ def repaired_toks(self, corrupted_raw_string: bytes):
21
+ # [b'control', b'header1', b'header2', b'', b'body1\nbody2']
22
+ return JoinedRequestAdapter.repaired_string_toks(corrupted_raw_string, RequestStringCLRF)
23
+
24
+ def test_control(self, repaired_toks: list):
25
+ assert repaired_toks[0] == b'control'
26
+
27
+ def test_header1(self, repaired_toks: list):
28
+ assert repaired_toks[1] == b'header1'
29
+
30
+ def test_header2(self, repaired_toks: list):
31
+ assert repaired_toks[2] == b'header2'
32
+
33
+ def test_clrf(self, repaired_toks: list):
34
+ assert repaired_toks[3] == b''
35
+
36
+ def test_body(self, repaired_toks: list):
37
+ assert repaired_toks[4] == b'body1\nbody2'
38
+
@@ -1 +1 @@
1
- 1.9.0
1
+ 1.9.3
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: stoobly-agent
3
- Version: 1.9.2
3
+ Version: 1.9.4
4
4
  Summary: Record, mock, and test HTTP(s) requests. CLI agent for Stoobly
5
5
  License: Apache-2.0
6
6
  Author: Matt Le
@@ -1,4 +1,4 @@
1
- stoobly_agent/__init__.py,sha256=epZElDe2ihHM7JdgjopRIH49aMQpYH4SYIjmxXXMnY0,44
1
+ stoobly_agent/__init__.py,sha256=xHYLozasADB35BWCrRP8wlaWwr4WTbSnYvQyjTdvED4,44
2
2
  stoobly_agent/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  stoobly_agent/app/api/__init__.py,sha256=ctkB8KR-eXO0SFhj602huHiyvQ3PslFWd8fkcufgrAI,1000
4
4
  stoobly_agent/app/api/application_http_request_handler.py,sha256=Vvz53yB0bR7J-QqMAkLlhcZrA4P64ZEN7w8cMbgl6o0,5261
@@ -69,11 +69,11 @@ stoobly_agent/app/cli/request_cli.py,sha256=THNloW111l9MLE0oPg4y7hVXL1U7OXoz760g
69
69
  stoobly_agent/app/cli/scaffold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
70
70
  stoobly_agent/app/cli/scaffold/app.py,sha256=bgNc_dWZtgkuDAkS9aHSCMxCrjX907Phlfb2bOasswI,3435
71
71
  stoobly_agent/app/cli/scaffold/app_command.py,sha256=sliaYulMNmaxbRXpJIDtBDWEBGEv5Tht4rpErzC_xxw,2368
72
- stoobly_agent/app/cli/scaffold/app_config.py,sha256=Gs-BynV1vY7_PpTOenn2mqc7lBIu0vBx6as9vtEU1sk,818
73
- stoobly_agent/app/cli/scaffold/app_create_command.py,sha256=sy4017eiir9MK0TEbTi1NyG9mhOGlenGGVnq0GsGUrQ,966
72
+ stoobly_agent/app/cli/scaffold/app_config.py,sha256=UGVJ7DVmXh-o_gYBlAAEjngNIUZPQUiXXk2oMStcsPg,1075
73
+ stoobly_agent/app/cli/scaffold/app_create_command.py,sha256=0ogYliGbq1PYP5rFs-ML33q_Z4t_rAgPWjhk7rhnGw0,1153
74
74
  stoobly_agent/app/cli/scaffold/command.py,sha256=aoTsdkkBzyu7TkVSMdNQQGk0Gj874jNgFcjR14y3TuM,254
75
75
  stoobly_agent/app/cli/scaffold/config.py,sha256=HZU5tkvr3dkPr4JMXZtrJlu2wxxO-134Em6jReFFcq0,688
76
- stoobly_agent/app/cli/scaffold/constants.py,sha256=VEd8bXG6u919cmc9wGtxVQhoHzrRP2KLH0ZTjlDiUxI,2384
76
+ stoobly_agent/app/cli/scaffold/constants.py,sha256=dpCGvKzNtGQyaZfghfy0F6bJsZNp1KPACX3Z3rd13sw,2416
77
77
  stoobly_agent/app/cli/scaffold/containerized_app.py,sha256=dAjn4RwcZV3aEL0POUmrbF_DC-r9h6s1zx7gT2t45v0,175
78
78
  stoobly_agent/app/cli/scaffold/docker/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
79
  stoobly_agent/app/cli/scaffold/docker/app_builder.py,sha256=7z5pk5JKlRDHx2USxY-WurttLyyUkIVYfl34_u1x9dE,501
@@ -82,7 +82,7 @@ stoobly_agent/app/cli/scaffold/docker/constants.py,sha256=1khQdgTaQ89ykGRNdTQh_e
82
82
  stoobly_agent/app/cli/scaffold/docker/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
83
  stoobly_agent/app/cli/scaffold/docker/service/build_decorator.py,sha256=ZU7z4bkvdS3OK5O4fJhlA9_PNwnFtZW6t7vNF7V5obQ,1003
84
84
  stoobly_agent/app/cli/scaffold/docker/service/builder.py,sha256=4cIMSYvgrkGWVuuYymiwlrR829O91qQl9ML8FhaDMj4,5857
85
- stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py,sha256=avGKpxOTXtIvFguSAaihxQwRzUm6I7UfeZ9_WzvwXuA,3733
85
+ stoobly_agent/app/cli/scaffold/docker/service/configure_gateway.py,sha256=872qX9atoNWMXvSZcnG5cvVXarWShxBS9cJ_Q-YrCW8,3850
86
86
  stoobly_agent/app/cli/scaffold/docker/service/types.py,sha256=qB-yYHlu-PZDc0HYgTUvE5bWNpHxaSThC3JUG8okR1k,88
87
87
  stoobly_agent/app/cli/scaffold/docker/workflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
88
  stoobly_agent/app/cli/scaffold/docker/workflow/build_decorator.py,sha256=VKD9hXbJGRIWHS5IeYXeX0-FQ0F43zG8VmsegL6eYwA,703
@@ -142,10 +142,10 @@ stoobly_agent/app/cli/scaffold/templates/app/gateway/mock/.docker-compose.mock.y
142
142
  stoobly_agent/app/cli/scaffold/templates/app/gateway/record/.docker-compose.record.yml,sha256=eyLH2h33Peunus8M1sUKL9AALCG2ABhV_heiJKhvgwo,138
143
143
  stoobly_agent/app/cli/scaffold/templates/app/gateway/test/.docker-compose.test.yml,sha256=oJO6i0lsuQaQeIH80yoPZo3Vs0LzUAH2WRl853yLq6g,136
144
144
  stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.config.yml,sha256=XnLQZMzzMMIwVycjyPN5QXsmRztkTFAna1kIHYuDfJQ,19
145
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=bEmlH0ga_p3vsYZFagMGxvw47KscURmbYrfegMZb1CI,202
145
+ stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/.docker-compose.base.yml,sha256=bxrtZqf3YtaJCukzScslh5PgWC5q8xkGIP1wKJf33LA,111
146
146
  stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/.docker-compose.exec.yml,sha256=JN89sU5uRf6YqHvN_O63K8rwQIAPJHbhFDLFmuUjKNM,304
147
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml,sha256=FnCn64DjxyAiB2P_1JUwFmXslMR961nVZHkYiEXytlg,232
148
- stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml,sha256=t34FNYZboJSfrKnIB2oJ3UuE_mJaW77-hcbSn3sfWec,235
147
+ stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/mock/.docker-compose.mock.yml,sha256=2wg-YArlQVjat-bin_PLOnULQJW7mOmZjP7CFp6Knbs,242
148
+ stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/record/.docker-compose.record.yml,sha256=yDw8OSX7rFsW6J51K5Qf33qM-isGL8V4xPAYayIfnvQ,245
149
149
  stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.configure,sha256=SKvht2K_3tW08K24rl8_j0jMYOhq1k-GsVwhoHwjxYA,337
150
150
  stoobly_agent/app/cli/scaffold/templates/build/services/build/mock/.init,sha256=ecmyS6EZpa3op0CmO7bvd3pmAwRb0oLwj1qsTkee9_o,247
151
151
  stoobly_agent/app/cli/scaffold/templates/build/services/build/record/.configure,sha256=eXp9eKJ-TORE5B0zLW4-t43ogS3nxtj2SmSeDALbi1U,278
@@ -188,7 +188,7 @@ stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure,sha256=5k
188
188
  stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/init,sha256=EaoFDyoJbHc9Ui8ELYKmfweXAycJptVOQblszeh3XTE,94
189
189
  stoobly_agent/app/cli/scaffold/templates/workflow/record/lifecycle_hooks.py,sha256=4vaVc_gnDTCLEqtcZybIk5dcmXrKmGuesF6gc3-_kX8,473
190
190
  stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/configure,sha256=GAU7AfSPcyDSI9RJ7mynT83YqgN9r_E9HZYx0RXE1lU,279
191
- stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init,sha256=EaoFDyoJbHc9Ui8ELYKmfweXAycJptVOQblszeh3XTE,94
191
+ stoobly_agent/app/cli/scaffold/templates/workflow/test/bin/init,sha256=_jnP53I1yyCtv5TXOnMtQcEMbU43tpZ-za7s8Ely6P0,281
192
192
  stoobly_agent/app/cli/scaffold/templates/workflow/test/fixtures.yml,sha256=CJlZ_kugygZpmyqIauBjNZxqk7XyLaa3yl3AWj8KV28,259
193
193
  stoobly_agent/app/cli/scaffold/templates/workflow/test/lifecycle_hooks.py,sha256=U7mlzT_wBR3uhHSG6CAyt5tBUNAvdIrCw33gdB-F294,467
194
194
  stoobly_agent/app/cli/scaffold/templates/workflow/test/public/.gitignore,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -203,7 +203,7 @@ stoobly_agent/app/cli/scaffold/workflow_env.py,sha256=x8V5pJmIiklD3f2q2-qq-CORf4
203
203
  stoobly_agent/app/cli/scaffold/workflow_log_command.py,sha256=Bke4lMOMxuDUFuAx9nlXHbKgYMO4KAg9ASHvjz4aVWc,1372
204
204
  stoobly_agent/app/cli/scaffold/workflow_run_command.py,sha256=eF3aaK4OIZXYuSBEAeBnhAL7EZrS1G4mSYrJbEiXt2o,11082
205
205
  stoobly_agent/app/cli/scaffold/workflow_validate_command.py,sha256=Uo_yo6rVR1ZR7xpvsQvlH48AyMBVLRupd4G-bRjzm_Q,5584
206
- stoobly_agent/app/cli/scaffold_cli.py,sha256=zrXf2nUI5jRnf6x-j3kKmia8vH_AngjpweVnjdD_R3Q,30075
206
+ stoobly_agent/app/cli/scaffold_cli.py,sha256=huVZUpBUW6sfcNmhAlcQTKYVCCg0kHhcjezxt_Fm4sk,32043
207
207
  stoobly_agent/app/cli/scenario_cli.py,sha256=3J1EiJOvunkfWrEkOsanw-XrKkOk78ij_GjBlE9p7CE,8229
208
208
  stoobly_agent/app/cli/snapshot_cli.py,sha256=Uf6g6ivsD0hUY8G99eU0fxzS3FymncAhI70PxV7Uaac,11919
209
209
  stoobly_agent/app/cli/trace_cli.py,sha256=K7E-vx3JUcqEDSWOdIOi_AieKNQz7dBfmRrVvKDkzFI,4605
@@ -216,7 +216,7 @@ stoobly_agent/app/cli/types/snapshot_migration.py,sha256=4_Re46FKjsflcTOO3qhNsbW
216
216
  stoobly_agent/app/cli/types/test.py,sha256=1c458B7DFBWsEk5Q1CrZ2CUi84YzEzcs-W4qTcudwAk,714
217
217
  stoobly_agent/app/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
218
  stoobly_agent/app/models/adapters/__init__.py,sha256=cEEE--Bvrvk6DAsHx_uPgFhLnZJETP4zSBtWjMqyIKc,233
219
- stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=PUVhrZL_kYBasFByfcN78OHQ4aCeX9E7y2SLwn07_Vs,2591
219
+ stoobly_agent/app/models/adapters/joined_request_adapter.py,sha256=fSq16n3AAlxi8KJdBESHp3JGio_M9uzMnHbnQU8VI3w,3598
220
220
  stoobly_agent/app/models/adapters/mitmproxy/__init__.py,sha256=f14D_0y3_Pz6NpRXQMIwJQiWBGhwKk0Wk_AmlY3twh0,105
221
221
  stoobly_agent/app/models/adapters/mitmproxy/request/__init__.py,sha256=UZeK_-vxQKD02Tuu-YHBtpR92k5mK3XFgwmd99h2VHA,281
222
222
  stoobly_agent/app/models/adapters/mitmproxy/request/python_adapter.py,sha256=lWSppm63oOv7RDmTsUI0EmQkUWlQhRGdjtViBEDU-AA,377
@@ -287,7 +287,7 @@ stoobly_agent/app/models/factories/resource/stoobly/request_adapter.py,sha256=Zr
287
287
  stoobly_agent/app/models/factories/resource/stoobly/scenario_adapter.py,sha256=HnM4g5Qdv16QXj8u4JCiJm2Dbw9OhAxmn9e_R8oaHG4,1105
288
288
  stoobly_agent/app/models/header_model.py,sha256=m91upRZr8GfE5um0d5dguUESKigBMWhSyu_X3HFk28Y,1406
289
289
  stoobly_agent/app/models/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
290
- stoobly_agent/app/models/helpers/apply.py,sha256=ev8QmDvV7flFo82JUA6kBkofYCJ_lNXQGVIpknB94g8,8501
290
+ stoobly_agent/app/models/helpers/apply.py,sha256=VWHZnZ0BnpyyO1MU6eyohimn1HGQClkDQuvYk51BttA,8491
291
291
  stoobly_agent/app/models/helpers/create_request_params_service.py,sha256=o_VB2FsTGBX55UBBw1yQ8MtsJ4-0YXcxd4kNQ9l21nM,2070
292
292
  stoobly_agent/app/models/model.py,sha256=77ZTByQmH5sWBcSrCF3kG_C4muHggcFyH1DWsOhIgvg,1180
293
293
  stoobly_agent/app/models/query_param_model.py,sha256=EBj76phSJ9_45KgP0vIZGbkkG6-tSn_U1fNW_7qLy_4,1455
@@ -415,7 +415,7 @@ stoobly_agent/app/settings/types/remote_settings.py,sha256=4PvEGKULXM0zv29XTDzV7
415
415
  stoobly_agent/app/settings/types/ui_settings.py,sha256=BqPy2F32AbODqzi2mp2kRk28QVUydQIwVmvftn46pco,84
416
416
  stoobly_agent/app/settings/ui_settings.py,sha256=YDEUMPuJFh0SLHaGz6O-Gpz8nwsunNzeuc-TzO9OUbM,1170
417
417
  stoobly_agent/app/settings/url_rule.py,sha256=Qx7YrIpVRSC-4LeNiCAfCtE50Jou4423hojMW-4qUYg,954
418
- stoobly_agent/cli.py,sha256=sw8Ke5mCvzQ50X-zsb2Ld_zW4T6S58P0fN5GyKNOrcQ,10255
418
+ stoobly_agent/cli.py,sha256=FS-V4gkzDE4PgXokqvWV7cFzWq5v6WNMro6B5Mi9E-4,10317
419
419
  stoobly_agent/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
420
420
  stoobly_agent/config/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
421
421
  stoobly_agent/config/constants/alias_resolve_strategy.py,sha256=_R1tVqFnyGxCraVS5-dhSskaDj_X8-NthsY7i_bEt9M,119
@@ -693,6 +693,7 @@ stoobly_agent/test/app/cli/snapshot/snapshot_copy_test.py,sha256=Yg78-FhSiG_r6Jp
693
693
  stoobly_agent/test/app/cli/snapshot/snapshot_migrate_test.py,sha256=voEvblK6CMGCrSJDTHVmkUkLXj0auNb78jxlGiiBBQQ,7370
694
694
  stoobly_agent/test/app/cli/snapshot/snapshot_prune_test.py,sha256=bn4yUU7Eb4-6GnwnRaPZPi5Cn7XEaIsrJ_mB7jydgWw,6693
695
695
  stoobly_agent/test/app/cli/snapshot/snapshot_update_test.py,sha256=fILsX2M5j4wuLRP6LJTHe4CPB8gvaEbsSoYmFCHmKVk,4514
696
+ stoobly_agent/test/app/models/adapters/joined_rquest_adapter_test.py,sha256=bF7WMrAiASQDNzDTvIXGJhsWLNhfYOmdQpSDo0hyWYY,1098
696
697
  stoobly_agent/test/app/models/adapters/orm/joined_request_string_adapter_test.py,sha256=a2IHTk3l7aiLyYF7vtqissrk0MFTF2wlUBiaKWyJKfU,2667
697
698
  stoobly_agent/test/app/models/adapters/orm/request/orm_mitmproxy_request_adapter_test.py,sha256=PbJsAaxPUEbF9vM7DX4z858biWf4qlGnvE8KBuy8SgY,2763
698
699
  stoobly_agent/test/app/models/adapters/orm/request/orm_python_request_adapter_test.py,sha256=1rHywokXUj7z3laHhfnei8j1GVmAHDOULvRWmtIyWuQ,2488
@@ -707,7 +708,7 @@ stoobly_agent/test/app/models/factories/resource/local_db/helpers/log_test.py,sh
707
708
  stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py,sha256=a1SFLyEyRRLuADvAw6ckQQKORFXvyK1lyrbkaLWx8oU,3399
708
709
  stoobly_agent/test/app/models/factories/resource/local_db/request_adapter_test.py,sha256=Pzq1cBPnP9oSWG-p0c-VoymoHxgp483QmNwmV1b78RA,8453
709
710
  stoobly_agent/test/app/models/factories/resource/local_db/response_adapter_test.py,sha256=9P95EKH5rZGOrmRkRIDlQZqtiLJHk9735og18Ffwpfw,2204
710
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=dxfL-Qxjo7CWAIdjoCBmI5kW-pkW-YJU0ao7JHCDD80,5
711
+ stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION,sha256=JyvJHgCQJn-THkr7jL9H-s1Ea2VWcQz-X3BiHK64uxk,6
711
712
  stoobly_agent/test/app/models/schemas/.stoobly/db/stoobly_agent.sqlite3,sha256=ch8gNx6zIelLKQx65gwFx_LRNqUD3EC5xcHZ0ukIQiU,188416
712
713
  stoobly_agent/test/app/models/schemas/.stoobly/settings.yml,sha256=vLwMjweKOdod6tSLtIlyBefPQuNXq9wio4kBaODKtAU,726
713
714
  stoobly_agent/test/app/models/schemas/.stoobly/tmp/options.json,sha256=OTRzarwus48CTrItedXCrgQttJHSEZonEYc7R_knvYg,2212
@@ -748,8 +749,8 @@ stoobly_agent/test/mock_data/scaffold/docker-compose-local-service.yml,sha256=1W
748
749
  stoobly_agent/test/mock_data/scaffold/index.html,sha256=qJwuYajKZ4ihWZrJQ3BNObV5kf1VGnnm_vqlPJzdqLE,258
749
750
  stoobly_agent/test/mock_data/uspto.yaml,sha256=6U5se7C3o-86J4m9xpOk9Npias399f5CbfWzR87WKwE,7835
750
751
  stoobly_agent/test/test_helper.py,sha256=m_oAI7tmRYCNZdKfNqISWhMv3e44tjeYViQ3nTUfnos,1007
751
- stoobly_agent-1.9.2.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
752
- stoobly_agent-1.9.2.dist-info/METADATA,sha256=qMOKulRLao6AQJC0ctx4h0IyYP6Fd-ACe5NtoyuIY1U,3087
753
- stoobly_agent-1.9.2.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
754
- stoobly_agent-1.9.2.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
755
- stoobly_agent-1.9.2.dist-info/RECORD,,
752
+ stoobly_agent-1.9.4.dist-info/LICENSE,sha256=o93sj12cdoEOsTCjPaPFsw3Xq0SXs3pPcY-9reE2sEw,548
753
+ stoobly_agent-1.9.4.dist-info/METADATA,sha256=xDOjRS-p08haoG5tLVE1xI3X2qrM6gaplT5nmlIN8s8,3087
754
+ stoobly_agent-1.9.4.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
755
+ stoobly_agent-1.9.4.dist-info/entry_points.txt,sha256=aq5wix5oC8MDQtmyPGU0xaFrsjJg7WH28NmXh2sc3Z8,56
756
+ stoobly_agent-1.9.4.dist-info/RECORD,,