From ee99edb93af68f72eeb4326250164bc43a8cbbed Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Wed, 1 Jul 2026 11:35:17 +0530 Subject: [PATCH] feat(items): wrap GET totalusercopyrights endpoint Refs #52. Adds Items.item_user_copyrights() for GET /uploads/{id}/item/{itemId}/totalusercopyrights, the user-findings counterpart to the existing item_copyrights() (totalcopyrights). It mirrors that method exactly, including the {active,inactive} status query param and the int return type. Validated against the OpenAPI spec (API 1.6.2 / Fossology 4.4.0). Tests mirror the existing totalcopyrights coverage: live happy path, unknown-item 404, and a mocked 500 error path. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: RAJVEER42 --- fossology/items.py | 34 ++++++++++++++++++++++++++++++++++ tests/test_items.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/fossology/items.py b/fossology/items.py index 20503b2..e6795d5 100644 --- a/fossology/items.py +++ b/fossology/items.py @@ -85,6 +85,40 @@ def item_copyrights( description = f"API error while getting total copyrights for item {item_id} from upload {upload.uploadname}." raise FossologyApiError(description, response) + def item_user_copyrights( + self, + upload: Upload, + item_id: int, + status: CopyrightStatus, + ) -> int: + """Get the total user copyright findings of the mentioned upload tree ID + + API Endpoint: GET /uploads/{id}/item/{itemId}/totalusercopyrights + + :param upload: the upload to get items from + :param item_id: the id of the item + :param status: the status of the copyrights + :type upload: Upload + :type item_id: int, + :type status: CopyrightStatus + :return: the total number of user copyright findings for the uploadtree item + :rtype: int + :raises FossologyApiError: if the REST call failed + """ + response = self.session.get( # type: ignore + f"{self.api}/uploads/{upload.id}/item/{item_id}/totalusercopyrights?status={status.value}" # type: ignore + ) + + if response.status_code == 200: + return response.json()["total_copyrights"] + + elif response.status_code == 404: + description = f"Upload {upload.id} or item {item_id} not found" + raise FossologyApiError(description, response) + else: + description = f"API error while getting total user copyrights for item {item_id} from upload {upload.uploadname}." + raise FossologyApiError(description, response) + def get_clearing_history( self, upload: Upload, diff --git a/tests/test_items.py b/tests/test_items.py index 108092c..da745b4 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -84,6 +84,39 @@ def test_item_copyrights_500_error( ) +def test_item_user_copyrights(foss: Fossology, upload_with_jobs: Upload): + files, _ = foss.search(license="BSD") + num_copyrights = foss.item_user_copyrights( + upload_with_jobs, files[0].uploadTreeId, CopyrightStatus.ACTIVE + ) + assert num_copyrights == 0 + + +def test_item_user_copyrights_with_unknown_item_raises_api_error( + foss: Fossology, upload_with_jobs: Upload +): + with pytest.raises(FossologyApiError) as excinfo: + foss.item_user_copyrights(upload_with_jobs, 1, CopyrightStatus.ACTIVE) + assert f"Upload {upload_with_jobs.id} or item 1 not found" in str(excinfo.value) + + +@responses.activate +def test_item_user_copyrights_500_error( + foss: Fossology, foss_server: str, upload_with_jobs: Upload +): + responses.add( + responses.GET, + f"{foss_server}/api/v1/uploads/{upload_with_jobs.id}/item/1/totalusercopyrights", + status=500, + ) + with pytest.raises(FossologyApiError) as excinfo: + foss.item_user_copyrights(upload_with_jobs, 1, CopyrightStatus.ACTIVE) + assert ( + f"API error while getting total user copyrights for item 1 from upload {upload_with_jobs.uploadname}" + in excinfo.value.message + ) + + def test_upload_get_clearing_history(foss: Fossology, upload_with_jobs: Upload): files, _ = foss.search(license="BSD") history = foss.get_clearing_history(upload_with_jobs, files[0].uploadTreeId)