Skip to content

Commit

Permalink
Codebeamer-lobster supports CB-API V3
Browse files Browse the repository at this point in the history
PTC strongly recommends to use codebeamer's rest api v3 instead of v1. The used endpoints are updated and one unittest has been added.

Resolves #49
Resolves #49
  • Loading branch information
TannazVhdBMWExt committed Jul 26, 2024
1 parent 14f23a8 commit 49a132d
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 44 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

### 0.9.17-dev

* The `lobster-codebeamer` tool now uses codebeamer api v3

* The `lobster-html-report` tool now supports argument `--dot` to specify
the path to the graphviz dot utility instead of expecting it in PATH

Expand Down
62 changes: 18 additions & 44 deletions lobster/tools/codebeamer/codebeamer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@
import argparse
import netrc

from copy import copy

import requests
from urllib.parse import quote

from lobster.items import Tracing_Tag, Requirement
from lobster.location import Codebeamer_Reference
Expand Down Expand Up @@ -84,27 +83,23 @@ def query_cb_single(cb_config, url):
def get_single_item(cb_config, item_id):
assert isinstance(item_id, int) and item_id > 0

url = "%s/item/%u" % (cb_config["base"],
item_id)
url = "%s/items/%u" % (cb_config["base"], item_id)
data = query_cb_single(cb_config, url)
return data


def get_many_items_maybe(cb_config, tracker_id, item_ids):
assert isinstance(tracker_id, int)
def get_many_items(cb_config, item_ids):
assert isinstance(item_ids, set)

rv = []

base_url = "%s/tracker/%u/items/or/%s" % (
cb_config["base"],
tracker_id,
";".join("id=%u" % item_id
for item_id in item_ids))
page_id = 1
query_string = quote(f"item.id IN ({','.join(str(item_id) for item_id in item_ids)})")

while True:
data = query_cb_single(cb_config, "%s/page/%u" % (base_url,
page_id))
base_url = "%s/items/query?page=%u&pageSize=%u&queryString=%s" % (cb_config["base"], page_id,
cb_config["page_size"], query_string)
data = query_cb_single(cb_config, base_url)
rv += data["items"]
if len(rv) == data["total"]:
break
Expand All @@ -123,14 +118,13 @@ def get_query(mh, cb_config, query_id):

while total_items is None or len(rv) < total_items:
print("Fetching page %u of query..." % page_id)
url = "%s/query/%u/page/%u?pagesize=%u" % \
url = "%s/reports/%u/items?page=%u&pageSize=%u" % \
(cb_config["base"],
query_id,
page_id,
cb_config["page_size"])
data = query_cb_single(cb_config, url)
assert len(data) == 1
data = data["trackerItems"]
assert len(data) == 4

if page_id == 1 and len(data["items"]) == 0:
print("This query doesn't generate items. Please check:")
Expand All @@ -145,7 +139,7 @@ def get_query(mh, cb_config, query_id):
else:
assert total_items == data["total"]

