Skip to content

Commit 50aa42d

Browse files
committed
v0.0.2 updates
refactored handlers to return type added get project inventory and inventory summary added more tests updated models
1 parent 89d02ce commit 50aa42d

7 files changed

Lines changed: 184 additions & 37 deletions

File tree

codeinsight_sdk/client.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
class CodeInsightClient:
1010
def __init__(self,
1111
base_url: str,
12-
api_token: str
12+
api_token: str,
13+
page_size: int = 100
1314
):
1415
self.base_url = base_url
1516
self.api_url = f"{base_url}/codeinsight/api"
1617
self.api_token = api_token
18+
self.page_size = page_size
1719
self.api_headers = {
1820
'Content-Type': 'application/json',
1921
"Authorization": "Bearer %s" % self.api_token,
@@ -35,11 +37,7 @@ def request(self, method, url_part: str, params: dict = None):
3537
def projects(self) -> Handler:
3638
return Handler.create(self, Project)
3739

38-
@property
39-
def project_inventory(self) -> Handler:
40-
return Handler.create(self, ProjectInventory)
4140

42-
4341
# Coming soon...?
4442
def inventories(self):
4543
raise NotImplementedError("Inventories are not yet implemented")

codeinsight_sdk/handlers.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import abc
2+
from typing import List
3+
4+
from codeinsight_sdk.models import Project, ProjectInventory, ProjectInventoryItem
25

36
class Handler(abc.ABC):
47
def __init__(self, client, cls):
@@ -8,8 +11,8 @@ def __init__(self, client, cls):
811
@staticmethod
912
def create(client, cls):
1013
k = cls.__name__
11-
handlers = {"Project": ProjectHandler,
12-
"ProjectInventory": ProjectInventoryHandler}
14+
handlers = {"Project": ProjectHandler,
15+
}
1316
handler = handlers.get(k)
1417
return handler(client, cls)
1518

@@ -19,20 +22,21 @@ def get(self):
1922

2023
class ProjectHandler(Handler):
2124
#Note API endpoints switch between projects and project...
22-
def all(self):
25+
def all(self) -> List[Project]:
2326
path = "projects"
2427
resp = self.client.request("GET", url_part=path)
2528
projects = []
2629
for project_data in resp.json()['data']:
2730
projects.append(self.cls.from_dict(project_data))
2831
return projects
2932

30-
def get(self, id:str):
33+
def get(self, id:str) -> Project:
3134
path = f"projects/{id}"
3235
resp = self.client.request("GET", url_part=path)
33-
project_data = resp.json()
36+
project_data = resp.json()['data']
37+
return self.cls.from_dict(project_data)
3438

35-
def get_id(self, projectName:str):
39+
def get_id(self, projectName:str) -> int:
3640
"""
3741
Retrieves the ID of a project based on its name.
3842
@@ -47,12 +51,23 @@ def get_id(self, projectName:str):
4751
resp = self.client.request("GET", url_part=path, params=params)
4852
projectId = resp.json()['Content']
4953
return projectId
50-
51-
class ProjectInventoryHandler(Handler):
52-
def get(self, id:str, skip_vulnerabilities: bool = False):
53-
path = f"project/inventory/{id}"
54+
55+
def get_inventory_summary(self, project_id:int) -> List[ProjectInventoryItem]:
56+
path = f"projects/{project_id}/inventorySummary"
57+
resp = self.client.request("GET", url_part=path)
58+
inventory = []
59+
for inv_item in resp.json()['data']:
60+
inventory.append(ProjectInventoryItem.from_dict(inv_item))
61+
return inventory
62+
63+
def get_inventory(self,project_id:int,
64+
skip_vulnerabilities: bool = False,
65+
published:bool = True,
66+
) -> ProjectInventory:
67+
path = f"project/inventory/{project_id}"
5468
params = {"skipVulnerabilities": skip_vulnerabilities}
5569
resp = self.client.request("GET", url_part=path, params=params)
5670
project_inventory = resp.json()
57-
project_cls = self.cls.from_dict(project_inventory)
71+
project_cls = ProjectInventory.from_dict(project_inventory)
5872
return project_cls
73+

codeinsight_sdk/models.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from dataclasses import dataclass
22
from dataclasses_json import DataClassJsonMixin, dataclass_json
3-
from typing import Any, Optional, List
4-
from .handlers import Handler
3+
from typing import Any, Optional, List, Dict
54

65
@dataclass
76
class Project(DataClassJsonMixin):
@@ -11,10 +10,10 @@ class Project(DataClassJsonMixin):
1110
owner: Optional[str] = None
1211
description: Optional[str] = None
1312
dateCreated: Optional[str] = None
13+
projectPath: Optional[str] = None
14+
# TODO: Should this be a dictionary or another class? This structure is reused in a few APIs
15+
vulnerabilities: Optional[Dict[str, Dict]] = None
1416

15-
@property
16-
def inventory(self) -> Handler:
17-
return Handler.create(self, ProjectInventory)
1817

1918
@dataclass
2019
class Vulnerability(DataClassJsonMixin):
@@ -28,15 +27,17 @@ class Vulnerability(DataClassJsonMixin):
2827
class ProjectInventoryItem(DataClassJsonMixin):
2928
itemNumber: int
3029
id: int
30+
componentName: str
31+
componentVersionName: str
3132
name: str
3233
type: str
3334
priority: str
3435
createdBy: str
3536
createdOn: str
3637
updatedOn: str
37-
componentName: str
38-
componentVersionName: str
38+
url: Optional[str] = None
3939
vulnerabilites: Optional[List[Vulnerability]] = None
40+
vulnerabilitySummary: Optional[Dict[str, Dict]] = None
4041
filePaths: Optional[List[str]] = None
4142

4243
@dataclass_json #Trying this style instead of DataClassJsonMixin

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "codeinsight_sdk"
3-
version = "0.0.1"
3+
version = "0.0.2"
44
description = "A Python client for the Revenera Code Insight"
55
authors = ["Zachary Karpinski <1206496+zkarpinski@users.noreply.github.com>"]
66
readme = "README.md"

tests/test_client.py

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,35 +33,103 @@ def test_get_all_projects(self, client):
3333
projects = client.projects.all()
3434
assert len(projects) > 0
3535

36-
def test_get_project(self,client):
36+
def test_get_project_id(self,client):
3737
projectName = "Test"
3838
with requests_mock.Mocker() as m:
3939
m.get(f"{TEST_URL}/codeinsight/api/project/id", text='{ "Content": 1 }')
4040
project_id = client.projects.get_id(projectName)
4141
assert project_id == 1
42+
43+
def test_get_project(self,client):
44+
project_id = 1
45+
fake_response_json = """ { "data": {
46+
"id": 1,
47+
"name": "Test",
48+
"description": "Test project",
49+
"createdBy": "Zach",
50+
"createdOn": "Today",
51+
"updatedOn": "Tomorrow",
52+
"projectType": "maven",
53+
"projectUrl": "",
54+
"vulnerabilities": {
55+
"CvssV2": {
56+
"High": 2,
57+
"Medium": 2,
58+
"Low": 3,
59+
"Unknown": 4
60+
},
61+
"CvssV3": {
62+
"Critical": 1,
63+
"High": 1,
64+
"Medium": 2,
65+
"Low": 6,
66+
"Unknown": 1
67+
}
68+
}
69+
}}
70+
"""
71+
with requests_mock.Mocker() as m:
72+
m.get(f"{TEST_URL}/codeinsight/api/projects/{project_id}", text=fake_response_json)
73+
project = client.projects.get(project_id)
74+
assert project.id == 1
75+
assert project.name == "Test"
76+
assert project.vulnerabilities["CvssV3"]["Critical"] == 1
77+
assert project.vulnerabilities["CvssV3"]["High"] == 1
78+
assert project.vulnerabilities["CvssV2"]["High"] == 2
79+
assert project.vulnerabilities["CvssV2"]["Unknown"] == 4
4280

4381
def test_get_project_inventory(self,client):
4482
project_id = 1
45-
fake_json = """
83+
fake_response_json = """
4684
{ "projectId": 1, "inventoryItems": [
4785
{"itemNumber":1, "id":1234, "name":"Example component","type":"component","priority":"low","createdBy":"Zach","createdOn":"Today","updatedOn":"Tomorrow","componentName":"snakeyaml","componentVersionName":"2.0"},
4886
{"itemNumber":2, "id":1235, "name":"Example component 2","type":"component","priority":"low","createdBy":"Zach","createdOn":"Today","updatedOn":"Tomorrow","componentName":"snakeyaml","componentVersionName":"2.0"}
4987
]}
5088
"""
5189
with requests_mock.Mocker() as m:
5290
m.get(f"{TEST_URL}/codeinsight/api/project/inventory/{project_id}",
53-
text=fake_json)
54-
projectInventory = client.project_inventory.get(project_id)
55-
print(projectInventory)
91+
text=fake_response_json)
92+
projectInventory = client.projects.get_inventory(project_id)
5693
assert projectInventory.projectId == project_id
5794
assert len(projectInventory.inventoryItems) >= 2
58-
5995

