stoobly-agent 0.34.7__py3-none-any.whl → 0.34.9__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.
@@ -16,97 +16,20 @@ class SchemaBuilder:
16
16
  self.param_column_name: str = param_column_name
17
17
 
18
18
  def build(self, params):
19
- self.param_names_created = {}
20
- self.__traverse('', params, None)
21
- return self.param_names_created.values()
22
-
23
- ###
24
- #
25
- # @param name [String] name of current param
26
- # @param value [Object] value of current param
27
- # @param param [QueryParamName, BodyParamName, ResponseParamName] parent param record
28
- #
29
- def __traverse(self, name: str, value, param: RequestComponentName):
30
- if type(value) is list:
31
- self.__traverse_array(name, value, param)
32
- elif type(value) is dict:
33
- self.__traverse_hash(name, value, param)
34
-
35
- def __traverse_array(self, name: str, value, parent_param: RequestComponentName):
36
- columns = {
37
- 'endpoint_id': self.endpoint_id,
38
- 'name': f"{name.capitalize()}Element",
39
- 'query': f"{parent_param.get('query')}[*]" if parent_param else '[*]'
40
- }
41
- columns[self.param_column_name + '_id'] = parent_param['id'] if parent_param else None
42
-
43
- # Iterate
44
- types = {}
45
-
46
- for e in value:
47
- # Example of e = {'id': {'value': 0, 'required': False}}
48
- type_value = None
49
- if e.get('value') is not None:
50
- type_value = e.get('value')
51
- else:
52
- type_value = e
53
-
54
- _type = self.__infer_type(type_value)
55
-
56
- if types.get(_type) is None:
57
- columns['inferred_type'] = convert(_type)
58
- types[_type] = self.__find_or_create_by(columns)
59
-
60
- self.__traverse('', type_value, types[_type])
61
-
62
- def __traverse_hash(self, name, value, parent_param: RequestComponentName):
63
- # Iterate
64
- for k, v in value.items():
65
- columns = {
19
+ params_list = []
20
+ for literal_param in params:
21
+ param: RequestComponentName = {
66
22
  'endpoint_id': self.endpoint_id,
67
- 'inferred_type': convert(self.__infer_type(v['value'])),
68
- 'is_required': v.get('required') is True,
69
-
23
+ 'name': literal_param['name'],
24
+ 'query': literal_param['query'],
25
+ 'is_required': literal_param['required'],
26
+ 'inferred_type': convert(self.__infer_type(literal_param['value'])),
70
27
  'is_deterministic': True,
71
- 'name': k,
72
- 'query': f"{parent_param.get('query')}.{k}" if parent_param else k,
28
+ 'id': literal_param['id'],
29
+ f"{self.param_column_name}_id": literal_param['parent_id']
73
30
  }
74
- columns[self.param_column_name + '_id'] = parent_param['id'] if parent_param else None
75
- param = self.__find_or_create_by(columns)
76
-
77
- self.__traverse(k, v['value'], param)
78
-
79
- def __find_or_create_by(self, columns):
80
- param = self.__find_by(columns)
81
-
82
- if param is None:
83
- param = self.__create(columns)
84
-
85
- return param
86
-
87
- def __find_by(self, columns) -> Union[RequestComponentName, None]:
88
- for id, param_name in self.param_names_created.items():
89
- matches = True
90
-
91
- for key in columns:
92
- if key not in param_name:
93
- matches = False
94
- break
95
-
96
- if param_name[key] != columns[key]:
97
- matches = False
98
- break
99
-
100
- if matches:
101
- return param_name
102
-
103
- def __create(self, columns):
104
- param: RequestComponentName = columns.copy()
105
- param['id'] = len(self.param_names_created.keys()) + 1
106
-
107
- self.param_names_created[param['id']] = param
108
-
109
- return param
31
+ params_list.append(param)
32
+ return params_list
110
33
 
111
34
  def __infer_type(self, val) -> str:
112
35
  return str(val.__class__)
@@ -76,7 +76,7 @@ def parse_multipart_form_data(content, content_type) -> Dict[bytes, bytes]:
76
76
 
77
77
  params_array = []
78
78
  for ele in decoded_multipart:
79
- params_array.append((decode(ele[0]), decode(ele[1])))
79
+ params_array.append((decode(ele[0]), ele[1]))
80
80
 
81
81
  return MultiDict(params_array)
82
82
 
@@ -58,7 +58,6 @@ def __get_intercept_handler_path():
58
58
  def __with_static_options(config: MitmproxyConfig, cli_options):
59
59
  options = (
60
60
  'block_global=false',
61
- 'flow_detail=1',
62
61
  f"scripts={__get_intercept_handler_path()}",
63
62
  'upstream_cert=false'
64
63
  )
@@ -45,8 +45,18 @@ def __request_excluded(request: MitmproxyRequest, exclude_rules: List[FirewallRu
45
45
  return False
46
46
 
47
47
  def __request_included(request: MitmproxyRequest, include_rules: List[FirewallRule]):
48
+ if not include_rules:
49
+ return True
50
+
48
51
  method = request.method.upper()
49
52
  rules = list(filter(lambda rule: method in rule.methods, include_rules))
53
+
54
+ # If there are include rules, but none that match the request's method,
55
+ # then we know that none of the include rules will match the request
56
+ if len(include_rules) > 0 and len(rules) == 0:
57
+ Logger.instance().info(f"{bcolors.OKBLUE}{request.method} {request.url} not included by firewall rule{bcolors.ENDC}")
58
+ return False
59
+
50
60
  patterns = list(map(lambda rule: rule.pattern, rules))
51
61
  if not __include(request, patterns):
52
62
  Logger.instance().info(f"{bcolors.OKBLUE}{request.method} {request.url} not included by firewall rule{bcolors.ENDC}")
stoobly_agent/cli.py CHANGED
@@ -86,7 +86,16 @@ def init(**kwargs):
86
86
  Passphrase for decrypting the private key provided in the --cert option. Note that passing cert_passphrase on the command line makes your passphrase visible in your system's process list. Specify it in
87
87
  config.yaml to avoid this.
88
88
  ''')
89
+ @click.option('--confdir', default=os.path.join(os.path.expanduser('~'), '.mitmproxy'), help='Location of the default mitmproxy configuration files.')
89
90
  @click.option('--connection-strategy', help=', '.join(CONNECTION_STRATEGIES), type=click.Choice(CONNECTION_STRATEGIES))
91
+ @click.option('--flow-detail', default='1', type=click.Choice(['1', '2', '3', '4']), help='''
92
+ The display detail level for flows in mitmdump: 0 (quiet) to 4 (very verbose).
93
+ 0: no output
94
+ 1: shortened request URL with response status code
95
+ 2: full request URL with response status code and HTTP headers
96
+ 3: 2 + truncated response content, content of WebSocket and TCP messages (content_view_lines_cutoff: 512)
97
+ 4: 3 + nothing is truncated
98
+ ''')
90
99
  @click.option('--headless', is_flag=True, default=False, help='Disable starting UI.')
91
100
  @click.option('--intercept', is_flag=True, default=False, help='Enable intercept on run.')
92
101
  @click.option('--log-level', default=logger.INFO, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]), help='''
@@ -218,7 +227,7 @@ def __build_request_from_curl(**kwargs):
218
227
 
219
228
  if len(toks) != 2:
220
229
  continue
221
-
230
+
222
231
  headers[toks[0].strip()] = toks[1].strip()
223
232
 
224
233
  return requests.Request(
@@ -226,4 +235,4 @@ def __build_request_from_curl(**kwargs):
226
235
  headers=headers,
227
236
  method=kwargs['request'],
228
237
  url=kwargs['url']
229
- )
238
+ )
@@ -61,39 +61,49 @@ class TestOpenApiEndpointAdapterMissingInfo():
61
61
  "response_param_names": [
62
62
  {
63
63
  "endpoint_id": 1,
64
- "inferred_type": "Integer",
65
- "is_required": True,
64
+ "name": "Element",
65
+ "query": "[*]",
66
+ "is_required": False,
67
+ "inferred_type": "Hash",
66
68
  "is_deterministic": True,
67
- "name": "id",
68
- "query": "id",
69
- "response_param_name_id": None,
70
69
  "id": 1,
70
+ "response_param_name_id": None
71
+ },
72
+ {
73
+ "endpoint_id": 1,
74
+ "name": "id",
75
+ "query": "[*].id",
76
+ "is_required": True,
77
+ "inferred_type": "Integer",
78
+ "is_deterministic": True,
79
+ "id": 2,
80
+ "response_param_name_id": 1,
71
81
  "values": [
72
82
  0
73
83
  ]
74
84
  },
75
85
  {
76
86
  "endpoint_id": 1,
77
- "inferred_type": "String",
87
+ "name": "name",
88
+ "query": "[*].name",
78
89
  "is_required": True,
90
+ "inferred_type": "String",
79
91
  "is_deterministic": True,
80
- "name": "name",
81
- "query": "name",
82
- "response_param_name_id": None,
83
- "id": 2,
92
+ "id": 3,
93
+ "response_param_name_id": 1,
84
94
  "values": [
85
95
  ""
86
96
  ]
87
97
  },
88
98
  {
89
99
  "endpoint_id": 1,
90
- "inferred_type": "String",
100
+ "name": "tag",
101
+ "query": "[*].tag",
91
102
  "is_required": False,
103
+ "inferred_type": "String",
92
104
  "is_deterministic": True,
93
- "name": "tag",
94
- "query": "tag",
95
- "response_param_name_id": None,
96
- "id": 3
105
+ "id": 4,
106
+ "response_param_name_id": 1
97
107
  }
98
108
  ],
99
109
  }
@@ -62,42 +62,52 @@ class TestOpenApiEndpointAdapterMissingOauthScopes():
62
62
  'name': 'x-next',
63
63
  },
