diff --git a/MANIFEST.in b/MANIFEST.in index d2529de..1d00492 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include Makefile include requirements*.txt include version.txt include README.md recursive-include swh py.typed -recursive-include swh/scanner/tests/data/ *.tgz +recursive-include swh/scanner/tests/data/ * diff --git a/swh/scanner/tests/data/global.yml b/swh/scanner/tests/data/global.yml new file mode 100644 index 0000000..76149de --- /dev/null +++ b/swh/scanner/tests/data/global.yml @@ -0,0 +1,3 @@ +web-api: + url: "https://example.com/api/1" + auth-token: "dummy" diff --git a/swh/scanner/tests/data/global2.yml b/swh/scanner/tests/data/global2.yml new file mode 100644 index 0000000..7e1f99d --- /dev/null +++ b/swh/scanner/tests/data/global2.yml @@ -0,0 +1,3 @@ +web-api: + url: "https://example.com/api/2/" + auth-token: "dummy2" diff --git a/swh/scanner/tests/test_cli.py b/swh/scanner/tests/test_cli.py new file mode 100644 index 0000000..eb17079 --- /dev/null +++ b/swh/scanner/tests/test_cli.py @@ -0,0 +1,121 @@ +# Copyright (C) 2020 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from pathlib import Path +from unittest.mock import Mock, call + +from click.testing import CliRunner +import pytest + +import swh.scanner.cli as cli +import swh.scanner.scanner as scanner + +DATADIR = Path(__file__).absolute().parent / "data" +CONFIG_PATH_GOOD = str(DATADIR / "global.yml") +CONFIG_PATH_GOOD2 = str(DATADIR / "global2.yml") # alternative to global.yml +ROOTPATH_GOOD = str(DATADIR) + + +@pytest.fixture(scope="function") +def m_scanner(mocker): + """Returns a mock swh.scanner.scanner object with all attributes mocked""" + # Customizable mock of scanner module + # Fortunately, noop is the default behavior for all methods + scanner_mock = Mock(scanner) + yield mocker.patch("swh.scanner.scanner", scanner_mock) + + +@pytest.fixture(scope="function") +def spy_configopen(mocker): + """Returns a mock of open builtin scoped to swh.core.config""" + yield mocker.patch("swh.core.config.open", wraps=open) + + +@pytest.fixture(scope="function") +def cli_runner(monkeypatch, tmp_path): + """Return a CliRunner with default environment variable SWH_CONFIG_FILE unset""" + BAD_CONFIG_PATH = str(tmp_path / "missing") + monkeypatch.setattr(cli, "DEFAULT_CONFIG_PATH", BAD_CONFIG_PATH) + return CliRunner(env={"SWH_CONFIG_FILE": None}) + + +# TEST BEGIN + +# For nominal code paths, check that the right config file is loaded +# scanner is mocked to not run actual scan, config loading is mocked to check its usage + + +def test_smoke(cli_runner): + """Break if basic functionality breaks""" + res = cli_runner.invoke(cli.scanner, ["scan", "-h"]) + assert res.exit_code == 0 + + +def test_config_path_option_bad(cli_runner, tmp_path): + """Test bad option no envvar bad default""" + CONFPATH_BAD = str(tmp_path / "missing") + res = cli_runner.invoke(cli.scanner, ["-C", CONFPATH_BAD, "scan", ROOTPATH_GOOD]) + assert res.exit_code != 0 + + +def test_default_config_path(cli_runner, m_scanner, spy_configopen, monkeypatch): + """Test no option no envvar good default""" + monkeypatch.setattr(cli, "DEFAULT_CONFIG_PATH", CONFIG_PATH_GOOD) + res = cli_runner.invoke(cli.scanner, ["scan", ROOTPATH_GOOD]) + assert res.exit_code == 0 + assert spy_configopen.call_args == call(CONFIG_PATH_GOOD) + assert m_scanner.scan.call_count == 1 + + +def test_root_no_config(cli_runner, m_scanner, spy_configopen): + """Test no config = no option no envvar bad default, good root""" + res = cli_runner.invoke(cli.scanner, ["scan", ROOTPATH_GOOD]) + assert res.exit_code == 0 + assert spy_configopen.call_count == 0 + assert m_scanner.scan.call_count == 1 + + +def test_root_bad(cli_runner, tmp_path): + """Test no option no envvar bad default bad root""" + ROOTPATH_BAD = str(tmp_path / "missing") + res = cli_runner.invoke(cli.scanner, ["scan", ROOTPATH_BAD]) + assert res.exit_code != 0 + + +def test_config_path_envvar_good(cli_runner, m_scanner, spy_configopen): + """Test no option good envvar bad default good root""" + cli_runner.env["SWH_CONFIG_FILE"] = CONFIG_PATH_GOOD + res = cli_runner.invoke(cli.scanner, ["scan", ROOTPATH_GOOD]) + assert res.exit_code == 0 + assert spy_configopen.call_args == call(CONFIG_PATH_GOOD) + assert m_scanner.scan.call_count == 1 + + +def test_config_path_envvar_bad(cli_runner, tmp_path): + """Test no option bad envvar bad default good root""" + CONFPATH_BAD = str(tmp_path / "missing") + cli_runner.env["SWH_CONFIG_FILE"] = CONFPATH_BAD + res = cli_runner.invoke(cli.scanner, ["scan", ROOTPATH_GOOD]) + assert res.exit_code != 0 + + +def test_config_path_option_envvar(cli_runner, m_scanner, spy_configopen): + """Test good option good envvar bad default good root + Check that option has precedence over envvar""" + cli_runner.env["SWH_CONFIG_FILE"] = CONFIG_PATH_GOOD2 + res = cli_runner.invoke( + cli.scanner, ["-C", CONFIG_PATH_GOOD, "scan", ROOTPATH_GOOD] + ) + assert res.exit_code == 0 + assert spy_configopen.call_args == call(CONFIG_PATH_GOOD) + assert m_scanner.scan.call_count == 1 + + +def test_api_url_option(cli_runner, m_scanner): + """Test no config good root good url""" + API_URL = "https://example.com/api/1" # without trailing "/" + res = cli_runner.invoke(cli.scanner, ["scan", ROOTPATH_GOOD, "-u", API_URL]) + assert res.exit_code == 0 + assert m_scanner.scan.call_count == 1