import base64 import subprocess from unittest.mock import MagicMock import pytest from id_configurator.config import Configuration from id_configurator.context import Context from id_configurator.modules.certificates import CertificatesModule CERT_CONTENT = base64.b64encode(b"fake-cert-bytes").decode("ascii") def make_config(with_certificate: bool) -> Configuration: data = { "uuid": "12345678-1234-5678-1234-567812345678", "organisation": "InterDiode", } if with_certificate: data["configurations"] = { "certificates": [{"name": "new-root-ca", "content": CERT_CONTENT}] } return Configuration.model_validate(data) def test_is_relevant_true_when_certificates_configured(tmp_path) -> None: context = Context(config=make_config(True), dst=tmp_path / "dst", src=None) assert CertificatesModule().is_relevant(context) is True # nosec B101 def test_is_relevant_false_when_no_certificates(tmp_path) -> None: context = Context(config=make_config(False), dst=tmp_path / "dst", src=None) assert CertificatesModule().is_relevant(context) is False # nosec B101 def _mock_success(monkeypatch, stderr: str = "") -> MagicMock: mock_run = MagicMock( return_value=subprocess.CompletedProcess( args=[], returncode=0, stdout="", stderr=stderr ) ) monkeypatch.setattr("id_configurator.modules.certificates.subprocess.run", mock_run) return mock_run def test_apply_writes_certificate_and_calls_update_ca_certificates( tmp_path, monkeypatch ) -> None: mock_run = _mock_success(monkeypatch) dst = tmp_path / "dst" context = Context(config=make_config(True), dst=dst, src=None) CertificatesModule().apply(context) written = dst / "usr/local/share/ca-certificates/new-root-ca.crt" assert written.read_bytes() == b"fake-cert-bytes" # nosec B101 mock_run.assert_called_once() args = mock_run.call_args.args[0] assert args[0] == "update-ca-certificates" # nosec B101 assert "--certsconf" not in args # nosec B101 assert "--certsdir" not in args # nosec B101 def test_apply_preserves_existing_local_certificates_from_src( tmp_path, monkeypatch ) -> None: mock_run = _mock_success(monkeypatch, stderr="some warning") src = tmp_path / "src" local_certs_dir = src / "usr/local/share/ca-certificates" local_certs_dir.mkdir(parents=True) (local_certs_dir / "existing.crt").write_bytes(b"existing-bytes") (local_certs_dir / "new-root-ca.crt").write_bytes( b"stale-bytes-should-be-overwritten" ) (src / "etc/ca-certificates.conf").parent.mkdir(parents=True) (src / "etc/ca-certificates.conf").write_text("existing.crt\n", encoding="utf-8") (src / "usr/share/ca-certificates").mkdir(parents=True) dst = tmp_path / "dst" context = Context(config=make_config(True), dst=dst, src=src) CertificatesModule().apply(context) local_dst = dst / "usr/local/share/ca-certificates" assert (local_dst / "existing.crt").read_bytes() == b"existing-bytes" # nosec B101 assert (local_dst / "new-root-ca.crt").read_bytes() == b"fake-cert-bytes" # nosec B101 args = mock_run.call_args.args[0] assert "--certsconf" in args # nosec B101 assert str(src / "etc/ca-certificates.conf") in args # nosec B101 assert "--certsdir" in args # nosec B101 assert str(src / "usr/share/ca-certificates") in args # nosec B101 def test_update_ca_certificates_raises_when_binary_missing( tmp_path, monkeypatch ) -> None: monkeypatch.setattr( "id_configurator.modules.certificates.subprocess.run", MagicMock(side_effect=FileNotFoundError()), ) context = Context(config=make_config(True), dst=tmp_path / "dst", src=None) with pytest.raises(RuntimeError, match="update-ca-certificates is not available"): CertificatesModule().apply(context) def test_update_ca_certificates_raises_on_failure(tmp_path, monkeypatch) -> None: error = subprocess.CalledProcessError( returncode=1, cmd=["update-ca-certificates"], stderr="boom" ) monkeypatch.setattr( "id_configurator.modules.certificates.subprocess.run", MagicMock(side_effect=error), ) context = Context(config=make_config(True), dst=tmp_path / "dst", src=None) with pytest.raises(RuntimeError, match="update-ca-certificates failed"): CertificatesModule().apply(context) # nosec B101