64
64
  ],
65
- "response_param_names": [
65
+ "response_param_names": [
66
66
  {
67
67
  "endpoint_id": 1,
68
- "inferred_type": "Integer",
69
- "is_required": True,
68
+ "name": "Element",
69
+ "query": "[*]",
70
+ "is_required": False,
71
+ "inferred_type": "Hash",
70
72
  "is_deterministic": True,
71
- "name": "id",
72
- "query": "id",
73
- "response_param_name_id": None,
74
73
  "id": 1,
74
+ "response_param_name_id": None
75
+ },
76
+ {
77
+ "endpoint_id": 1,
78
+ "name": "id",
79
+ "query": "[*].id",
80
+ "is_required": True,
81
+ "inferred_type": "Integer",
82
+ "is_deterministic": True,
83
+ "id": 2,
84
+ "response_param_name_id": 1,
75
85
  "values": [
76
86
  0
77
87
  ]
78
88
  },
79
89
  {
80
90
  "endpoint_id": 1,
81
- "inferred_type": "String",
91
+ "name": "name",
92
+ "query": "[*].name",
82
93
  "is_required": True,
94
+ "inferred_type": "String",
83
95
  "is_deterministic": True,
84
- "name": "name",
85
- "query": "name",
86
- "response_param_name_id": None,
87
- "id": 2,
96
+ "id": 3,
97
+ "response_param_name_id": 1,
88
98
  "values": [
89
99
  ""
90
100
  ]
91
101
  },
92
102
  {
93
103
  "endpoint_id": 1,
94
- "inferred_type": "String",
104
+ "name": "tag",
105
+ "query": "[*].tag",
95
106
  "is_required": False,
107
+ "inferred_type": "String",
96
108
  "is_deterministic": True,
97
- "name": "tag",
98
- "query": "tag",
99
- "response_param_name_id": None,
100
- "id": 3
109
+ "id": 4,
110
+ "response_param_name_id": 1
101
111
  }
102
112
  ],
103
113
  }
