diff --git a/cli/config.py b/cli/config.py index 5afef4099..73c79d87e 100644 --- a/cli/config.py +++ b/cli/config.py @@ -255,7 +255,8 @@ def __export_config_async(endpoint: platform.Platform, what: list[str], **kwargs def __export_config(endpoint: platform.Platform, what: list[str], **kwargs) -> None: - if kwargs[options.KEYS] or options.WHAT_PROJECTS not in what or kwargs[options.FORMAT] != "json": + """Exports the configuration of the SonarQube platform""" + if kwargs[options.KEYS] or options.WHAT_PROJECTS not in what or kwargs[options.FORMAT] != "json" or not kwargs[options.REPORT_FILE]: __export_config_sync(endpoint=endpoint, what=what, **kwargs) else: __export_config_async(endpoint=endpoint, what=what, **kwargs) diff --git a/sonar/organizations.py b/sonar/organizations.py index 3dbb6bc29..b323e03f9 100644 --- a/sonar/organizations.py +++ b/sonar/organizations.py @@ -82,6 +82,9 @@ def get_object(cls, endpoint: pf.Platform, key: str) -> Organization: except HTTPError as e: if e.response.status_code == HTTPStatus.NOT_FOUND: raise exceptions.ObjectNotFound(key, f"Organization '{key}' not found") + raise e + if len(data["organizations"]) == 0: + raise exceptions.ObjectNotFound(key, f"Organization '{key}' not found") return cls.load(endpoint, data["organizations"][0]) @classmethod @@ -99,7 +102,7 @@ def load(cls, endpoint: pf.Platform, data: types.ApiPayload) -> Organization: raise exceptions.UnsupportedOperation(_NOT_SUPPORTED) uu = sqobject.uuid(data["key"], endpoint.url) o = _OBJECTS.get(uu, cls(endpoint, data["key"], data["name"])) - o.json_data = data + o._json = data o.name = data["name"] o.description = data["description"] return o diff --git a/sonar/projects.py b/sonar/projects.py index 662df88d4..7e192a728 100644 --- a/sonar/projects.py +++ b/sonar/projects.py @@ -987,6 +987,27 @@ def __get_branch_export(self, export_settings: types.ConfigSettings) -> Optional return None return util.remove_nones(branch_data) + def migration_export(self, export_settings: types.ConfigSettings) -> types.ObjectJsonRepr: + """Produces the data that is exported for SQ to SC migration""" + json_data = super().migration_export(export_settings) + json_data["detectedCi"] = self.ci() + json_data["revision"] = self.revision() + last_task = self.last_task() + json_data["backgroundTasks"] = {} + if last_task: + ctxt = last_task.scanner_context() + if ctxt: + ctxt = {k: v for k, v in ctxt.items() if k not in _UNNEEDED_CONTEXT_DATA} + t_hist = [] + for t in self.task_history(): + t_hist.append({k: v for k, v in t._json.items() if k not in _UNNEEDED_TASK_DATA}) + json_data["backgroundTasks"] = { + "lastTaskScannerContext": ctxt, + # "lastTaskWarnings": last_task.warnings(), + "taskHistory": t_hist, + } + return json_data + def export(self, export_settings: types.ConfigSettings, settings_list: dict[str, str] = None) -> types.ObjectJsonRepr: """Exports the entire project configuration as JSON @@ -1018,22 +1039,6 @@ def export(self, export_settings: types.ConfigSettings, settings_list: dict[str, if export_settings.get("MODE", "") == "MIGRATION": json_data.update(self.migration_export(export_settings)) - json_data["detectedCi"] = self.ci() - json_data["revision"] = self.revision() - last_task = self.last_task() - json_data["backgroundTasks"] = {} - if last_task: - ctxt = last_task.scanner_context() - if ctxt: - ctxt = {k: v for k, v in ctxt.items() if k not in _UNNEEDED_CONTEXT_DATA} - t_hist = [] - for t in self.task_history(): - t_hist.append({k: v for k, v in t._json.items() if k not in _UNNEEDED_TASK_DATA}) - json_data["backgroundTasks"] = { - "lastTaskScannerContext": ctxt, - # "lastTaskWarnings": last_task.warnings(), - "taskHistory": t_hist, - } settings_dict = settings.get_bulk(endpoint=self.endpoint, component=self, settings_list=settings_list, include_not_set=False) # json_data.update({s.to_json() for s in settings_dict.values() if include_inherited or not s.inherited}) diff --git a/sonar/sqobject.py b/sonar/sqobject.py index d312286fa..f8605bf72 100644 --- a/sonar/sqobject.py +++ b/sonar/sqobject.py @@ -134,7 +134,7 @@ def search_objects(endpoint: object, object_class: any, params: types.ApiParams, objects_list = {} data = json.loads(endpoint.get(api, params=new_params).text) for obj in data[returned_field]: - if object_class.__name__ in ("Portfolio", "Group", "QualityProfile", "User", "Application", "Project"): + if object_class.__name__ in ("Portfolio", "Group", "QualityProfile", "User", "Application", "Project", "Organization"): objects_list[obj[key_field]] = object_class.load(endpoint=endpoint, data=obj) else: objects_list[obj[key_field]] = object_class(endpoint, obj[key_field], data=obj) diff --git a/test/test_sonarcloud.py b/test/test_sonarcloud.py new file mode 100644 index 000000000..b02c4931d --- /dev/null +++ b/test/test_sonarcloud.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# +# sonar-tools tests +# Copyright (C) 2024 Olivier Korach +# mailto:olivier.korach AT gmail DOT com +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +""" sonarcloud tests """ + +import os +import sys +from unittest.mock import patch +import pytest + +import utilities as util +from sonar import errcodes, exceptions +from sonar import organizations +import cli.options as opt +from cli import config + +CMD = "config.py" +SC_OPTS = [f"-{opt.URL_SHORT}", "https://sonarcloud.io", f"-{opt.TOKEN_SHORT}", os.getenv("SONAR_TOKEN_SONARCLOUD")] + +OPTS = [CMD] + SC_OPTS + [f"-{opt.EXPORT_SHORT}", f"-{opt.REPORT_FILE_SHORT}", util.JSON_FILE] +MY_ORG_1 = "okorach" +MY_ORG_2 = "okorach-github" + +def test_sc_config_export() -> None: + """test_sc_config_export""" + util.clean(util.JSON_FILE) + with pytest.raises(SystemExit) as e: + with patch.object(sys, "argv", OPTS + [f"-{opt.ORG_SHORT}", "okorach"]): + config.main() + assert int(str(e.value)) == errcodes.OK + assert util.file_not_empty(util.JSON_FILE) + util.clean(util.JSON_FILE) + + +def test_sc_config_export_no_org() -> None: + """test_sc_config_export""" + util.clean(util.JSON_FILE) + with pytest.raises(SystemExit) as e: + with patch.object(sys, "argv", OPTS): + config.main() + assert int(str(e.value)) == errcodes.ARGS_ERROR + assert not os.path.isfile(util.JSON_FILE) + + +def test_org_search() -> None: + """test_org_search""" + org_list = organizations.search(endpoint=util.SC) + assert MY_ORG_1 in org_list + assert MY_ORG_2 in org_list + + +def test_org_get_list() -> None: + """test_org_search""" + org_list = organizations.get_list(endpoint=util.SC) + assert MY_ORG_1 in org_list + assert MY_ORG_2 in org_list + + org_list = organizations.get_list(endpoint=util.SC, key_list=[MY_ORG_1]) + assert MY_ORG_1 in org_list + assert MY_ORG_2 not in org_list + + +def test_org_get_non_existing() -> None: + """test_org_search_sq""" + with pytest.raises(exceptions.ObjectNotFound): + _ = organizations.Organization.get_object(endpoint=util.SC, key="oko_foo_bar") + + with pytest.raises(exceptions.ObjectNotFound): + _ = organizations.get_list(endpoint=util.SC, key_list=["oko_foo_bar"]) + + +def test_org_str() -> None: + """test_org_str""" + org = organizations.Organization.get_object(endpoint=util.SC, key=MY_ORG_1) + assert str(org) == f"organization key '{MY_ORG_1}'" + + +def test_org_export() -> None: + """test_org_export""" + org = organizations.Organization.get_object(endpoint=util.SC, key=MY_ORG_1) + exp = org.export() + assert "newCodePeriod" in exp + + +def test_org_attr() -> None: + """test_org_attr""" + org = organizations.Organization.get_object(endpoint=util.SC, key=MY_ORG_1) + assert org.key == MY_ORG_1 + assert org.name == "Olivier Korach" + assert org._json["url"] == "https://github.com/okorach" + (nc_type, nc_val) = org.new_code_period() + assert nc_type == "PREVIOUS_VERSION" + assert org.subscription() == "FREE" + assert org.alm()["key"] == "github" + + +def test_org_search_sq() -> None: + """test_org_search_sq""" + with pytest.raises(exceptions.UnsupportedOperation): + _ = organizations.search(endpoint=util.SQ) + + with pytest.raises(exceptions.UnsupportedOperation): + _ = organizations.get_list(endpoint=util.SQ) diff --git a/test/utilities.py b/test/utilities.py index 2fece9586..46289cee6 100644 --- a/test/utilities.py +++ b/test/utilities.py @@ -43,6 +43,7 @@ CE_OPTS = [f"-{opt.URL_SHORT}", LATEST_CE, f"-{opt.TOKEN_SHORT}", os.getenv("SONAR_TOKEN_ADMIN_USER")] SQ = platform.Platform(url=os.getenv("SONAR_HOST_URL"), token=os.getenv("SONAR_TOKEN_ADMIN_USER")) +SC = platform.Platform(url="https://sonarcloud.io", token=os.getenv("SONAR_TOKEN_SONARCLOUD")) TEST_SQ = platform.Platform(url=LATEST_TEST, token=os.getenv("SONAR_TOKEN_ADMIN_USER"))