insightconnect-plugin-runtime 6.2.1__py3-none-any.whl → 6.2.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.
@@ -1,27 +1,27 @@
1
+ import importlib.metadata as importlib_metadata
1
2
  import json
2
- import subprocess
3
- import yaml
4
3
  import os
5
- import pkg_resources
6
4
  import signal
7
- from flask import jsonify, request, abort, make_response, Blueprint
8
- from werkzeug.exceptions import InternalServerError, HTTPException
5
+ import subprocess
9
6
  from typing import Any, Dict
10
7
 
11
8
  import structlog
9
+ import yaml
10
+ from flask import Blueprint, abort, jsonify, make_response, request
11
+ from werkzeug.exceptions import HTTPException, InternalServerError
12
12
 
13
+ from insightconnect_plugin_runtime.api.schemas import (
14
+ ActionTriggerDetailsSchema,
15
+ ConnectionDetailsSchema,
16
+ PluginInfoSchema,
17
+ TaskDetailsSchema,
18
+ )
13
19
  from insightconnect_plugin_runtime.exceptions import (
14
20
  ClientException,
15
- ServerException,
16
- LoggedException,
17
21
  ConnectionTestException,
22
+ LoggedException,
18
23
  PluginException,
19
- )
20
- from insightconnect_plugin_runtime.api.schemas import (
21
- PluginInfoSchema,
22
- ActionTriggerDetailsSchema,
23
- TaskDetailsSchema,
24
- ConnectionDetailsSchema,
24
+ ServerException,
25
25
  )
26
26
  from insightconnect_plugin_runtime.util import OutputMasker
27
27
 
@@ -780,11 +780,10 @@ class Endpoints:
780
780
 
781
781
  def get_plugin_sdk_version(self):
782
782
  try:
783
- version = pkg_resources.require("insightconnect-plugin-runtime")[0].version
783
+ version = importlib_metadata.version("insightconnect-plugin-runtime")
784
784
  except Exception:
785
785
  self.logger.warn("Unable to get SDK version")
786
786
  version = "0.0.0"
787
-
788
787
  return version
789
788
 
