mirror of
https://github.com/kevinveenbirkenbach/cli-gnome-extension-manager.git
synced 2026-08-16 19:52:49 +00:00
Replaces main.sh with the 'cli-gnome-extension-manager' distribution in a src layout, shipping both the cli-gnome-extension-manager and the older goexma command. sync_repository keeps the three states of the shell original apart: clone when the folder is missing, pull when it is a checkout, leave it alone otherwise, so a manually installed extension is never overwritten. build_extension still moves the checkout away before make install, but into a TemporaryDirectory instead of a fixed /tmp/<name>, which removes the collision between parallel runs and cleans up on abort. Every step passes through the exit code of the failing tool, replacing the shell chain of || exit 1; a missing gnome-extensions aborts with exit 127. The e2e suite builds a real git repository, clones it with real git and runs real make install. Adds --extensions-dir, unit, integration and container-based e2e tests, ruff and markdown/mermaid linting, CodeQL and Dependabot, and pins the core metadata to 2.4 so twine accepts the artifacts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
137 lines
4.7 KiB
Python
137 lines
4.7 KiB
Python
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from gnome_extension_manager.core import (
|
|
REPOSITORY_BINARIES,
|
|
REQUIRED_BINARIES,
|
|
MissingBinaryError,
|
|
build_extension,
|
|
default_extensions_dir,
|
|
gnome_extensions,
|
|
run,
|
|
sync_repository,
|
|
)
|
|
|
|
|
|
class TestRequiredBinaries(unittest.TestCase):
|
|
def test_declares_every_external_command_the_package_calls(self):
|
|
declared = set(REQUIRED_BINARIES) | set(REPOSITORY_BINARIES)
|
|
|
|
self.assertEqual(declared, {"gnome-extensions", "git", "make"})
|
|
|
|
def test_repository_binaries_are_not_declared_as_always_required(self):
|
|
self.assertFalse(set(REQUIRED_BINARIES) & set(REPOSITORY_BINARIES))
|
|
|
|
def test_missing_binary_is_reported_with_its_name(self):
|
|
with mock.patch(
|
|
"gnome_extension_manager.core.subprocess.run"
|
|
) as subprocess_run:
|
|
subprocess_run.side_effect = FileNotFoundError
|
|
|
|
with self.assertRaises(MissingBinaryError) as caught:
|
|
run(["git", "clone", "x", "y"])
|
|
|
|
self.assertEqual(caught.exception.binary, "git")
|
|
|
|
|
|
class TestDefaultExtensionsDir(unittest.TestCase):
|
|
def test_points_at_the_per_user_gnome_directory(self):
|
|
self.assertEqual(
|
|
default_extensions_dir(),
|
|
Path.home() / ".local" / "share" / "gnome-shell" / "extensions",
|
|
)
|
|
|
|
|
|
class TestSyncRepository(unittest.TestCase):
|
|
def test_missing_folder_is_cloned(self):
|
|
with (
|
|
tempfile.TemporaryDirectory() as base,
|
|
mock.patch("gnome_extension_manager.core.run", return_value=0) as run,
|
|
):
|
|
folder = Path(base) / "ext"
|
|
|
|
self.assertEqual(sync_repository(folder, "https://example.com/ext.git"), 0)
|
|
|
|
run.assert_called_once_with(
|
|
["git", "clone", "https://example.com/ext.git", str(folder)]
|
|
)
|
|
|
|
def test_existing_checkout_is_pulled(self):
|
|
with tempfile.TemporaryDirectory() as base:
|
|
folder = Path(base) / "ext"
|
|
(folder / ".git").mkdir(parents=True)
|
|
|
|
with mock.patch("gnome_extension_manager.core.run", return_value=0) as run:
|
|
self.assertEqual(sync_repository(folder, "https://e.example/x"), 0)
|
|
|
|
run.assert_called_once_with(["git", "pull"], cwd=str(folder))
|
|
|
|
def test_existing_folder_without_git_is_left_alone(self):
|
|
with tempfile.TemporaryDirectory() as base:
|
|
folder = Path(base) / "ext"
|
|
folder.mkdir()
|
|
|
|
with mock.patch("gnome_extension_manager.core.run") as run:
|
|
self.assertEqual(sync_repository(folder, "https://e.example/x"), 0)
|
|
|
|
run.assert_not_called()
|
|
|
|
def test_failed_clone_propagates_the_exit_code(self):
|
|
with (
|
|
tempfile.TemporaryDirectory() as base,
|
|
mock.patch("gnome_extension_manager.core.run", return_value=128),
|
|
):
|
|
self.assertEqual(
|
|
sync_repository(Path(base) / "ext", "https://e.example/x"), 128
|
|
)
|
|
|
|
|
|
class TestBuildExtension(unittest.TestCase):
|
|
def test_extension_without_makefile_is_kept_in_place(self):
|
|
with tempfile.TemporaryDirectory() as base:
|
|
folder = Path(base) / "ext"
|
|
folder.mkdir()
|
|
|
|
with mock.patch("gnome_extension_manager.core.run") as run:
|
|
self.assertEqual(build_extension(folder), 0)
|
|
|
|
run.assert_not_called()
|
|
self.assertTrue(folder.exists())
|
|
|
|
def test_makefile_extension_is_built_and_the_checkout_removed(self):
|
|
with tempfile.TemporaryDirectory() as base:
|
|
folder = Path(base) / "ext"
|
|
folder.mkdir()
|
|
(folder / "Makefile").write_text("install:\n\ttrue\n")
|
|
|
|
with mock.patch("gnome_extension_manager.core.run", return_value=0) as run:
|
|
self.assertEqual(build_extension(folder), 0)
|
|
|
|
self.assertEqual(run.call_args.args[0], ["make", "install"])
|
|
self.assertFalse(folder.exists())
|
|
|
|
def test_failed_build_propagates_the_exit_code(self):
|
|
with tempfile.TemporaryDirectory() as base:
|
|
folder = Path(base) / "ext"
|
|
folder.mkdir()
|
|
(folder / "Makefile").write_text("install:\n\tfalse\n")
|
|
|
|
with mock.patch("gnome_extension_manager.core.run", return_value=2):
|
|
self.assertEqual(build_extension(folder), 2)
|
|
|
|
|
|
class TestGnomeExtensions(unittest.TestCase):
|
|
def test_delegates_to_the_gnome_extensions_tool(self):
|
|
with mock.patch("gnome_extension_manager.core.run", return_value=0) as run:
|
|
gnome_extensions("enable", "dash-to-dock@example")
|
|
|
|
run.assert_called_once_with(
|
|
["gnome-extensions", "enable", "dash-to-dock@example"]
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|