From 5bccfec88421a62c6ec7fa55cff33b107dd20abd Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Wed, 1 Jul 2026 11:33:44 +0530 Subject: [PATCH 1/2] feat(license): wrap GET /license/export-csv endpoint Refs #52. Adds LicenseEndpoint.export_licenses_csv() for GET /license/export-csv, the export counterpart to the merged import_licenses_csv() (#185). Returns the exported licenses as CSV text and accepts an optional license id (0 = all). Verified against Fossology 4.4.0 (API 1.6.1) running in a container: an export->import round-trip test passes (idempotent), plus a mocked 403 error path. Signed-off-by: RAJVEER42 --- fossology/license.py | 22 ++++++++++++++++++++++ tests/test_license.py | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/fossology/license.py b/fossology/license.py index 0f5b5c1..a9a5758 100644 --- a/fossology/license.py +++ b/fossology/license.py @@ -195,6 +195,28 @@ def import_licenses_csv( description = f"Unable to import licenses from {csv_file}" raise FossologyApiError(description, response) + def export_licenses_csv(self, license_id: int = 0) -> str: + """Export licenses as CSV. + + API Endpoint: GET /license/export-csv + + :param license_id: id of the license to export, 0 to export all (default: 0) + :type license_id: int + :return: the exported licenses as CSV text + :rtype: str + :raises FossologyApiError: if the REST call failed + """ + response = self.session.get( + f"{self.api}/license/export-csv", params={"id": license_id} + ) + + if response.status_code == 200: + logger.info(f"Exported licenses as CSV (id={license_id})") + return response.text + + description = f"Unable to export licenses as CSV (id={license_id})" + raise FossologyApiError(description, response) + def update_license( self, shortname: str, diff --git a/tests/test_license.py b/tests/test_license.py index 7e1c49a..07b74b3 100644 --- a/tests/test_license.py +++ b/tests/test_license.py @@ -86,6 +86,28 @@ def test_import_licenses_csv_request_payload( assert "shortname,fullname\nFoo,Foo License" in body +def test_export_import_licenses_csv_roundtrip(foss: fossology.Fossology, tmp_path): + # Export the full license set, then re-import it. The export format matches + # the import format, and re-importing existing licenses is idempotent. + exported = foss.export_licenses_csv() + assert isinstance(exported, str) + assert exported + csv_path = tmp_path / "exported.csv" + csv_path.write_text(exported) + message = foss.import_licenses_csv(str(csv_path)) + assert "Read csv" in message + + +@responses.activate +def test_export_licenses_csv_error(foss_server: str, foss: fossology.Fossology): + responses.add( + responses.GET, f"{foss_server}/api/v1/license/export-csv", status=403 + ) + with pytest.raises(FossologyApiError) as excinfo: + foss.export_licenses_csv() + assert "Unable to export licenses as CSV (id=0)" in str(excinfo.value) + + @responses.activate def test_detail_license_error(foss_server: str, foss: fossology.Fossology): responses.add(responses.GET, f"{foss_server}/api/v1/license/Blah", status=500) From a213d69776b77a298a138492f17c0a3af9add3ff Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Sat, 4 Jul 2026 02:42:35 +0530 Subject: [PATCH 2/2] test(license): cover single-license export via the id parameter Add two tests requested during review of #196: - export a single license with export_licenses_csv(id) and assert the CSV contains exactly that one license (header row + one data row) - assert the full export (no id) returns more than one license Verified live against Fossology 4.4.0 (API 1.6.1). Signed-off-by: RAJVEER42 --- tests/test_license.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_license.py b/tests/test_license.py index 07b74b3..def9733 100644 --- a/tests/test_license.py +++ b/tests/test_license.py @@ -1,6 +1,8 @@ # Copyright 2019 Siemens AG # SPDX-License-Identifier: MIT +import csv +import io from unittest.mock import MagicMock import pytest @@ -108,6 +110,29 @@ def test_export_licenses_csv_error(foss_server: str, foss: fossology.Fossology): assert "Unable to export licenses as CSV (id=0)" in str(excinfo.value) +def test_export_single_license_by_id(foss: fossology.Fossology): + # Export a single license using the "id" parameter and verify the CSV + # contains exactly that one license (header row + one data row). + licenses, _ = foss.list_licenses() + target = licenses[0] + exported = foss.export_licenses_csv(target.id) + rows = list(csv.reader(io.StringIO(exported))) + assert len(rows) == 2 + assert target.shortName in exported + + +def test_export_all_licenses_returns_more_than_one(foss: fossology.Fossology): + # Exporting without an id returns every license, i.e. strictly more than + # the single-license export above. + licenses, _ = foss.list_licenses() + single_rows = list( + csv.reader(io.StringIO(foss.export_licenses_csv(licenses[0].id))) + ) + all_rows = list(csv.reader(io.StringIO(foss.export_licenses_csv()))) + assert len(single_rows) - 1 == 1 + assert len(all_rows) - 1 > 1 + + @responses.activate def test_detail_license_error(foss_server: str, foss: fossology.Fossology): responses.add(responses.GET, f"{foss_server}/api/v1/license/Blah", status=500)