rv += [to_lobster(cb_config, cb_item)
rv += [to_lobster(cb_config, cb_item["item"])
for cb_item in data["items"]]

page_id += 1
Expand All @@ -162,8 +156,8 @@ def to_lobster(cb_config, cb_item):
# This looks like it's business logic, maybe we should make this
# configurable?

if "type" in cb_item:
kind = cb_item["type"].get("name", "codebeamer item")
if "typeName" in cb_item:
kind = cb_item["typeName"]
else:
kind = "codebeamer item"

Expand Down Expand Up @@ -206,32 +200,12 @@ def import_tagged(mh, cb_config, items_to_import):
assert isinstance(mh, Message_Handler)
assert isinstance(cb_config, dict)
assert isinstance(items_to_import, set)
work_list = copy(items_to_import)
rv = []

tracker_id = None
while work_list:
if tracker_id is None or len(work_list) < 3:
target = work_list.pop()
print("Fetching single item %u" % target)

cb_item = get_single_item(cb_config, target)
l_item = to_lobster(cb_config, cb_item)
tracker_id = l_item.location.tracker
rv.append(l_item)

else:
print("Attempting to fetch %u items from %s" %
(len(work_list), tracker_id))
cb_items = get_many_items_maybe(cb_config, tracker_id, work_list)

for cb_item in cb_items:
l_item = to_lobster(cb_config, cb_item)
assert tracker_id == l_item.location.tracker
rv.append(l_item)
work_list.remove(l_item.location.item)

tracker_id = None
cb_items = get_many_items(cb_config, items_to_import)
for cb_item in cb_items:
l_item = to_lobster(cb_config, cb_item)
rv.append(l_item)

return rv

Expand Down Expand Up @@ -272,7 +246,7 @@ def main():

cb_config = {
"root" : options.cb_root,
"base" : "%s/cb/rest" % options.cb_root,
"base" : "%s/cb/api/v3" % options.cb_root,
"user" : options.cb_user,
"pass" : options.cb_pass,
"verify_ssl" : not options.ignore_ssl_errors,
Expand Down
96 changes: 96 additions & 0 deletions test-unit/lobster-codebeamer/test_codebeamer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import unittest
from unittest.mock import Mock, patch

from lobster.tools.codebeamer.codebeamer import get_single_item, get_many_items, to_lobster, \
import_tagged
from lobster.errors import Message_Handler

list_of_compared_attributes = ['name', 'kind', 'status', 'just_down', 'just_up', 'just_global']


class QueryCodebeamerTest(unittest.TestCase):

def _assertListEqualByAttributes(self, list1, list2):
self.assertEqual(len(list1), len(list2), "Lists length are not the same")
for obj1, obj2 in zip(list1, list2):
for attr in list_of_compared_attributes:
self.assertEqual(getattr(obj1, attr), getattr(obj2, attr), f"{obj1} is not like {obj2} in {attr}")

@patch('lobster.tools.codebeamer.codebeamer.query_cb_single')
def test_get_single_item(self, mock_get):
_item_id = 11693324
_cb_config = {'base': 'https://test.com'}
_moch_response = Mock()
_expected_test_result = {
'page': 1,
'pageSize': 100,
'total': 1,
'items': [{'item': {'id': 11693324, 'name': 'test name'}}]
}
_moch_response.return_value = _expected_test_result

mock_get.return_value = _moch_response

query_result = get_single_item(_cb_config, _item_id)
self.assertEqual(query_result, _moch_response)

@patch('lobster.tools.codebeamer.codebeamer.query_cb_single')
def test_get_many_items(self, mock_get):
_item_ids = {24406947, 21747817}
_cb_config = {'base': 'https://test.com', 'page_size': 100}
_response_items = [
{'id': 24406947, 'name': 'Test name 1'},
{'id': 21747817, 'name': 'Test name 2'}
]
_moch_response = {
'page': 1,
'pageSize': 100,
'total': 2,
'items': _response_items
}

mock_get.return_value = _moch_response

query_result = get_many_items(_cb_config, _item_ids)
self.assertEqual(query_result, _response_items)

@patch('lobster.tools.codebeamer.codebeamer.query_cb_single')
def test_import_tagged(self, mock_get):
_mh = Message_Handler()
_item_ids = {24406947, 21747817}
_cb_config = {'root': 'https://test.com/', 'base': 'https://test.com/base', 'page_size': 100}
_response_items = [
{
'id': 24406947,
'name': 'Test name 1',
'typeName': 'Requirement',
'version': 7,
'status': {'name': 'status'},
'tracker': {'id': 123}
},
{
'id': 21747817,
'name': 'Test name 2',
'typeName': 'Requirement',
'version': 10,
'status': {'name': 'status'},
'tracker': {'id': 123}
}
]
_mock_response = {
'page': 1,
'pageSize': 100,
'total': 2,
'items': _response_items
}
mock_get.return_value = _mock_response

_expected_result = [to_lobster(_cb_config, items) for items in _response_items]

import_tagged_result = import_tagged(_mh, _cb_config, _item_ids)

self._assertListEqualByAttributes(import_tagged_result, _expected_result)


if __name__ == '__main__':
unittest.main()

0 comments on commit 49a132d

Please sign in to comment.