60-
## Coming soon features ##
61-
def test_inventories(self, client):
62-
with pytest.raises(NotImplementedError):
63-
client.inventories() != None
96+
#### FIX THIS! ####
97+
def test_get_project_inventory_summary(self,client):
98+
project_id = 1
99+
fake_response_json = """ { "data": [
100+
{
101+
"itemNumber": 1,
102+
"id": 12345,
103+
"name": "Inventory Item 1",
104+
"type":"component",
105+
"priority":"low",
106+
"createdBy":"Zach",
107+
"createdOn":"Today",
108+
"updatedOn":"Tomorrow",
109+
"componentName":"snakeyaml",
110+
"componentVersionName":"2.0"
111+
},
112+
{
113+
"itemNumber": 2,
114+
"id": 12346,
115+
"name": "Inventory Item 2",
116+
"type":"component",
117+
"priority":"low",
118+
"createdBy":"Zach",
119+
"createdOn":"Today",
120+
"updatedOn":"Tomorrow",
121+
"componentName":"snakeyaml",
122+
"componentVersionName":"2.0"
123+
}
124+
]
64125
65-
def test_vulnerabilities(self, client):
66-
with pytest.raises(NotImplementedError):
67-
client.vulnerabilites() != None
126+
}
127+
"""
128+
with requests_mock.Mocker() as m:
129+
m.get(f"{TEST_URL}/codeinsight/api/projects/{project_id}/inventorySummary",
130+
text=fake_response_json)
131+
project_inventory_summary = client.projects.get_inventory_summary(project_id)
132+
133+
assert len(project_inventory_summary) == 2
134+
assert project_inventory_summary[1].id == 12346
135+

