Skip to content

Commit ca4cf08

Browse files
authored
Merge pull request #36 from mbengit/master
Improve test coverage
2 parents 35e42c1 + 041e86e commit ca4cf08

24 files changed

+516
-213
lines changed

.gitignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
.cache
2+
.coverage
3+
.idea
24
.project
35
.pydevproject
46
.pytest_cache
@@ -9,10 +11,9 @@
911
*.pyc
1012
*.swp
1113
build
14+
coverage.xml
15+
coverage-html
1216
dist
1317
qpylib.egg-info
14-
RemoteSystemsTempFiles
1518
qpylib/version.py
16-
.idea/
17-
coverage.xml
18-
.coverage
19+
RemoteSystemsTempFiles

.pylintrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ include-naming-hint=no
117117
property-classes=abc.abstractproperty
118118

119119
# Regular expression matching correct function names
120-
function-rgx=[a-z_][a-z0-9_]{2,40}$
120+
function-rgx=[a-z_][a-z0-9_]{2,50}$
121121

122122
# Naming hint for function names
123-
function-name-hint=[a-z_][a-z0-9_]{2,40}$
123+
function-name-hint=[a-z_][a-z0-9_]{2,50}$
124124

125125
# Regular expression matching correct variable names
126126
variable-rgx=[a-z_][a-z0-9_]{2,30}$

CHANGELOG.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
# Changelog
2-
All notable changes to this project will be documented in this file.
32

4-
## [Unreleased]
5-
### Added
6-
### Changed
7-
### Removed
3+
## 2.0.1
4+
- Non-SDK REST calls default to `verify=True`.
5+
SDK default remains `verify=False` for now, but that will change in a future release.
6+
7+
## 2.0
8+
- First official release of qpylib on Github.
9+
- Support for Python 3 and Red Hat UBI app base image.
10+
- abstract/sdk/live layers removed.
11+
- New encryption algorithm in encdec.py, plus backwards compatibility with previous versions.
12+
- New ariel.py module supports Ariel searches via REST API.
13+
- `get_app_id` now uses `QRADAR_APP_ID` environment variable instead of manifest value.
14+
- REST methods now pass through `kwargs`.
15+
- REST replaces `gethostname()` with `localhost`.

