From 49a132dbc9e24f65116d094bfaa6787784f94a22 Mon Sep 17 00:00:00 2001 From: tannazvhdVispi Date: Fri, 26 Jul 2024 14:23:53 +0200 Subject: [PATCH] Codebeamer-lobster supports CB-API V3 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 https://github.com/bmw-software-engineering/lobster/issues/49 Resolves #49 --- CHANGELOG.md | 2 + lobster/tools/codebeamer/codebeamer.py | 62 ++++-------- .../lobster-codebeamer/test_codebeamer.py | 96 +++++++++++++++++++ 3 files changed, 116 insertions(+), 44 deletions(-) create mode 100644 test-unit/lobster-codebeamer/test_codebeamer.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 82459df8..26e5d8a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/lobster/tools/codebeamer/codebeamer.py b/lobster/tools/codebeamer/codebeamer.py index f636b02c..46a46a48 100755 --- a/lobster/tools/codebeamer/codebeamer.py +++ b/lobster/tools/codebeamer/codebeamer.py @@ -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 @@ -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 @@ -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:") @@ -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 @@ -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" @@ -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 @@ -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, diff --git a/test-unit/lobster-codebeamer/test_codebeamer.py b/test-unit/lobster-codebeamer/test_codebeamer.py new file mode 100644 index 00000000..6bdb6a31 --- /dev/null +++ b/test-unit/lobster-codebeamer/test_codebeamer.py @@ -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() \ No newline at end of file