@@ -61,39 +61,49 @@ class TestOpenApiEndpointAdapterMissingServers():
61
61
  "response_param_names": [
62
62
  {
63
63
  "endpoint_id": 1,
64
- "inferred_type": "Integer",
65
- "is_required": True,
64
+ "name": "Element",
65
+ "query": "[*]",
66
+ "is_required": False,
67
+ "inferred_type": "Hash",
66
68
  "is_deterministic": True,
67
- "name": "id",
68
- "query": "id",
69
- "response_param_name_id": None,
70
69
  "id": 1,
70
+ "response_param_name_id": None
71
+ },
72
+ {
73
+ "endpoint_id": 1,
74
+ "name": "id",
75
+ "query": "[*].id",
76
+ "is_required": True,
77
+ "inferred_type": "Integer",
78
+ "is_deterministic": True,
79
+ "id": 2,
80
+ "response_param_name_id": 1,
71
81
  "values": [
72
82
  0
73
83
  ]
74
84
  },
75
85
  {
76
86
  "endpoint_id": 1,
77
- "inferred_type": "String",
87
+ "name": "name",
88
+ "query": "[*].name",
78
89
  "is_required": True,
90
+ "inferred_type": "String",
79
91
  "is_deterministic": True,
80
- "name": "name",
81
- "query": "name",
82
- "response_param_name_id": None,
83
- "id": 2,
92
+ "id": 3,
93
+ "response_param_name_id": 1,
84
94
  "values": [
85
95
  ""
86
96
  ]
87
97
  },
88
98
  {
89
99
  "endpoint_id": 1,
90
- "inferred_type": "String",
100
+ "name": "tag",
101
+ "query": "[*].tag",
91
102
  "is_required": False,
103
+ "inferred_type": "String",
92
104
  "is_deterministic": True,
93
- "name": "tag",
94
- "query": "tag",
95
- "response_param_name_id": None,
96
- "id": 3
105
+ "id": 4,
106
+ "response_param_name_id": 1
97
107
  }
98
108
  ],
