| # Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Unit tests for apt.py.""" | |
| import shutil | |
| import subprocess | |
| import sys | |
| import unittest | |
| from unittest import mock | |
| from build.auth import apt | |
| class AptTest(unittest.TestCase): | |
| """Unit tests for apt.py module-scoped functions.""" | |
| def setUp(self) -> None: | |
| # Clear the module's functools cache to prevent cross-contamination between test cases | |
| apt.is_available.cache_clear() | |
| @mock.patch.object(shutil, "which") | |
| def test_is_available_when_apt_exists(self, mock_which: mock.Mock) -> None: | |
| mock_which.side_effect = lambda cmd: f"/usr/bin/{cmd}" | |
| self.assertTrue(apt.is_available()) | |
| @mock.patch.object(shutil, "which") | |
| def test_is_available_when_apt_missing(self, mock_which: mock.Mock) -> None: | |
| # Simulate 'apt-cache' being missing from the host | |
| mock_which.side_effect = ( | |
| lambda cmd: None if cmd == "apt-cache" else f"/usr/bin/{cmd}" | |
| ) | |
| self.assertFalse(apt.is_available()) | |
| @mock.patch.object(shutil, "which", return_value="/usr/bin/apt") | |
| @mock.patch.object(subprocess, "run") | |
| def test_package_exists_true( | |
| self, mock_run: mock.Mock, mock_which: mock.Mock | |
| ) -> None: | |
| mock_proc = mock.Mock() | |
| mock_proc.returncode = 0 | |
| mock_run.return_value = mock_proc | |
| self.assertTrue(apt.exists("some-package")) | |
| @mock.patch.object(shutil, "which", return_value="/usr/bin/apt") | |
| @mock.patch.object(subprocess, "run") | |
| def test_package_exists_false( | |
| self, mock_run: mock.Mock, mock_which: mock.Mock | |
| ) -> None: | |
| mock_proc = mock.Mock() | |
| mock_proc.returncode = 1 | |
| mock_run.return_value = mock_proc | |
| self.assertFalse(apt.exists("some-package")) | |
| @mock.patch.object(shutil, "which", return_value="/usr/bin/apt") | |
| @mock.patch.object(subprocess, "run") | |
| def test_package_exists_os_error_returns_false( | |
| self, mock_run: mock.Mock, mock_which: mock.Mock | |
| ) -> None: | |
| mock_run.side_effect = OSError("Database locked") | |
| self.assertFalse(apt.exists("some-package")) | |
| @mock.patch.object(shutil, "which", return_value="/usr/bin/apt") | |
| def test_malformed_package_name_raises_value_error( | |
| self, mock_which: mock.Mock | |
| ) -> None: | |
| with self.assertRaises(ValueError) as ctx: | |
| apt.exists("invalid/package") | |
| self.assertIn("Malformed package name", str(ctx.exception)) | |
| self.assertIn("apt.py", str(ctx.exception)) | |
| with self.assertRaises(ValueError) as ctx: | |
| apt.install("invalid package") | |
| self.assertIn("Malformed package name", str(ctx.exception)) | |
| self.assertIn("apt.py", str(ctx.exception)) | |
| @mock.patch.object(shutil, "which", return_value="/usr/bin/apt") | |
| @mock.patch.object(sys.stdin, "isatty", return_value=True) | |
| @mock.patch.object(subprocess, "run") | |
| @mock.patch("builtins.input", return_value="y") | |
| def test_install_package_accepts( | |
| self, | |
| mock_input: mock.Mock, | |
| mock_run: mock.Mock, | |
| mock_isatty: mock.Mock, | |
| mock_which: mock.Mock, | |
| ) -> None: | |
| apt.install("some-package", interactive=True) | |
| mock_run.assert_called_once_with( | |
| ["sudo", "apt", "install", "-y", "some-package"], | |
| check=True, | |
| ) | |
| @mock.patch.object(shutil, "which", return_value="/usr/bin/apt") | |
| @mock.patch.object(sys.stdin, "isatty", return_value=True) | |
| @mock.patch.object(subprocess, "run") | |
| @mock.patch("builtins.input", return_value="n") | |
| def test_install_package_declines( | |
| self, | |
| mock_input: mock.Mock, | |
| mock_run: mock.Mock, | |
| mock_isatty: mock.Mock, | |
| mock_which: mock.Mock, | |
| ) -> None: | |
| with self.assertRaises(RuntimeError) as ctx: | |
| apt.install("some-package", interactive=True) | |
| self.assertIn("installation declined.", str(ctx.exception)) | |
| if __name__ == "__main__": | |
| unittest.main() |