tests/test_handlers.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pytest
2+
3+
from codeinsight_sdk import CodeInsightClient
4+
from codeinsight_sdk.handlers import *
5+
from codeinsight_sdk.models import *
6+
7+
class TestHandlers(object):
8+
@pytest.fixture
9+
def client(self):
10+
return CodeInsightClient("","")
11+
12+
def test_bad_handler(self, client):
13+
with pytest.raises(Exception):
14+
Handler.create(client, "BadClass")
15+
16+
def test_project_handler(self, client):
17+
project_handler = Handler.create(client, Project)
18+
assert isinstance(project_handler, ProjectHandler)
19+
assert issubclass(ProjectHandler, Handler)

tests/test_not_implemented.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import pytest
2+
3+
from codeinsight_sdk import CodeInsightClient
4+
5+
class TestNotImplemented(object):
6+
7+
@pytest.fixture
8+
def client(self):
9+
return CodeInsightClient("","")
10+
11+
## Coming soon features ##
12+
def test_inventories(self, client):
13+
with pytest.raises(NotImplementedError):
14+
client.inventories() != None
15+
16+
def test_vulnerabilities(self, client):
17+
with pytest.raises(NotImplementedError):
18+
client.vulnerabilites() != None
19+
20+
def test_users(self, client):
21+
with pytest.raises(NotImplementedError):
22+
client.users() != None
23+
24+
def test_licenses(self, client):
25+
with pytest.raises(NotImplementedError):
26+
client.licenses() != None
27+
28+
def test_tasks(self, client):
29+
with pytest.raises(NotImplementedError):
30+
client.tasks() != None
31+
32+
def test_rules(self, client):
33+
with pytest.raises(NotImplementedError):
34+
client.rules() != None
35+
36+
def test_reports(self, client):
37+
with pytest.raises(NotImplementedError):
38+
client.reports() != None
39+
40+
def test_files(self, client):
41+
with pytest.raises(NotImplementedError):
42+
client.files() != None
43+
44+
def test_folders(self, client):
45+
with pytest.raises(NotImplementedError):
46+
client.folders() != None

0 commit comments

Comments
 (0)