99
109
  }
@@ -73,8 +73,9 @@ class TestOpenApiEndpointAdapter():
73
73
  'endpoint_id': 1,
74
74
  'id': 2,
75
75
  'inferred_type': 'String',
76
+ 'is_deterministic': True,
76
77
  'is_required': False,
77
- 'name': 'TagsElement',
78
+ 'name': 'tagsElement',
78
79
  'query': 'tags[*]',
79
80
  'query_param_name_id': 1,
80
81
  },
@@ -104,6 +105,7 @@ class TestOpenApiEndpointAdapter():
104
105
  'endpoint_id': 1,
105
106
  'id': 1,
106
107
  'inferred_type': 'Hash',
108
+ 'is_deterministic': True,
107
109
  'is_required': False,
108
110
  'name': 'Element',
109
111
  'query': '[*]',
@@ -365,6 +367,56 @@ class TestOpenApiEndpointAdapter():
365
367
  'query': 'apis',
366
368
  'response_param_name_id': None,
367
369
  },
370
+ {
371
+ 'endpoint_id': 1,
372
+ 'id': 3,
373
+ 'inferred_type': 'Hash',
374
+ 'is_deterministic': True,
375
+ 'is_required': False,
376
+ 'name': 'apisElement',
377
+ 'query': 'apis[*]',
378
+ 'response_param_name_id': 2,
379
+ },
380
+ {
381
+ 'endpoint_id': 1,
382
+ 'id': 4,
383
+ 'inferred_type': 'String',
384
+ 'is_deterministic': True,
385
+ 'is_required': False,
386
+ 'name': 'apiKey',
387
+ 'query': 'apis[*].apiKey',
388
+ 'response_param_name_id': 3,
389
+ },
390
+ {
391
+ 'endpoint_id': 1,
392
+ 'id': 5,
393
+ 'inferred_type': 'String',
394
+ 'is_deterministic': True,
395
+ 'is_required': False,
396
+ 'name': 'apiVersionNumber',
397
+ 'query': 'apis[*].apiVersionNumber',
398
+ 'response_param_name_id': 3,
399
+ },
400
+ {
401
+ 'endpoint_id': 1,
402
+ 'id': 6,
403
+ 'inferred_type': 'String',
404
+ 'is_deterministic': True,
405
+ 'is_required': False,
406
+ 'name': 'apiUrl',
407
+ 'query': 'apis[*].apiUrl',
408
+ 'response_param_name_id': 3,
409
+ },
410
+ {
411
+ 'endpoint_id': 1,
412
+ 'id': 7,
413
+ 'inferred_type': 'String',
414
+ 'is_deterministic': True,
415
+ 'is_required': False,
416
+ 'name': 'apiDocumentationUrl',
417
+ 'query': 'apis[*].apiDocumentationUrl',
418
+ 'response_param_name_id': 3,
419
+ },
368
420
  ],
