Files
2026-10-05 20:46:38 +02:00

210 lines
6.2 KiB
Python

import json
from unittest.mock import MagicMock
from typer.testing import CliRunner
from id_configurator import cli
runner = CliRunner()
MINIMAL_CONFIG = {
"uuid": "12345678-1234-5678-1234-567812345678",
"organisation": "InterDiode",
}
CONFIG_WITH_CERT = {
**MINIMAL_CONFIG,
"configurations": {"certificates": [{"name": "my-ca", "content": "fake"}]},
}
CONFIG_WITH_GO_REPO = {
**MINIMAL_CONFIG,
"configurations": {
"go": {"repositories": [{"name": "demo", "url": "https://example.invalid/"}]}
},
}
def write_config(tmp_path, data) -> str:
path = tmp_path / "config.json"
path.write_text(json.dumps(data), encoding="utf-8")
return str(path)
def test_validate_valid_config(tmp_path) -> None:
config_path = write_config(tmp_path, MINIMAL_CONFIG)
result = runner.invoke(cli.app, ["validate", "--config", config_path])
assert result.exit_code == 0
assert "is valid" in result.stdout
def test_validate_without_uuid_or_organisation(tmp_path) -> None:
config_path = write_config(tmp_path, {})
result = runner.invoke(cli.app, ["validate", "--config", config_path])
assert result.exit_code == 0
assert "<no uuid>" in result.stdout
assert "<no organisation>" in result.stdout
def test_validate_without_config_fails() -> None:
result = runner.invoke(cli.app, ["validate"])
assert result.exit_code != 0
assert "either --config, --config-env" in result.output
def _mock_urlopen(content: bytes) -> MagicMock:
response = MagicMock()
response.read.return_value = content
response.__enter__.return_value = response
response.__exit__.return_value = False
return MagicMock(return_value=response)
def test_validate_from_config_url(monkeypatch) -> None:
monkeypatch.setattr(
"id_configurator.config.urllib.request.urlopen",
_mock_urlopen(json.dumps(MINIMAL_CONFIG).encode("utf-8")),
)
result = runner.invoke(
cli.app, ["validate", "--config-url", "https://example.invalid/config.json"]
)
assert result.exit_code == 0
assert "is valid" in result.stdout
def test_validate_from_config_url_insecure(monkeypatch) -> None:
mock_urlopen = _mock_urlopen(json.dumps(MINIMAL_CONFIG).encode("utf-8"))
monkeypatch.setattr("id_configurator.config.urllib.request.urlopen", mock_urlopen)
result = runner.invoke(
cli.app,
[
"validate",
"--config-url",
"https://example.invalid/config.json",
"--config-url-insecure",
],
)
assert result.exit_code == 0
assert "is valid" in result.stdout
_, kwargs = mock_urlopen.call_args
assert kwargs["context"].verify_mode.name == "CERT_NONE"
def test_list_modules() -> None:
result = runner.invoke(cli.app, ["list-modules"])
assert result.exit_code == 0
assert set(result.stdout.splitlines()) == {
"apk",
"apt",
"certificates",
"go",
"java",
"npm",
"nuget",
"php",
"python",
"ruby",
"rust",
"yum",
}
def test_apply_skips_irrelevant_modules(tmp_path, caplog) -> None:
config_path = write_config(tmp_path, MINIMAL_CONFIG)
dst = tmp_path / "dst"
with caplog.at_level("DEBUG", logger="id_configurator"):
result = runner.invoke(
cli.app, ["apply", "--config", config_path, "--dst", str(dst), "-v"]
)
assert result.exit_code == 0
assert "skipping module apk" in caplog.text
assert "skipping module java" in caplog.text
assert "skipping module apt" in caplog.text
assert "skipping module certificates" in caplog.text
assert "skipping module yum" in caplog.text
assert "skipping module python" in caplog.text
def test_apply_runs_relevant_module(tmp_path) -> None:
config_path = write_config(tmp_path, CONFIG_WITH_CERT)
dst = tmp_path / "dst"
result = runner.invoke(
cli.app, ["apply", "--config", config_path, "--dst", str(dst)]
)
assert result.exit_code == 0
assert (dst / "usr/local/share/ca-certificates/my-ca.crt").read_bytes() == b"fake"
assert (dst / "etc/ssl/certs/ca-certificates.crt").read_bytes() == b"fake\n"
def test_apply_with_user_option_writes_per_user_config(tmp_path) -> None:
config_path = write_config(tmp_path, CONFIG_WITH_GO_REPO)
dst = tmp_path / "dst"
result = runner.invoke(
cli.app,
[
"apply",
"--config",
config_path,
"--dst",
str(dst),
"--user",
"alice",
"--user",
"root",
],
)
assert result.exit_code == 0
assert (dst / "home/alice/.config/go/env").read_text(encoding="utf-8") == (
"GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
)
assert (dst / "root/.config/go/env").read_text(encoding="utf-8") == (
"GOPROXY=https://example.invalid/\nGOSUMDB=off\n"
)
def test_apply_with_auto_users_reads_passwd_from_src(tmp_path) -> None:
config_path = write_config(tmp_path, CONFIG_WITH_GO_REPO)
src = tmp_path / "src"
(src / "etc").mkdir(parents=True)
(src / "etc/passwd").write_text(
"root:x:0:0:root:/root:/bin/bash\n"
"alice:x:1000:1000:Alice:/home/alice:/bin/bash\n"
"daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n",
encoding="utf-8",
)
dst = tmp_path / "dst"
result = runner.invoke(
cli.app,
[
"apply",
"--config",
config_path,
"--dst",
str(dst),
"--src",
str(src),
"--auto-users",
],
)
assert result.exit_code == 0
# root is always included despite its uid 0; daemon (nologin shell) is
# a system account, excluded.
assert (dst / "root/.config/go/env").exists()
assert (dst / "home/alice/.config/go/env").exists()
assert not (dst / "usr/sbin/.config/go/env").exists()
def test_main_default_name_calls_app(monkeypatch) -> None:
mock_app = MagicMock()
monkeypatch.setattr(cli, "app", mock_app)
cli.main()
mock_app.assert_called_once()
def test_main_other_name_does_not_call_app(monkeypatch) -> None:
mock_app = MagicMock()
monkeypatch.setattr(cli, "app", mock_app)
cli.main("id_configurator.__main__")
mock_app.assert_not_called()