CONTRIBUTING.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ To contribute code or documentation, please submit a [pull request](https://gith
55
## Proposing new features
66

77
If you would like to implement a new feature, please [raise an issue](https://github.com/ibm/qpylib/issues)
8-
before sending a pull request so the feature can be discussed.
8+
before sending a pull request so that the feature can be discussed.
99

1010
## Fixing bugs
1111

1212
To fix a bug, please [raise an issue](https://github.ibm.com/ibm/qpylib/issues) before sending a
13-
pull request so it can be tracked.
13+
pull request so that the bug can be tracked.
1414

1515
## Merge approval
1616

@@ -22,7 +22,7 @@ A list of maintainers can be found on the [MAINTAINERS](MAINTAINERS.md) page.
2222
Each source file must include a license header for the Apache Software License 2.0.
2323
Using the SPDX format is the simplest approach. See existing source files for an example.
2424

25-
## Setup
25+
## Development
2626

2727
On your local machine you can lint, test and build in mostly
2828
the same way that Travis CI does. There are three scripts you can use:
@@ -33,17 +33,16 @@ the same way that Travis CI does. There are three scripts you can use:
3333

3434
The `requirements.txt` file contains the Python packages needed to run these scripts.
3535

36-
## Code style
36+
### Code style
3737

38-
Pull requests will only be accepted if they pass the linting.
39-
Linting can be run either on your fork through Travis CI, or locally using `lint.sh`.
38+
Pull requests will be accepted only if `lint.sh` produces no warnings or errors.
4039

41-
## Test
40+
### Test
4241

43-
Pull requests will only be accepted if they pass all tests.
44-
Tests can be run either on your fork through Travis CI, or locally using `test.sh`.
42+
Pull requests will be accepted only if `test.sh` reports no test failures **and**
43+
sufficient test coverage.
4544

46-
## Build
45+
### Build
4746

4847
The output of `build.sh` is a tar.gz file containing the qpylib Python package.
4948
You can use `pip install` to install the package into your Python 3 environment.

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ The qpylib library hosted here is for use only with apps that have been written
1414
Python 3 to run on Red Hat Universal Base Image. It is not compatible with apps
1515
written to run on a CentOS base image.
1616

17-
**NOTE**: until the first official qpylib release, planned for early Q3 2020,
18-
any code contained herein is unsupported and subject to change.
19-
2017
## Project details
2118

2219
* [LICENSE](LICENSE)

clean.sh

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33
#
44
# SPDX-License-Identifier: Apache-2.0
55

6-
rm -rf .pytest_cache
76
rm -f qpylib/version.py
8-
rm -rf dist qpylib.egg-info
7+
8+
rm -rf dist
9+
rm -rf qpylib.egg-info
10+
11+
rm -rf .pytest_cache
912
rm -rf qpylib/__pycache__
13+
rm -rf qpylib/encryption/__pycache__
1014
rm -rf test/__pycache__
15+
1116
rm -f .coverage
1217
rm -f coverage.xml
18+
rm -rf coverage-html

qpylib/ariel.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ def search(self, query, api_version='latest'):
4444
response_json = response.json()
4545
return (response_json['status'], response_json['search_id'])
4646

47-
def search_sync(self, query, timeout=60, api_version='latest'):
47+
def search_sync(self, query, timeout=60, sleep_interval=10, api_version='latest'):
4848
''' Initiates a synchronous Ariel search.
4949
query: AQL query to execute.
5050
timeout: number of seconds to wait for search to complete.
51+
sleep_interval: number of seconds to sleep before retrying status check.
5152
api_version: QRadar API version to use, defaults to latest.
5253
Returns a tuple containing search ID and record count.
5354
Raises ArielError if any of these occur:
@@ -67,7 +68,7 @@ def search_sync(self, query, timeout=60, api_version='latest'):
6768
raise ArielError('Ariel search {0} failed: {1}'.format(search_id, status),
6869
aql=query)
6970
if time.time() < end_time:
70-
time.sleep(10)
71+
time.sleep(sleep_interval)
7172
continue
7273
raise ArielError('Ariel search {0} did not complete within {1}s'
7374
.format(search_id, timeout), aql=query)

qpylib/asset_qpylib.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def get_asset_json_ld(asset_id):
4141
get_asset_url_full(asset_id),
4242
'asset',
4343
'Asset details',
44-
'Asset details for id ' + asset_id,
44+
'Asset details for id ' + str(asset_id),
4545
asset_json)
4646

4747
def get_asset_json_html(asset_id, generate_html=None):

qpylib/encdec.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ class Encryption():
3131
'''
3232
engines = {2: Enginev2, 3: Enginev3, 4: Enginev4}
3333
latest_engine_version = max(engines)
34-
latest_engine_class = engines[latest_engine_version]
3534

3635
def __init__(self, data):
3736
''' data is an object containing two non-empty strings:
@@ -68,13 +67,13 @@ def encrypt(self, clear_text):
6867
Returns the encrypted value.
6968
'''
7069
self._reset_config_if_required()
71-
engine = Encryption.latest_engine_class(self.config[self.name], self.app_uuid)
70+
engine = self.latest_engine_class()(self.config[self.name], self.app_uuid)
7271

7372
try:
7473
encrypted_secret = engine.encrypt(clear_text)
7574
except Exception as error:
7675
raise EncryptionError('Failed to encrypt secret for name {0}: {1}'
77-
.format(self.name, error))
76+
.format(self.name, type(error).__name__))
7877

7978
self.config[self.name]['secret'] = encrypted_secret
8079
self._save_config()
@@ -99,13 +98,17 @@ def decrypt(self):
9998
secret = engine.decrypt()
10099
except Exception as error:
101100
raise EncryptionError('Failed to decrypt secret for name {0}: {1}'
102-
.format(self.name, error))
101+
.format(self.name, type(error).__name__))
103102

104103
if engine.version != Encryption.latest_engine_version:
105104
self.encrypt(secret)
106105

107106
return secret
108107

108+
@staticmethod
109+
def latest_engine_class():
110+
return Encryption.engines[Encryption.latest_engine_version]
111+
109112
def _choose_engine(self):
110113
# If no version is present in the config we default to engine v2.
111114
try:
@@ -130,7 +133,7 @@ def _reset_config_if_required(self):
130133
except KeyError:
131134
reset_required = True
132135
if reset_required:
133-
self.config[self.name] = Encryption.latest_engine_class.generate_config()
136+
self.config[self.name] = self.latest_engine_class().generate_config()
134137

135138
def _read_config(self):
136139
try:

qpylib/json_qpylib.py

Lines changed: 37 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,82 +5,68 @@
55
import json
66
from . import app_qpylib
77

8-
# A dictionary of jsonld context types mapped to the type name
98
JSONLD_TYPES = {}
10-
119
KEY_CONTEXT = '@context'
1210
KEY_TYPE = '@type'
1311
KEY_ID = '@id'
1412

1513
def register_jsonld_endpoints():
16-
manifest = app_qpylib.get_manifest_json()
17-
18-
services = None
19-
if 'services' in manifest.keys():
20-
services = manifest['services']
21-
if services is None:
14+
try:
15+
services = app_qpylib.get_manifest_json()['services']
16+
except KeyError:
2217
return
2318

2419
for service in services:
25-
endpoints = None
26-
if 'endpoints' in service.keys():
20+
try:
2721
endpoints = service['endpoints']
28-
if endpoints is None:
22+
except KeyError:
2923
continue
3024
for endpoint in endpoints:
31-
jsonld_context = None
32-
if 'request_mime_type' in endpoint.keys():
33-
argument = endpoint
34-
jsonld_context = _extract_jsonld_context(argument, 'request_mime_type', 'request_body_type')
35-
register_jsonld_type_from_context(jsonld_context)
36-
if 'response' in endpoint.keys():
37-
argument = endpoint['response']
38-
jsonld_context = _extract_jsonld_context(argument, 'mime_type', 'body_type')
39-
register_jsonld_type_from_context(jsonld_context)
25+
_extract_and_register_jsonld_context(endpoint, 'request_mime_type', 'request_body_type')
26+
try:
27+
_extract_and_register_jsonld_context(endpoint['response'], 'mime_type', 'body_type')
28+
except KeyError:
29+
pass
30+
31+
def _extract_and_register_jsonld_context(endpoint_direction, mime_id, body_id):
32+
try:
33+
if endpoint_direction[mime_id] == 'application/json+ld':
34+
register_jsonld_type_from_context(endpoint_direction[body_id])
35+
except KeyError:
36+
pass
4037

41-
def _extract_jsonld_context(argument, mime_id, context_id):
42-
if (mime_id in argument.keys() and
43-
context_id in argument.keys() and
44-
argument[mime_id] == 'application/json+ld'):
45-
return argument[context_id]
46-
return None
38+
def register_jsonld_type(jsonld_type, context):
39+
global JSONLD_TYPES
40+
JSONLD_TYPES[str(jsonld_type)] = context
4741

4842
def register_jsonld_type_from_context(context):
49-
if context is not None:
50-
jsonld_type = _extract_type(context)
43+
jsonld_type = _extract_type_from_context(context)
44+
if jsonld_type:
5145
register_jsonld_type(jsonld_type, context)
5246

53-
def register_jsonld_type(jsonld_type, context):
54-
global JSONLD_TYPES
55-
JSONLD_TYPES[str(jsonld_type)] = context
47+
def _extract_type_from_context(context):
48+
if KEY_CONTEXT in context.keys():
49+
at_context = context[KEY_CONTEXT]
50+
if KEY_TYPE in at_context.keys():
51+
if at_context[KEY_TYPE] == KEY_ID and KEY_ID in at_context.keys():
52+
return at_context[KEY_ID]
53+
return at_context[KEY_TYPE]
54+
return None
5655

5756
def get_jsonld_type(jsonld_type):
5857
global JSONLD_TYPES
59-
if jsonld_type in JSONLD_TYPES.keys():
58+
try:
6059
return JSONLD_TYPES[str(jsonld_type)]
61-
raise ValueError('json ld key has not been registered')
60+
except KeyError:
61+
raise ValueError('JSON-LD type {0} has not been registered'.format(str(jsonld_type)))
6262

63-
def _extract_type(argument):
64-
type_id = None
65-
if KEY_CONTEXT in argument.keys():
66-
context = argument[KEY_CONTEXT]
67-
if KEY_TYPE in context.keys():
68-
type_id = context[KEY_TYPE]
69-
if type_id == KEY_ID and KEY_ID in context.keys():
70-
type_id = context[KEY_ID]
71-
return type_id
72-
73-
def render_json_ld_type(jld_type, data, jld_id=None):
63+
def render_jsonld_type(jld_type, data, jld_id=None):
7464
jld_context = get_jsonld_type(jld_type)
75-
json_dict = {}
65+
json_dict = {KEY_CONTEXT: jld_context[KEY_CONTEXT], KEY_TYPE: jld_type}
66+
if jld_id:
67+
json_dict[KEY_ID] = jld_id
7668
for json_key in data:
7769
json_dict[json_key] = data[json_key]
78-
79-
json_dict[KEY_CONTEXT] = jld_context[KEY_CONTEXT]
80-
json_dict[KEY_TYPE] = jld_type
81-
if jld_id is not None:
82-
json_dict[KEY_TYPE] = jld_type
83-
8470
return json.dumps(json_dict, sort_keys=True)
8571

8672
# pylint: disable=too-many-arguments

0 commit comments

Comments
 (0)