369
421
  }
370
422
 
@@ -475,6 +527,18 @@ class TestOpenApiEndpointAdapter():
475
527
  'type': 'static',
476
528
  },
477
529
  ],
530
+ 'response_param_names': [
531
+ {
532
+ 'endpoint_id': 3,
533
+ 'id': 1,
534
+ 'inferred_type': 'Hash',
535
+ 'is_deterministic': True,
536
+ 'is_required': False,
537
+ 'name': 'Element',
538
+ 'query': '[*]',
539
+ 'response_param_name_id': None,
540
+ },
541
+ ],
478
542
  }
479
543
 
480
544
  @pytest.fixture(scope='class')
@@ -485,6 +549,7 @@ class TestOpenApiEndpointAdapter():
485
549
  http_endpoint_version['body_param_names'][0]['endpoint_id'] = 6
486
550
  http_endpoint_version['body_param_names'][1]['endpoint_id'] = 6
487
551
  http_endpoint_version['body_param_names'][2]['endpoint_id'] = 6
552
+ http_endpoint_version['response_param_names'][0]['endpoint_id'] = 6
488
553
  return http_endpoint_version
489
554
 
490
555
  def test_adapt_from_file(self, open_api_endpoint_adapter, uspto_file_path, expected_get_root_https, expected_get_root_http, expected_get_dataset_version_fields_https, expected_get_dataset_version_fields_http, expected_post_dataset_version_records_https, expected_post_dataset_version_records_http):
@@ -694,14 +759,14 @@ class TestOpenApiEndpointAdapter():
694
759
  'values': [0]
695
760
  },
696
761
  {
697
- 'body_param_name_id': None,
762
+ 'body_param_name_id': 4,
698
763
  'endpoint_id': 2,
699
764
  'id': 7,
700
765
  'inferred_type': 'Hash',
701
766
  'is_deterministic': True,
702
767
  'is_required': False,
703
768
  'name': 'adoption',
704
- 'query': 'adoption'
769
+ 'query': 'extra.adoption'
705
770
  },
706
771
  {
707
772
  'body_param_name_id': 7,
@@ -711,7 +776,7 @@ class TestOpenApiEndpointAdapter():
711
776
  'is_deterministic': True,
712
777
  'is_required': True,
713
778
  'name': 'adopted',
714
- 'query': 'adoption.adopted',
779
+ 'query': 'extra.adoption.adopted',
715
780
  'values': [False]
716
781
  },
