Implement sjnetpkg 0.1.0, a Git-based package manager
Packages are ordinary Git repositories carrying a sjnetpkg.toml, so sjnetpkg only needs to resolve, clone, track and run them instead of inventing an archive format or build system. This adds the v0.1 CLI (install from registry or Git URL, remove, list/inspect, registry update, run) on top of the Python standard library, together with a test suite. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
"""Test bootstrap: make the ``src`` layout importable.
|
||||
|
||||
This is used by pytest. When running with the standard library test runner,
|
||||
pass the source tree explicitly, e.g.::
|
||||
|
||||
PYTHONPATH=src python3 -m unittest discover -s tests -t tests
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_SRC = Path(__file__).resolve().parents[1] / "src"
|
||||
if str(_SRC) not in sys.path:
|
||||
sys.path.insert(0, str(_SRC))
|
||||
@@ -0,0 +1,265 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from sjnetpkg import cli, config
|
||||
|
||||
GIT_AVAILABLE = shutil.which("git") is not None
|
||||
|
||||
|
||||
def run_cli(*args: str) -> tuple[int, str, str]:
|
||||
"""Invoke the CLI, capturing stdout/stderr and the exit code."""
|
||||
stdout, stderr = io.StringIO(), io.StringIO()
|
||||
with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
|
||||
code = cli.main(list(args))
|
||||
return code, stdout.getvalue(), stderr.getvalue()
|
||||
|
||||
|
||||
def write_package(root: Path, directory: str, manifest: str, files: dict[str, str] | None = None) -> Path:
|
||||
target = root / directory
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
(target / "sjnetpkg.toml").write_text(manifest, encoding="utf-8")
|
||||
for name, content in (files or {}).items():
|
||||
(target / name).write_text(content, encoding="utf-8")
|
||||
return target
|
||||
|
||||
|
||||
def make_git_repo(path: Path, manifest: str, files: dict[str, str] | None = None) -> Path:
|
||||
write_package(path.parent, path.name, manifest, files)
|
||||
subprocess.run(["git", "init", "-q", str(path)], check=True)
|
||||
subprocess.run(["git", "-C", str(path), "add", "-A"], check=True)
|
||||
subprocess.run(
|
||||
[
|
||||
"git", "-C", str(path),
|
||||
"-c", "user.email=test@example.com",
|
||||
"-c", "user.name=Test",
|
||||
"commit", "-q", "-m", "init",
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
class CliTestCase(unittest.TestCase):
|
||||
"""Base class that isolates the sjnetpkg home directory."""
|
||||
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self._tmp.name)
|
||||
self.home = self.root / ".sjnetpkg"
|
||||
patcher = mock.patch.dict(os.environ, {"SJNETPKG_HOME": str(self.home)})
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.paths = config.get_paths()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def install_fixture(self, name: str = "cool-tool", manifest: str | None = None,
|
||||
files: dict[str, str] | None = None) -> Path:
|
||||
manifest = manifest or f'name = "{name}"\nversion = "1.0.0"\n'
|
||||
repo = make_git_repo(self.root / "repos" / name, manifest, files)
|
||||
code, _, err = run_cli("-U", repo.as_uri())
|
||||
self.assertEqual(code, 0, err)
|
||||
return repo
|
||||
|
||||
|
||||
class VersionAndHelpTests(CliTestCase):
|
||||
def test_version(self) -> None:
|
||||
code, out, _ = run_cli("--version")
|
||||
self.assertEqual(code, 0)
|
||||
self.assertEqual(out.strip(), "sjnetpkg 0.1.0")
|
||||
|
||||
def test_help(self) -> None:
|
||||
code, out, _ = run_cli("--help")
|
||||
self.assertEqual(code, 0)
|
||||
self.assertIn("Usage:", out)
|
||||
for line in ("sjnetpkg -S <package>", "sjnetpkg -Sy", "sjnetpkg run <package>"):
|
||||
self.assertIn(line, out)
|
||||
|
||||
def test_no_arguments_is_a_usage_error(self) -> None:
|
||||
code, _, err = run_cli()
|
||||
self.assertEqual(code, 2)
|
||||
self.assertIn("Usage:", err)
|
||||
|
||||
def test_unknown_command(self) -> None:
|
||||
code, _, err = run_cli("frobnicate")
|
||||
self.assertEqual(code, 2)
|
||||
self.assertIn("unknown command", err)
|
||||
|
||||
|
||||
class QueryTests(CliTestCase):
|
||||
def test_list_without_packages(self) -> None:
|
||||
code, out, _ = run_cli("-Q")
|
||||
self.assertEqual(code, 0)
|
||||
self.assertIn("No packages installed.", out)
|
||||
|
||||
def test_list_alignment(self) -> None:
|
||||
config.ensure_home(self.paths)
|
||||
write_package(self.paths.pkg, "cool-tool", 'name = "cool-tool"\nversion = "1.0.0"\n')
|
||||
write_package(self.paths.pkg, "another-tool", 'name = "another-tool"\nversion = "2.4.1"\n')
|
||||
code, out, _ = run_cli("-Q")
|
||||
self.assertEqual(code, 0)
|
||||
lines = out.strip().splitlines()
|
||||
self.assertEqual(len(lines), 2)
|
||||
self.assertRegex(lines[0], r"^another-tool\s+2\.4\.1$")
|
||||
self.assertRegex(lines[1], r"^cool-tool\s+1\.0\.0$")
|
||||
# The versions are printed in the same column.
|
||||
self.assertEqual(lines[0].index("2.4.1"), lines[1].index("1.0.0"))
|
||||
|
||||
def test_info_for_unknown_package(self) -> None:
|
||||
code, _, err = run_cli("-Q", "missing")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("package 'missing' is not installed.", err)
|
||||
|
||||
|
||||
@unittest.skipUnless(GIT_AVAILABLE, "git is required for these tests")
|
||||
class InstallTests(CliTestCase):
|
||||
def test_install_from_url(self) -> None:
|
||||
repo = self.install_fixture()
|
||||
target = self.home / "pkg" / "cool-tool"
|
||||
self.assertTrue((target / ".git").is_dir())
|
||||
self.assertTrue((target / "sjnetpkg.toml").is_file())
|
||||
|
||||
def test_install_reports_package(self) -> None:
|
||||
repo = make_git_repo(
|
||||
self.root / "repos" / "cool-tool",
|
||||
'name = "cool-tool"\nversion = "1.0.0"\n',
|
||||
)
|
||||
code, out, _ = run_cli("-U", repo.as_uri())
|
||||
self.assertEqual(code, 0)
|
||||
self.assertIn("Installed cool-tool 1.0.0", out)
|
||||
|
||||
def test_second_install_is_rejected(self) -> None:
|
||||
repo = self.install_fixture()
|
||||
code, _, err = run_cli("-U", repo.as_uri())
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("package 'cool-tool' is already installed.", err)
|
||||
|
||||
def test_invalid_url_is_rejected(self) -> None:
|
||||
code, _, err = run_cli("-U", "not-a-url")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("invalid git URL", err)
|
||||
|
||||
def test_missing_manifest_does_not_leave_a_directory(self) -> None:
|
||||
repo_dir = self.root / "repos" / "empty-tool"
|
||||
repo_dir.mkdir(parents=True)
|
||||
(repo_dir / "README.md").write_text("no manifest here")
|
||||
subprocess.run(["git", "init", "-q", str(repo_dir)], check=True)
|
||||
subprocess.run(["git", "-C", str(repo_dir), "add", "-A"], check=True)
|
||||
subprocess.run(
|
||||
["git", "-C", str(repo_dir), "-c", "user.email=t@e", "-c", "user.name=T",
|
||||
"commit", "-q", "-m", "init"],
|
||||
check=True,
|
||||
)
|
||||
code, _, err = run_cli("-U", repo_dir.as_uri())
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("sjnetpkg.toml", err)
|
||||
self.assertFalse((self.home / "pkg" / "empty-tool").exists())
|
||||
|
||||
|
||||
@unittest.skipUnless(GIT_AVAILABLE, "git is required for these tests")
|
||||
class RegistryInstallTests(CliTestCase):
|
||||
def _write_registry(self, url: str, name: str = "cool-tool") -> None:
|
||||
config.ensure_home(self.paths)
|
||||
self.paths.registry.write_text(
|
||||
f'[packages.{name}]\nrepo = "{url}"\n', encoding="utf-8"
|
||||
)
|
||||
|
||||
def test_install_from_registry(self) -> None:
|
||||
repo = make_git_repo(
|
||||
self.root / "repos" / "cool-tool",
|
||||
'name = "cool-tool"\nversion = "1.0.0"\n',
|
||||
)
|
||||
self._write_registry(repo.as_uri())
|
||||
code, out, err = run_cli("-S", "cool-tool")
|
||||
self.assertEqual(code, 0, err)
|
||||
self.assertIn("Installed cool-tool 1.0.0", out)
|
||||
|
||||
def test_unknown_package(self) -> None:
|
||||
self._write_registry("https://example.com/other.git", name="other")
|
||||
code, _, err = run_cli("-S", "cool-tool")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("package 'cool-tool' was not found.", err)
|
||||
|
||||
def test_missing_local_registry(self) -> None:
|
||||
code, _, err = run_cli("-S", "cool-tool")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("no local registry found", err)
|
||||
|
||||
def test_update_registry(self) -> None:
|
||||
source = self.root / "remote-registry.toml"
|
||||
source.write_text('[packages.cool-tool]\nrepo = "https://example.com/cool-tool.git"\n')
|
||||
config.ensure_home(self.paths)
|
||||
self.paths.config.write_text(f'registry = "{source.as_uri()}"\n', encoding="utf-8")
|
||||
code, out, _ = run_cli("-Sy")
|
||||
self.assertEqual(code, 0)
|
||||
self.assertIn("Registry updated", out)
|
||||
self.assertTrue(self.paths.registry.is_file())
|
||||
|
||||
|
||||
@unittest.skipUnless(GIT_AVAILABLE, "git is required for these tests")
|
||||
class RunAndRemoveTests(CliTestCase):
|
||||
def test_run_returns_process_exit_code(self) -> None:
|
||||
manifest = (
|
||||
'name = "cool-tool"\nversion = "1.0.0"\n\n'
|
||||
f'[run]\ncommand = \'"{sys.executable}" main.py\'\n'
|
||||
)
|
||||
self.install_fixture(manifest=manifest, files={"main.py": "import sys\nsys.exit(7)\n"})
|
||||
code, _, _ = run_cli("run", "cool-tool")
|
||||
self.assertEqual(code, 7)
|
||||
|
||||
def test_run_uses_package_directory_as_working_directory(self) -> None:
|
||||
manifest = (
|
||||
'name = "cool-tool"\nversion = "1.0.0"\n\n'
|
||||
f'[run]\ncommand = \'"{sys.executable}" check_cwd.py\'\n'
|
||||
)
|
||||
script = (
|
||||
"import pathlib, sys\n"
|
||||
"sys.exit(0 if pathlib.Path('sjnetpkg.toml').exists() else 3)\n"
|
||||
)
|
||||
self.install_fixture(manifest=manifest, files={"check_cwd.py": script})
|
||||
code, _, _ = run_cli("run", "cool-tool")
|
||||
self.assertEqual(code, 0)
|
||||
|
||||
def test_run_without_run_command(self) -> None:
|
||||
self.install_fixture()
|
||||
code, _, err = run_cli("run", "cool-tool")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("does not define a run command", err)
|
||||
|
||||
def test_run_unknown_package(self) -> None:
|
||||
code, _, err = run_cli("run", "missing")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("package 'missing' is not installed.", err)
|
||||
|
||||
def test_info_and_remove(self) -> None:
|
||||
self.install_fixture()
|
||||
code, out, _ = run_cli("-Q", "cool-tool")
|
||||
self.assertEqual(code, 0)
|
||||
self.assertIn("Name: cool-tool", out)
|
||||
self.assertIn("Version: 1.0.0", out)
|
||||
self.assertIn("Repository:", out)
|
||||
|
||||
code, out, _ = run_cli("-R", "cool-tool")
|
||||
self.assertEqual(code, 0)
|
||||
self.assertIn("Removed cool-tool", out)
|
||||
self.assertFalse((self.home / "pkg" / "cool-tool").exists())
|
||||
|
||||
def test_remove_unknown_package(self) -> None:
|
||||
code, _, err = run_cli("-R", "missing")
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn("package 'missing' is not installed.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from sjnetpkg import config
|
||||
|
||||
|
||||
class DefaultHomeTests(unittest.TestCase):
|
||||
def test_env_override_is_used(self) -> None:
|
||||
with mock.patch.dict(os.environ, {"SJNETPKG_HOME": "/tmp/custom-home"}):
|
||||
self.assertEqual(config.default_home(), Path("/tmp/custom-home"))
|
||||
|
||||
def test_falls_back_to_home_directory(self) -> None:
|
||||
env = {k: v for k, v in os.environ.items() if k != "SJNETPKG_HOME"}
|
||||
with mock.patch.dict(os.environ, env, clear=True):
|
||||
self.assertEqual(config.default_home(), Path.home() / ".sjnetpkg")
|
||||
|
||||
|
||||
class PathsTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.home = Path(self._tmp.name) / ".sjnetpkg"
|
||||
self.paths = config.get_paths(self.home)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_layout(self) -> None:
|
||||
self.assertEqual(self.paths.config, self.home / "config.toml")
|
||||
self.assertEqual(self.paths.registry, self.home / "registry.toml")
|
||||
self.assertEqual(self.paths.pkg, self.home / "pkg")
|
||||
self.assertEqual(self.paths.package("cool-tool"), self.home / "pkg" / "cool-tool")
|
||||
|
||||
def test_ensure_home_creates_directories_and_config(self) -> None:
|
||||
config.ensure_home(self.paths)
|
||||
self.assertTrue(self.paths.pkg.is_dir())
|
||||
self.assertTrue(self.paths.config.is_file())
|
||||
self.assertEqual(config.registry_url(self.paths), config.DEFAULT_REGISTRY_URL)
|
||||
|
||||
def test_ensure_home_keeps_existing_config(self) -> None:
|
||||
self.home.mkdir(parents=True)
|
||||
self.paths.config.write_text('registry = "https://example.org/r.toml"\n')
|
||||
config.ensure_home(self.paths)
|
||||
self.assertEqual(config.registry_url(self.paths), "https://example.org/r.toml")
|
||||
|
||||
def test_missing_config_uses_default_registry(self) -> None:
|
||||
self.assertEqual(config.registry_url(self.paths), config.DEFAULT_REGISTRY_URL)
|
||||
|
||||
def test_invalid_config_raises(self) -> None:
|
||||
self.home.mkdir(parents=True)
|
||||
self.paths.config.write_text("this is = not toml\n")
|
||||
with self.assertRaises(config.ConfigError):
|
||||
config.registry_url(self.paths)
|
||||
|
||||
def test_empty_registry_value_raises(self) -> None:
|
||||
self.home.mkdir(parents=True)
|
||||
self.paths.config.write_text('registry = ""\n')
|
||||
with self.assertRaises(config.ConfigError):
|
||||
config.registry_url(self.paths)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from sjnetpkg import config, package
|
||||
|
||||
|
||||
def write_package(root: Path, directory: str, manifest: str) -> Path:
|
||||
target = root / directory
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
(target / "sjnetpkg.toml").write_text(manifest, encoding="utf-8")
|
||||
return target
|
||||
|
||||
|
||||
class ValidNameTests(unittest.TestCase):
|
||||
def test_valid_names(self) -> None:
|
||||
for name in ("cool-tool", "a", "tool_2", "tool.py", "A1"):
|
||||
self.assertTrue(package.is_valid_name(name), name)
|
||||
|
||||
def test_invalid_names(self) -> None:
|
||||
for name in ("", ".", "..", "-tool", "tool-", "bad name", "../escape", "a/b"):
|
||||
self.assertFalse(package.is_valid_name(name), name)
|
||||
|
||||
|
||||
class ManifestTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self._tmp.name)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_reads_name_version_and_run_command(self) -> None:
|
||||
directory = write_package(
|
||||
self.root,
|
||||
"cool-tool",
|
||||
'name = "cool-tool"\nversion = "1.0.0"\n\n[run]\ncommand = "cargo run"\n',
|
||||
)
|
||||
manifest = package.read_manifest(directory)
|
||||
self.assertEqual(manifest.name, "cool-tool")
|
||||
self.assertEqual(manifest.version, "1.0.0")
|
||||
self.assertEqual(manifest.run_command, "cargo run")
|
||||
self.assertEqual(manifest.path, directory)
|
||||
|
||||
def test_run_section_is_optional(self) -> None:
|
||||
directory = write_package(
|
||||
self.root, "lib", 'name = "lib"\nversion = "0.1.0"\n'
|
||||
)
|
||||
self.assertIsNone(package.read_manifest(directory).run_command)
|
||||
|
||||
def test_missing_manifest_raises(self) -> None:
|
||||
directory = self.root / "empty"
|
||||
directory.mkdir()
|
||||
with self.assertRaises(package.PackageError):
|
||||
package.read_manifest(directory)
|
||||
|
||||
def test_invalid_toml_raises(self) -> None:
|
||||
directory = write_package(self.root, "broken", "name = = \n")
|
||||
with self.assertRaises(package.PackageError):
|
||||
package.read_manifest(directory)
|
||||
|
||||
def test_missing_name_raises(self) -> None:
|
||||
directory = write_package(self.root, "noname", 'version = "1.0.0"\n')
|
||||
with self.assertRaises(package.PackageError):
|
||||
package.read_manifest(directory)
|
||||
|
||||
def test_missing_version_raises(self) -> None:
|
||||
directory = write_package(self.root, "noversion", 'name = "noversion"\n')
|
||||
with self.assertRaises(package.PackageError):
|
||||
package.read_manifest(directory)
|
||||
|
||||
def test_invalid_name_raises(self) -> None:
|
||||
directory = write_package(
|
||||
self.root, "bad", 'name = "../escape"\nversion = "1.0.0"\n'
|
||||
)
|
||||
with self.assertRaises(package.PackageError):
|
||||
package.read_manifest(directory)
|
||||
|
||||
|
||||
class InstalledPackagesTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.paths = config.get_paths(Path(self._tmp.name) / ".sjnetpkg")
|
||||
self.paths.pkg.mkdir(parents=True)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_list_is_empty_without_packages_directory(self) -> None:
|
||||
self.assertEqual(package.list_installed(config.get_paths(self.paths.home / "x")), [])
|
||||
|
||||
def test_list_ignores_invalid_and_non_directory_entries(self) -> None:
|
||||
write_package(self.paths.pkg, "cool-tool", 'name = "cool-tool"\nversion = "1.0.0"\n')
|
||||
write_package(self.paths.pkg, "broken", "nonsense")
|
||||
(self.paths.pkg / "loose-file").write_text("hello")
|
||||
names = [m.name for m in package.list_installed(self.paths)]
|
||||
self.assertEqual(names, ["cool-tool"])
|
||||
|
||||
def test_find_installed_by_directory_name(self) -> None:
|
||||
directory = write_package(
|
||||
self.paths.pkg, "repo-name", 'name = "manifest-name"\nversion = "1.0.0"\n'
|
||||
)
|
||||
self.assertEqual(package.find_installed(self.paths, "repo-name"), directory)
|
||||
|
||||
def test_find_installed_by_manifest_name(self) -> None:
|
||||
directory = write_package(
|
||||
self.paths.pkg, "repo-name", 'name = "manifest-name"\nversion = "1.0.0"\n'
|
||||
)
|
||||
self.assertEqual(package.find_installed(self.paths, "manifest-name"), directory)
|
||||
|
||||
def test_find_installed_unknown_raises(self) -> None:
|
||||
with self.assertRaises(package.NotInstalledError):
|
||||
package.find_installed(self.paths, "missing")
|
||||
|
||||
def test_remove_package(self) -> None:
|
||||
directory = write_package(self.paths.pkg, "cool-tool", 'name = "cool-tool"\nversion = "1.0.0"\n')
|
||||
self.assertEqual(package.remove_package(self.paths, "cool-tool"), directory)
|
||||
self.assertFalse(directory.exists())
|
||||
|
||||
def test_remove_missing_package_raises(self) -> None:
|
||||
with self.assertRaises(package.NotInstalledError):
|
||||
package.remove_package(self.paths, "missing")
|
||||
|
||||
def test_remove_refuses_to_leave_package_directory(self) -> None:
|
||||
outside = Path(self._tmp.name) / "outside"
|
||||
outside.mkdir()
|
||||
link = self.paths.pkg / "link"
|
||||
try:
|
||||
link.symlink_to(outside, target_is_directory=True)
|
||||
except (OSError, NotImplementedError): # pragma: no cover - platform dependent
|
||||
self.skipTest("symlinks are not supported on this platform")
|
||||
with self.assertRaises(package.PackageError):
|
||||
package.remove_package(self.paths, "link")
|
||||
self.assertTrue(outside.exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,123 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from sjnetpkg import config, registry
|
||||
|
||||
|
||||
class LoadRegistryTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.paths = config.get_paths(Path(self._tmp.name) / ".sjnetpkg")
|
||||
self.paths.home.mkdir(parents=True)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def test_loads_packages(self) -> None:
|
||||
self.paths.registry.write_text(
|
||||
'[packages.cool-tool]\nrepo = "https://example.com/cool-tool.git"\n'
|
||||
'[packages.other]\nrepo = "https://example.com/other.git"\n'
|
||||
)
|
||||
self.assertEqual(
|
||||
registry.load_registry(self.paths),
|
||||
{
|
||||
"cool-tool": "https://example.com/cool-tool.git",
|
||||
"other": "https://example.com/other.git",
|
||||
},
|
||||
)
|
||||
|
||||
def test_missing_registry_raises(self) -> None:
|
||||
with self.assertRaises(registry.RegistryError):
|
||||
registry.load_registry(self.paths)
|
||||
|
||||
def test_invalid_toml_raises(self) -> None:
|
||||
self.paths.registry.write_text("not = = toml\n")
|
||||
with self.assertRaises(registry.RegistryError):
|
||||
registry.load_registry(self.paths)
|
||||
|
||||
def test_missing_packages_table_raises(self) -> None:
|
||||
self.paths.registry.write_text('something = "else"\n')
|
||||
with self.assertRaises(registry.RegistryError):
|
||||
registry.load_registry(self.paths)
|
||||
|
||||
def test_entry_without_repo_raises(self) -> None:
|
||||
self.paths.registry.write_text('[packages.cool-tool]\nname = "cool-tool"\n')
|
||||
with self.assertRaises(registry.RegistryError):
|
||||
registry.load_registry(self.paths)
|
||||
|
||||
def test_resolve_returns_repo(self) -> None:
|
||||
self.assertEqual(
|
||||
registry.resolve({"cool-tool": "https://example.com/cool-tool.git"}, "cool-tool"),
|
||||
"https://example.com/cool-tool.git",
|
||||
)
|
||||
|
||||
def test_resolve_unknown_raises(self) -> None:
|
||||
with self.assertRaises(registry.PackageNotFoundError):
|
||||
registry.resolve({}, "missing")
|
||||
|
||||
|
||||
class DownloadRegistryTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._tmp = tempfile.TemporaryDirectory()
|
||||
self.root = Path(self._tmp.name)
|
||||
self.paths = config.get_paths(self.root / ".sjnetpkg")
|
||||
self.paths.home.mkdir(parents=True)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self._tmp.cleanup()
|
||||
|
||||
def _source(self, name: str, content: str) -> str:
|
||||
path = self.root / name
|
||||
path.write_text(content, encoding="utf-8")
|
||||
return path.as_uri()
|
||||
|
||||
def test_download_replaces_local_registry(self) -> None:
|
||||
url = self._source(
|
||||
"remote.toml",
|
||||
'[packages.cool-tool]\nrepo = "https://example.com/cool-tool.git"\n',
|
||||
)
|
||||
registry.download_registry(url, self.paths.registry)
|
||||
self.assertEqual(
|
||||
registry.load_registry(self.paths),
|
||||
{"cool-tool": "https://example.com/cool-tool.git"},
|
||||
)
|
||||
|
||||
def test_failed_download_keeps_existing_registry(self) -> None:
|
||||
self.paths.registry.write_text('[packages.old]\nrepo = "https://example.com/old.git"\n')
|
||||
missing = (self.root / "missing.toml").as_uri()
|
||||
with self.assertRaises(registry.RegistryError):
|
||||
registry.download_registry(missing, self.paths.registry)
|
||||
self.assertEqual(
|
||||
registry.load_registry(self.paths),
|
||||
{"old": "https://example.com/old.git"},
|
||||
)
|
||||
|
||||
def test_invalid_download_keeps_existing_registry(self) -> None:
|
||||
self.paths.registry.write_text('[packages.old]\nrepo = "https://example.com/old.git"\n')
|
||||
url = self._source("broken.toml", "this = = invalid\n")
|
||||
with self.assertRaises(registry.RegistryError):
|
||||
registry.download_registry(url, self.paths.registry)
|
||||
self.assertEqual(
|
||||
registry.load_registry(self.paths),
|
||||
{"old": "https://example.com/old.git"},
|
||||
)
|
||||
|
||||
def test_update_uses_configured_url(self) -> None:
|
||||
url = self._source(
|
||||
"remote.toml",
|
||||
'[packages.cool-tool]\nrepo = "https://example.com/cool-tool.git"\n',
|
||||
)
|
||||
config.ensure_home(self.paths)
|
||||
self.paths.config.write_text(f'registry = "{url}"\n', encoding="utf-8")
|
||||
registry.update_registry(self.paths)
|
||||
self.assertEqual(
|
||||
registry.load_registry(self.paths),
|
||||
{"cool-tool": "https://example.com/cool-tool.git"},
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user