790
789
  def add_plugin_custom_config(
@@ -10,7 +10,7 @@ import subprocess
10
10
  import time
11
11
  from datetime import datetime, timedelta
12
12
  from io import IOBase
13
- from typing import Any, Callable, Dict, List, Union, Tuple
13
+ from typing import Any, Callable, Dict, List, Union, Tuple, Optional
14
14
  from urllib import request
15
15
  from hashlib import sha1
16
16
  from json import JSONDecodeError
@@ -21,6 +21,7 @@ from insightconnect_plugin_runtime.exceptions import (
21
21
  PluginException,
22
22
  HTTPStatusCodes,
23
23
  ResponseExceptionData,
24
+ APIException
24
25
  )
25
26
 
26
27
  CAMEL_CASE_REGEX = r"\b[a-z0-9]+([A-Z][a-z]+[0-9]*)*\b"
@@ -33,17 +34,45 @@ DEFAULTS_HOURS_AGO = 24
33
34
  logger = logging.getLogger()
34
35
 
35
36
 
36
- def hash_sha1(log: Dict) -> str:
37
+ def hash_sha1(log: Dict[str, Any], keys: Optional[List[str]] = None) -> str:
37
38
  """
38
39
  Iterate through a dictionary and hash each value.
40
+ Optionally only hash certain keys in the dictionary.
41
+
39
42
  :param log: Dictionary to be hashed.
40
- :type Dict:
43
+ :param keys: Optional list of keys to hash on if provided
44
+
41
45
  :return: Hex digest of hash.
42
- :rtype: str
43
46
  """
47
+
44
48
  hash_ = sha1() # nosec B303
45
- for key, value in log.items():
49
+
50
+ # Leaving no room for developer error and ensuring they know exactly where it went wrong
51
+ # if they provide a key not in list format
52
+ if keys is not None and not isinstance(keys, list):
53
+ raise TypeError(
54
+ f"The 'keys' parameter must be a list or None in the 'hash_sha1' function, not {type(keys).__name__}"
55
+ )
56
+
57
+ # Hash all key-value pairs if no keys provided
58
+ if keys is None:
59
+ items_to_hash = log.items()
60
+
61
+ # Otherwise, only include specified keys
62
+ else:
63
+ items_to_hash = []
64
+ for key in keys:
65
+ if key in log:
66
+ items_to_hash.append((key, log[key]))
67
+
68
+ # Alert if the key is not found in the log
69
+ else:
70
+ raise KeyError(f"Key '{key}' not found in the provided log.")
71
+
72
+ # Iterate through items to hash and hash
73
+ for key, value in items_to_hash:
46
74
  hash_.update(f"{key}{value}".encode(ENCODE_TYPE))
75
+
47
76
  return hash_.hexdigest()
48
77
 
49
78
 
@@ -83,6 +112,7 @@ def make_request(
83
112
  exception_custom_configs: Dict[int, Exception] = {},
84
113
  exception_data_location: str = None,
85
114
  allowed_status_codes: List[str] = [],
115
+ max_response_size: int = None,
86
116
  ) -> Tuple[requests.Response, Dict]:
87
117
  """
88
118
  Makes a HTTP request while checking for RequestErrors and JSONDecodeErrors
@@ -105,6 +135,8 @@ def make_request(
105
135
  :type str:
106
136
  :param allowed_status_codes: Status codes that will not raise an exception.
107
137
  :type List[str]:
138
+ :param max_response_size: Raise an error if the stream content is bigger than this specified size
139
+ :type int:
108
140
 
109
141
  :return: The request response and the response JSON.
110
142
  :rtype: Tuple[Response, Dict]
@@ -120,6 +152,19 @@ def make_request(
120
152
  cert=cert,
121
153
  stream=stream,
122
154
  )
155
+
156
+ # Before we close this session check and download all content, check if the returned content is too large.
157
+ # This may not be supported on all APIs as they need to support streaming and return the content-length
158
+ # header but adding this extra check will be beneficial for memory usage to those that do support it.
159
+ if stream and max_response_size:
160
+ resp_size = response.headers.get("content-length", "0")
161
+ if int(resp_size) > max_response_size:
162
+ raise APIException(
163
+ status_code=400,
164
+ cause=f"API response is exceeding allowed limit of {max_response_size} bytes.",
165
+ assistance="Please update the parameters to reduce the size of the data being returned.",
166
+ data=f"Content length returned was {resp_size} and max allowed is {max_response_size}",
167
+ )
123
168
  except requests.exceptions.Timeout as exception:
124
169
  raise PluginException(
125
170
  preset=PluginException.Preset.TIMEOUT, data=str(exception)
@@ -137,7 +182,12 @@ def make_request(
137
182
  raise PluginException(
138
183
  preset=PluginException.Preset.UNKNOWN, data=str(exception)
139
184
  )
140
- response_handler(response, exception_custom_configs, exception_data_location, allowed_status_codes)
185
+ response_handler(
186
+ response,
187
+ exception_custom_configs,
188
+ exception_data_location,
189
+ allowed_status_codes,
190
+ )
141
191
  return response
142
192
 
143
193
 
@@ -192,7 +242,7 @@ def request_error_handling(
192
242
  exception.response,
193
243
  data_location=exception_data_location,
194
244
  custom_configs=custom_configs,
195
- allowed_status_codes=allowed_status_codes
245
+ allowed_status_codes=allowed_status_codes,
196
246
  )
197
247
  else:
198
248
  raise PluginException(
@@ -443,15 +493,19 @@ def convert_dict_to_snake_case(
443
493
 
444
494
  if isinstance(input_dict, list):
445
495
  return [
446
- convert_dict_to_snake_case(element)
447
- if isinstance(element, (dict, list))
448
- else element
496
+ (
497
+ convert_dict_to_snake_case(element)
498
+ if isinstance(element, (dict, list))
499
+ else element
500
+ )
449
501
  for element in input_dict
450
502
  ]
451
503
  return {
452
- convert_to_snake_case(key): convert_dict_to_snake_case(value)
453
- if isinstance(value, (dict, list))
454
- else value
504
+ convert_to_snake_case(key): (
505
+ convert_dict_to_snake_case(value)
506
+ if isinstance(value, (dict, list))
507
+ else value
508
+ )
455
509
  for key, value in input_dict.items()
456
510
  }
457
511
 
@@ -665,7 +719,7 @@ def open_cachefile(cache_file, append=False):
665
719
  logger.info("OpenCacheFile: %s created", cache_file)
666
720
  f = open(cache_file, "a+" if append else "r+")
667
721
  return f
668
- logging.error("OpenCacheFile: %s directory or does not exist", cache_dir)
722
+ logger.error("OpenCacheFile: %s directory or does not exist", cache_dir)
669
723
 
670
724
 
671
725
  def remove_cachefile(cache_file):
@@ -748,11 +802,11 @@ def open_url(url, timeout=None, verify=True, **kwargs):
748
802
  urlobj = request.urlopen(req, timeout=timeout, context=ctx)
749
803
  return urlobj
750
804
  except request.HTTPError as e:
751
- logging.error("HTTPError: %s for %s", str(e.code), url)
805
+ logger.error("HTTPError: %s for %s", str(e.code), url)
752
806
  if e.code == 304:
753
807
  return None
754
808
  except request.URLError as e:
755
- logging.error("URLError: %s for %s", str(e.reason), url)
809
+ logger.error("URLError: %s for %s", str(e.reason), url)
756
810
  raise Exception("GetURL Failed")
757
811
 
758
812
 
@@ -780,17 +834,17 @@ def check_url(url):
780
834
  return True
781
835
 
782
836
  except requests.exceptions.HTTPError:
783
- logging.error(
837
+ logger.error(
784
838
  "Requests: HTTPError: status code %s for %s",
785
839
  str(resp.status_code) if resp else None,
786
840
  url,
787
841
  )
788
842
  except requests.exceptions.Timeout:
789
- logging.error("Requests: Timeout for %s", url)
843
+ logger.error("Requests: Timeout for %s", url)
790
844
  except requests.exceptions.TooManyRedirects:
791
- logging.error("Requests: TooManyRedirects for %s", url)
845
+ logger.error("Requests: TooManyRedirects for %s", url)
792
846
  except requests.ConnectionError:
793
- logging.error("Requests: ConnectionError for %s", url)
847
+ logger.error("Requests: ConnectionError for %s", url)
794
848
  return False
795
849
 
796
850
 
@@ -810,7 +864,7 @@ def exec_command(command):
810
864
  rcode = p.poll()
811
865
  return {"stdout": stdout, "stderr": stderr, "rcode": rcode}
812
866
  except OSError as e:
813
- logging.error(
867
+ logger.error(
814
868
  "SubprocessError: %s %s: %s", str(e.filename), str(e.strerror), str(e.errno)
815
869
  )
816
870
  raise Exception("ExecCommand")
@@ -836,7 +890,7 @@ def encode_file(file_path):
836
890
  return efile
837
891
  return None
838
892
  except (IOError, OSError) as e:
839
- logging.error("EncodeFile: Failed to open file: %s", e.strerror)
893
+ logger.error("EncodeFile: Failed to open file: %s", e.strerror)
840
894
  raise Exception("EncodeFile")
841
895
  finally:
842
896
  if isinstance(f, IOBase):
@@ -859,17 +913,17 @@ def check_url_modified(url):
859
913
  if resp.status_code == 200:
860
914
  return True
861
915
  except requests.exceptions.HTTPError:
862
- logging.error(
916
+ logger.error(
863
917
  "Requests: HTTPError: status code %s for %s",
864
918
  str(resp.status_code) if resp else None,
865
919
  url,
866
920
  )
867
921
  except requests.exceptions.Timeout:
868
- logging.error("Requests: Timeout for %s", url)
922
+ logger.error("Requests: Timeout for %s", url)
869
923
  except requests.exceptions.TooManyRedirects:
870
- logging.error("Requests: TooManyRedirects for %s", url)
924
+ logger.error("Requests: TooManyRedirects for %s", url)
871
925
  except requests.ConnectionError:
872
- logging.error("Requests: ConnectionError for %s", url)
926
+ logger.error("Requests: ConnectionError for %s", url)
873
927
  return False
874
928
 
875
929
 
@@ -895,7 +949,7 @@ def get_url_path_filename(url):
895
949
  if name[n].endswith("."):
896
950
  return name
897
951
  except IndexError:
898
- logging.error("Range: IndexError: URL basename is short: %s of %s", name, url)
952
+ logger.error("Range: IndexError: URL basename is short: %s of %s", name, url)
899
953
  return None
900
954
  return None
901
955
 
@@ -915,16 +969,16 @@ def get_url_filename(url):
915
969
  return name
916
970
  return None
917
971
  except requests.exceptions.MissingSchema:
918
- logging.error("Requests: MissingSchema: Requires ftp|http(s):// for %s", url)
972
+ logger.error("Requests: MissingSchema: Requires ftp|http(s):// for %s", url)
919
973
  except requests.exceptions.HTTPError:
920
- logging.error(
974
+ logger.error(
921
975
  "Requests: HTTPError: status code %s for %s",
922
976
  str(resp.status_code) if resp else None,
923
977
  url,
924
978
  )
925
979
  except requests.exceptions.Timeout:
926
- logging.error("Requests: Timeout for %s", url)
980
+ logger.error("Requests: Timeout for %s", url)
927
981
  except requests.exceptions.TooManyRedirects:
928
- logging.error("Requests: TooManyRedirects for %s", url)
982
+ logger.error("Requests: TooManyRedirects for %s", url)
929
983
  except requests.ConnectionError:
930
- logging.error("Requests: ConnectionError for %s", url)
984
+ logger.error("Requests: ConnectionError for %s", url)
@@ -1,21 +1,18 @@
1
1
  import json
2
+ from pathlib import Path
2
3
 
3
- import pkg_resources
4
4
 
5
-
6
- def load_schema(file_name):
5
+ def load_schema(file_name: str) -> dict:
7
6
  """
8
7
  Loads a json schema from the packages data folder.
9
8
  :param file_name: name of the file
10
9
  :return: JSON object as a dictionary
11
10
  """
12
11
 
13
- schema = pkg_resources.resource_stream(__name__, "/".join(("data", file_name)))
14
- schema = schema.read()
15
- if isinstance(schema, bytes):
16
- schema = schema.decode("utf-8")
17
- schema = json.loads(schema)
18
- return schema
12
+ with open(
13
+ Path(__file__).parent / "data" / file_name, "r", encoding="utf-8"
14
+ ) as schema_file:
15
+ return json.loads(schema_file.read())
19
16
 
20
17
 
21
18
  input_message_schema = load_schema("input_message_schema.json")
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: insightconnect-plugin-runtime
3
- Version: 6.2.1
3
+ Version: 6.2.4
4
4
  Summary: InsightConnect Plugin Runtime
5
5
  Home-page: https://github.com/rapid7/komand-plugin-sdk-python
6
6
  Author: Rapid7 Integrations Alliance
@@ -12,20 +12,28 @@ Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Natural Language :: English
13
13
  Classifier: Topic :: Software Development :: Build Tools
14
14
  Description-Content-Type: text/markdown
15
- Requires-Dist: requests==2.32.2
15
+ Requires-Dist: requests==2.32.3
16
16
  Requires-Dist: python_jsonschema_objects==0.5.2
17
- Requires-Dist: jsonschema==4.21.1
18
- Requires-Dist: certifi==2024.07.04
19
- Requires-Dist: Flask==3.0.2
20
- Requires-Dist: gunicorn==22.0.0
17
+ Requires-Dist: jsonschema==4.22.0
18
+ Requires-Dist: certifi==2024.12.14
19
+ Requires-Dist: Flask==3.1.0
20
+ Requires-Dist: gunicorn==23.0.0
21
21
  Requires-Dist: greenlet==3.1.1
22
- Requires-Dist: gevent==24.10.1
22
+ Requires-Dist: gevent==24.11.1
23
23
  Requires-Dist: marshmallow==3.21.0
24
24
  Requires-Dist: apispec==6.5.0
25
25
  Requires-Dist: apispec-webframeworks==1.0.0
26
- Requires-Dist: blinker==1.7.0
27
- Requires-Dist: structlog==24.1.0
26
+ Requires-Dist: blinker==1.9.0
27
+ Requires-Dist: structlog==24.4.0
28
28
  Requires-Dist: python-json-logger==2.0.7
29
+ Dynamic: author
30
+ Dynamic: author-email
31
+ Dynamic: classifier
32
+ Dynamic: description
33
+ Dynamic: description-content-type
34
+ Dynamic: home-page
35
+ Dynamic: requires-dist
36
+ Dynamic: summary
29
37
 
30
38
 
31
39
  # InsightConnect Python Plugin Runtime ![Build Status](https://github.com/rapid7/komand-plugin-sdk-python/workflows/Continuous%20Integration/badge.svg)
@@ -48,10 +56,10 @@ to get started.
48
56
 
49
57
  ## Development of the InsightConnect Plugin Runtime
50
58
 
51
- The Python Runtime codebase is built to support Python 3.11.10 as of version 6.2.0. The following dependencies will need
59
+ The Python Runtime codebase is built to support Python 3.11.11 as of version 6.2.3. The following dependencies will need
52
60
  to be installed when developing or testing the Plugin Runtime:
53
61
 
54
- - Python 3.11.10
62
+ - Python 3.11.11
55
63
  - Docker
56
64
  - make
57
65
  - tox
@@ -67,7 +75,7 @@ version and activate it. Then build, install, and confirm the package has been i
67
75
  > source venv/bin/activate
68
76
  > pip install -e ./
69
77
  > pip list | grep insightconnect-plugin-runtime
70
- insightconnect-plugin-runtime 5.2.2
78
+ insightconnect-plugin-runtime 6.2.3
71
79
  ```
72
80
 
73
81
  #### Building the InsightConnect Plugin Runtime Docker Images
@@ -123,7 +131,7 @@ name as a parameter:
123
131
 
124
132
  The plugin will be started in `http` mode and listening at `http:0.0.0.0:10001`:
125
133
  ```
126
- [2020-02-13 23:21:13 -0500] [56567] [INFO] Starting gunicorn 19.7.1
134
+ [2020-02-13 23:21:13 -0500] [56567] [INFO] Starting gunicorn 23.0.0
127
135
  [2020-02-13 23:21:13 -0500] [56567] [INFO] Listening at: http://0.0.0.0:10001 (56567)
128
136
  [2020-02-13 23:21:13 -0500] [56567] [INFO] Using worker: threads
129
137
  [2020-02-13 23:21:13 -0500] [56571] [INFO] Booting worker with pid: 56571
@@ -184,7 +192,7 @@ Running a specific test file:
184
192
 
185
193
  | | Plugin | Slim Plugin |
186
194
  |:------------------|:-------:|:-----------:|
187
- | Python Version | 3.11.10 | 3.11.10 |
195
+ | Python Version | 3.11.11 | 3.11.11 |
188
196
  | OS | Alpine | Bullseye |
189
197
  | Package installer | apk | apt |
190
198
  | Shell | /bin/sh | /bin/bash |
@@ -211,6 +219,9 @@ contributed. Black is installed as a test dependency and the hook can be initial
211
219
  after cloning this repository.
212
220
 
213
221
  ## Changelog
222
+ * 6.2.4 - Update `make_request` helper to support extra parameter of `max_response_size` to cap the response
223
+ * 6.2.3 - Updated dockerfiles for both `slim` and `full` SDK types to use Python 3.11.11 | Updated dependencies | Removed `pkg_resources` usage due to deprecation
224
+ * 6.2.2 - Fix instances where logging errors would lead to duplicate entries being output | Add option to hash only on provided keys for `hash_sha1` function
214
225
  * 6.2.1 - Fix instances where logging would lead to duplicate entries being output
215
226
  * 6.2.0 - Update base images to pull Python 3.11.10 | changed the pep-8 check in tox to `pycodestyle`
216
227
  * 6.1.4 - Address vulnerabilities within local development requirements.txt and vulnerabilities in slim image.
@@ -4,10 +4,10 @@ insightconnect_plugin_runtime/cli.py,sha256=Pb-Janu-XfRlSXxPHh30OIquljWptrhhS51C
4
4
  insightconnect_plugin_runtime/connection.py,sha256=4bHHV2B0UFGsAtvLu1fiYQRwx7fissUakHPUyjLQO0E,2340
5
5
  insightconnect_plugin_runtime/dispatcher.py,sha256=ru7njnyyWE1-oD-VbZJ-Z8tELwvDf69rM7Iezs4rbnw,1774
6
6
  insightconnect_plugin_runtime/exceptions.py,sha256=Pvcdkx81o6qC2qU661x-DzNjuIMP82x52nPMSEqEo4s,8491
7
- insightconnect_plugin_runtime/helper.py,sha256=IY4-ntZbiXWFx7jAQ_n1vBjQBd_tt4Zr4INtXzQmiGg,31564
7
+ insightconnect_plugin_runtime/helper.py,sha256=B0XqAXmn8CT1KQ6i5IoWLQrQ_HVOvuKrIKFuj_npQ-g,33770
8
8
  insightconnect_plugin_runtime/metrics.py,sha256=hf_Aoufip_s4k4o8Gtzz90ymZthkaT2e5sXh5B4LcF0,3186
9
9
  insightconnect_plugin_runtime/plugin.py,sha256=Yf4LNczykDVc31F9G8uuJ9gxEsgmxmAr0n4pcZzichM,26393
10
- insightconnect_plugin_runtime/schema.py,sha256=jTNc6KAMqFpaDVWrAYhkVC6e8I63P3X7uVlJkAr1hiY,583
10
+ insightconnect_plugin_runtime/schema.py,sha256=6MVw5hqGATU1VLgwfOWfPsP3hy1OnsugCTsgX8sknes,521
11
11
  insightconnect_plugin_runtime/server.py,sha256=09fxsbKf2ZZvSqRP2Bv9e9-fspDyEFR8_YgIFeMnXqQ,12578
12
12
  insightconnect_plugin_runtime/step.py,sha256=KdERg-789-s99IEKN61DR08naz-YPxyinPT0C_T81C4,855
13
13
  insightconnect_plugin_runtime/task.py,sha256=d-H1EAzVnmSdDEJtXyIK5JySprxpF9cetVoFGtWlHrg,123
@@ -15,7 +15,7 @@ insightconnect_plugin_runtime/trigger.py,sha256=Zq3cy68N3QxAGbNZKCID6CZF05Zi7YD2
15
15
  insightconnect_plugin_runtime/util.py,sha256=qPkZ3LA55nYuNYdansEbnCnBccQkpzIpp9NA1B64Kvw,8444
16
16
  insightconnect_plugin_runtime/variables.py,sha256=7FjJGnU7KUR7m9o-_tRq7Q3KiaB1Pp0Apj1NGgOwrJk,3056
17
17
  insightconnect_plugin_runtime/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- insightconnect_plugin_runtime/api/endpoints.py,sha256=gIxuxO7AFqmic0k9b_2gWjjJ8ICtWDa88X4Tl4fEgik,33333
18
+ insightconnect_plugin_runtime/api/endpoints.py,sha256=8QQrxzW8jmQIkalud8fqYwB05uUw8sTiDNgO5ZekOCA,33353
19
19
  insightconnect_plugin_runtime/api/schemas.py,sha256=jRmDrwLJTBl-iQOnyZkSwyJlCWg4eNjAnKfD9Eko4z0,2754
20
20
  insightconnect_plugin_runtime/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  insightconnect_plugin_runtime/clients/aws_client.py,sha256=bgSK3Txr1YonDiUN5JFEZ5cAyCXFM14JjfMus6lRV8o,23017
@@ -68,7 +68,7 @@ tests/unit/test_aws_action.py,sha256=pBE23Qn4aXKJqPmwiHMcEU5zPdyvbKO-eK-6jUlrsQw
68
68
  tests/unit/test_custom_encoder.py,sha256=KLYyVOTq9MEkZXyhVHqjm5LVSW6uJS4Davgghsw9DGk,2207
69
69
  tests/unit/test_endpoints.py,sha256=LuXOfLBu47rDjGa5YEsOwTZBEdvQdl_C6-r46oxWZA8,6401
70
70
  tests/unit/test_exceptions.py,sha256=Y4F-ij8WkEJkUU3mPvxlEchqE9NCdxDvR8bJzPVVNao,5328
71
- tests/unit/test_helpers.py,sha256=_1jlU-h-GNY9MLQ8nV_or4zkTI911AmHKtvFyamKjfU,16297
71
+ tests/unit/test_helpers.py,sha256=ym1tFi1VSKmdPaHEAlMEl1S7Ibu9-LrqZ2oqJv7bfbE,18685
72
72
  tests/unit/test_metrics.py,sha256=PjjTrB9w7uQ2Q5UN-893-SsH3EGJuBseOMHSD1I004s,7979
73
73
  tests/unit/test_oauth.py,sha256=nbFG0JH1x04ExXqSe-b5BGdt_hJs7DP17eUa6bQzcYI,2093
74
74
  tests/unit/test_plugin.py,sha256=ZTNAZWwZhDIAbxkVuWhnz9FzmojbijgMmsLWM2mXQI0,4160
@@ -77,8 +77,8 @@ tests/unit/test_server_cloud_plugins.py,sha256=PuMDHTz3af6lR9QK1BtPScr7_cRbWheto
77
77
  tests/unit/test_server_spec.py,sha256=je97BaktgK0Fiz3AwFPkcmHzYtOJJNqJV_Fw5hrvqX4,644
78
78
  tests/unit/test_trigger.py,sha256=E53mAUoVyponWu_4IQZ0IC1gQ9lakBnTn_9vKN2IZfg,1692
79
79
  tests/unit/test_variables.py,sha256=OUEOqGYZA3Nd5oKk5GVY3hcrWKHpZpxysBJcO_v5gzs,291
80
- tests/unit/utils.py,sha256=VooVmfpIgxmglNdtmT32AkEDFxHxyRHLK8RsCWjjYRY,2153
81
- insightconnect_plugin_runtime-6.2.1.dist-info/METADATA,sha256=7VvNCT7PV0qyLXPRcZcp6tUulmRLyEHix_Qf53iwn3M,15117
82
- insightconnect_plugin_runtime-6.2.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
83
- insightconnect_plugin_runtime-6.2.1.dist-info/top_level.txt,sha256=AJtyJOpiFzHxsbHUICTcUKXyrGQ3tZxhrEHsPjJBvEA,36
84
- insightconnect_plugin_runtime-6.2.1.dist-info/RECORD,,
80
+ tests/unit/utils.py,sha256=hcY0A2H_DMgCDXUTvDtCXMdMvRjLQgTaGcTpATb8YG0,2236
81
+ insightconnect_plugin_runtime-6.2.4.dist-info/METADATA,sha256=zprsS3dKV5_7yPthb5aUPyXTAItb-fJt-8NQn9M98bA,15719
82
+ insightconnect_plugin_runtime-6.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
83
+ insightconnect_plugin_runtime-6.2.4.dist-info/top_level.txt,sha256=AJtyJOpiFzHxsbHUICTcUKXyrGQ3tZxhrEHsPjJBvEA,36
84
+ insightconnect_plugin_runtime-6.2.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -4,6 +4,7 @@ from insightconnect_plugin_runtime.exceptions import (
4
4
  HTTPStatusCodes,
5
5
  ResponseExceptionData,
6
6
  PluginException,
7
+ APIException
7
8
  )
8
9
  import requests
9
10
  import os
@@ -378,16 +379,39 @@ class TestRequestsHelpers(TestCase):
378
379
  json={"sample": "value"},
379
380
  headers={"Content-Type": "application/json"},
380
381
  )
381
- response = helper.make_request(_request=request)
382
+ response = helper.make_request(_request=request, stream=True, max_response_size=100)
382
383
  expected = {
383
384
  "json": {"example": "sample"},
384
385
  "status_code": 200,
385
386
  "content": b"example",
386
387
  "url": "https://example.com/success",
388
+ "content-length": "1",
387
389
  }
388
390
  self.assertEqual(response.content, expected.get("content"))
389
391
  self.assertEqual(response.status_code, expected.get("status_code"))
390
392
  self.assertEqual(response.url, expected.get("url"))
393
+ self.assertEqual(response.headers.get("content-length"), expected.get("content-length"))
394
+
395
+ @patch("requests.Session.send")
396
+ def test_make_request_enforces_max_response_size(self, mocked_request):
397
+ returned_max_size, test_max_size = "5000", 1527
398
+
399
+ response = requests.Response()
400
+ response.headers = {"content-length": returned_max_size, "content-type": "application/json"}
401
+ mocked_request.return_value = response
402
+
403
+ test_request = requests.Request(method="GET", url="https://event_source.com/api/v1/logs")
404
+
405
+ with self.assertRaises(APIException) as api_err:
406
+ helper.make_request(_request=test_request, stream=True, max_response_size=test_max_size)
407
+
408
+ self.assertEqual(400, api_err.exception.status_code)
409
+ exp_err_cause = f"API response is exceeding allowed limit of {test_max_size} bytes."
410
+ exp_err_data = f"Content length returned was {returned_max_size} and max allowed is {test_max_size}"
411
+ self.assertEqual(400, api_err.exception.status_code)
412
+ self.assertEqual(exp_err_cause, api_err.exception.cause)
413
+ self.assertEqual(exp_err_data, api_err.exception.data)
414
+
391
415
 
392
416
  @parameterized.expand(
393
417
  [
@@ -510,20 +534,46 @@ class TestRequestsHelpers(TestCase):
510
534
  )
511
535
 
512
536
 
513
- def test_hash_sha1():
514
- log = {"example": "value", "sample": "value"}
515
- assert "2e1ccc1a95e9b2044f13546c25fe380bbd039293" == helper.hash_sha1(log)
537
+ class TestHashing(TestCase):
538
+ def setUp(self) -> None:
539
+ self.log = {"example": "value", "sample": "value"}
516
540
 
541
+ def test_hash_sha1_no_keys(self):
542
+ # Test hash with no keys provided
543
+ expected_hash = "2e1ccc1a95e9b2044f13546c25fe380bbd039293"
544
+ self.assertEqual(helper.hash_sha1(self.log), expected_hash)
517
545
 
518
- def test_compare_and_dedupe_hashes():
519
- hashes = ["2e1ccc1a95e9b2044f13546c25fe380bbd039293"]
520
- logs = [
521
- {
522
- "example": "value",
523
- "sample": "value",
524
- },
525
- {"specimen": "new_value"},
526
- ]
527
- assert [{"specimen": "new_value"}], [
528
- "ad6ae80c0356e02b1561cb58408ee678eb1070bb"
529
- ] == helper.compare_and_dedupe_hashes(hashes, logs)
546
+ def test_hash_sha1_keys(self):
547
+ # Test hash with valid key provided
548
+ expected_hash = "61c908e52d66a763ceed0798b8e5f4b7f0328a21"
549
+ self.assertEqual(helper.hash_sha1(self.log, keys=["example"]), expected_hash)
550
+
551
+ def test_hash_sha1_keys_wrong_type(self):
552
+ # Test hash with wrong type for keys
553
+ with self.assertRaises(TypeError) as context:
554
+ helper.hash_sha1(self.log, keys="test")
555
+
556
+ self.assertEqual(
557
+ str(context.exception),
558
+ "The 'keys' parameter must be a list or None in the 'hash_sha1' function, not str"
559
+ )
560
+
561
+ def test_hash_sha1_keys_not_found(self):
562
+ # Test hash with key not found
563
+ with self.assertRaises(KeyError) as context:
564
+ helper.hash_sha1(self.log, keys=["example", "test"])
565
+
566
+ self.assertEqual(str(context.exception), "\"Key 'test' not found in the provided log.\"")
567
+
568
+ def test_compare_and_dedupe_hashes(self):
569
+ hashes = ["2e1ccc1a95e9b2044f13546c25fe380bbd039293"]
570
+ logs = [
571
+ {
572
+ "example": "value",
573
+ "sample": "value",
574
+ },
575
+ {"specimen": "new_value"},
576
+ ]
577
+ assert [{"specimen": "new_value"}], [
578
+ "ad6ae80c0356e02b1561cb58408ee678eb1070bb"
579
+ ] == helper.compare_and_dedupe_hashes(hashes, logs)
tests/unit/utils.py CHANGED
@@ -34,6 +34,7 @@ def get_mock_response(
34
34
  response._content = bytes_string
35
35
  response.url = url
36
36
  response.reason = reason
37
+ response.headers = {"content-length": "1", "content-type": "application/json"}
37
38
 
38
39
  def return_json():
39
40
  return data