717
782
  {
@@ -722,7 +787,7 @@ class TestOpenApiEndpointAdapter():
722
787
  'is_deterministic': True,
723
788
  'is_required': False,
724
789
  'name': 'shelter',
725
- 'query': 'adoption.shelter'
790
+ 'query': 'extra.adoption.shelter'
726
791
  },
727
792
  {
728
793
  'body_param_name_id': None,
@@ -840,8 +905,9 @@ class TestOpenApiEndpointAdapter():
840
905
  'endpoint_id': 1,
841
906
  'id': 7,
842
907
  'inferred_type': 'String',
908
+ 'is_deterministic': True,
843
909
  'is_required': False,
844
- 'name': 'PhotourlsElement',
910
+ 'name': 'photoUrlsElement',
845
911
  'query': 'photoUrls[*]'
846
912
  },
847
913
  {
@@ -859,8 +925,9 @@ class TestOpenApiEndpointAdapter():
859
925
  'endpoint_id': 1,
860
926
  'id': 9,
861
927
  'inferred_type': 'Hash',
928
+ 'is_deterministic': True,
862
929
  'is_required': False,
863
- 'name': 'TagsElement',
930
+ 'name': 'tagsElement',
864
931
  'query': 'tags[*]'
865
932
  },
866
933
  {
@@ -971,8 +1038,9 @@ class TestOpenApiEndpointAdapter():
971
1038
  'endpoint_id': 1,
972
1039
  'id': 7,
973
1040
  'inferred_type': 'String',
1041
+ 'is_deterministic': True,
974
1042
  'is_required': False,
975
- 'name': 'PhotourlsElement',
1043
+ 'name': 'photoUrlsElement',
976
1044
  'query': 'photoUrls[*]',
977
1045
  'response_param_name_id': 6,
978
1046
  },
@@ -990,8 +1058,9 @@ class TestOpenApiEndpointAdapter():
990
1058
  'endpoint_id': 1,
991
1059
  'id': 9,
992
1060
  'inferred_type': 'Hash',
1061
+ 'is_deterministic': True,
993
1062
  'is_required': False,
994
- 'name': 'TagsElement',
1063
+ 'name': 'tagsElement',
995
1064
  'query': 'tags[*]',
996
1065
  'response_param_name_id': 8,
997
1066
  },
@@ -1043,6 +1112,7 @@ class TestOpenApiEndpointAdapter():
1043
1112
  'endpoint_id': 14,
1044
1113
  'id': 1,
1045
1114
  'inferred_type': 'Hash',
1115
+ 'is_deterministic': True,
1046
1116
  'is_required': False,
1047
1117
  'name': 'Element',
1048
1118
  'query': '[*]'
@@ -7,7 +7,10 @@ class TestSchemaBuilder():
7
7
 
8
8
  def test_it_builds_single_level_dict(self):
9
9
  builder = SchemaBuilder(-1, 'query_param_name')
10
- param_names = builder.build({ 'id': {'value':1}, 'name': {'value':'test'} })
10
+ param_names = builder.build([
11
+ {'name': 'id', 'value': 0, 'query': 'id', 'required': False, 'id': 1, "parent_id": None},
12
+ {'name': 'name', 'value': '', 'query': 'name', 'required': False, 'id': 2, "parent_id": None}
13
+ ])
11
14
 
12
15
  param_names_index = self.__index(param_names)
13
16
  expected_queries = ['id.Integer', 'name.String']
@@ -17,11 +20,17 @@ class TestSchemaBuilder():
17
20
 
18
21
  def test_it_builds_two_level_dict(self):
19
22
  builder = SchemaBuilder(-1, 'query_param_name')
20
- param_names = builder.build({ 'list': {'value': [{'value':{ 'id': {'value':1}, 'name': {'value':'test'} }}] }, 'total': {'value':1} })
23
+ param_names = builder.build([
24
+ {'name': 'list', 'value': [], 'query': 'list', 'required': False, 'id': 1, "parent_id": None},
25
+ {'name': 'ListElement', 'value': {}, 'query': 'list[*]', 'required': False, 'id': 2, "parent_id": 1},
26
+ {'name': 'id', 'value': 0, 'query': 'list[*].id', 'required': False, 'id': 3, "parent_id": 2},
27
+ {'name': 'name', 'value': '', 'query': 'list[*].name', 'required': False, 'id': 4, "parent_id": 2},
28
+ {'name': 'total', 'value': 0, 'query': 'total', 'required': False, 'id': 5, "parent_id": None}
29
+ ])
21
30
 
22
31
  param_names_index = self.__index(param_names)
23
32
  expected_queries = [
24
- 'list.Array', 'total.Integer', 'list[*].id.Integer', 'list[*].name.String'
33
+ 'list.Array', 'list[*].Hash', 'list[*].id.Integer', 'list[*].name.String', 'total.Integer'
25
34
  ]
26
35
 
27
36
  for query in expected_queries:
@@ -29,11 +38,13 @@ class TestSchemaBuilder():
29
38
 
30
39
  def test_it_builds_single_level_array(self):
31
40
  builder = SchemaBuilder(-1, 'body_param_name')
32
- param_names = builder.build([{'value':1}, {'value':'a'}])
41
+ param_names = builder.build([
42
+ {'name': 'Element', 'value': 0, 'query': '[*]', 'required': False, 'id': 1, "parent_id": None},
43
+ ])
33
44
 
34
45
  param_names_index = self.__index(param_names)
35
46
  expected_queries = [
36
- '[*].Integer', '[*].String'
47
+ '[*].Integer'
37
48
  ]
38
49
 
39
50
  for query in expected_queries:
@@ -41,11 +52,15 @@ class TestSchemaBuilder():
41
52
 
42
53
  def test_it_builds_item_and_single_level_array(self):
43
54
  builder = SchemaBuilder(-1, 'query_param_name')
44
- param_names = builder.build({ 'tags': {'value':[{'value':''}]}, 'limit': {'value':0} })
55
+ param_names = builder.build([
56
+ {'name': 'tags', 'value': [], 'query': 'tags', 'required': False, 'id': 1, "parent_id": None},
57
+ {'name': 'TagsElement', 'value': '', 'query': 'tags[*]', 'required': False, 'id': 2, "parent_id": 1},
58
+ {'name': 'limit', 'value': 0, 'query': 'limit', 'required': False, 'id': 3, "parent_id": None}
59
+ ])
45
60
 
46
61
  param_names_index = self.__index(param_names)
47
62
  expected_queries = [
48
- 'limit.Integer', 'tags.Array', 'tags[*].String'
63
+ 'tags.Array', 'tags[*].String', 'limit.Integer'
49
64
  ]
50
65
 
51
66
  for query in expected_queries:
@@ -1 +1 @@
1
- 0.34.6
1
+ 0.34.8
@@ -16,11 +16,11 @@ class TestMultipart():
16
16
  def test_decodes_response(self, multipart_string: bytes, content_type: str):
17
17
  multidict = decode_response(multipart_string, content_type)
18
18
 
19
- assert multidict.get('author') == 'John Smith'
20
- assert multidict.get('file') == 'Hello World'
19
+ assert multidict.get('author') == b'John Smith'
20
+ assert multidict.get('file') == b'Hello World'
21
21
 
22
22
  def test_encodes_response(self, content_type: str):
23
- expected_params = { 'author': 'John Smith', 'file': 'Hello World'}
23
+ expected_params = { 'author': b'John Smith', 'file': b'Hello World'}
24
24
  multipart_string = encode_response(expected_params, content_type)
25
25
 
26
26
  multidict = decode_response(multipart_string, content_type)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: stoobly-agent
3
- Version: 0.34.7
3
+ Version: 0.34.9
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