From 866bd9adabe35d3c843dba37ec7559348f1bf137 Mon Sep 17 00:00:00 2001 From: Arthur Deierlein Date: Tue, 31 Oct 2023 18:15:16 +0100 Subject: [PATCH] chore(api/tests): added test for repo cloning and parsing deps --- api/outdated/conftest.py | 8 +++ api/outdated/outdated/parser.py | 2 +- api/outdated/outdated/tests/test_tracking.py | 66 ++++++++++++++++++++ api/outdated/outdated/tracking.py | 14 +++-- api/outdated/settings.py | 2 + 5 files changed, 86 insertions(+), 6 deletions(-) diff --git a/api/outdated/conftest.py b/api/outdated/conftest.py index 8b6b1412..79850bcc 100644 --- a/api/outdated/conftest.py +++ b/api/outdated/conftest.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from collections.abc import Callable + from pathlib import Path from unittest.mock import MagicMock from pytest_mock import MockerFixture @@ -94,3 +95,10 @@ def _tracker_mock(target: str) -> MagicMock: def tracker_init_mock(mocker: MockerFixture) -> MagicMock: """Mock for the Trackers __init__ method.""" return mocker.patch.object(Tracker, "__init__", return_value=None) + + +@pytest.fixture +def tmp_repo_root(settings, tmp_path) -> Path: # noqa: ANN001 + """Change settings.REPOSITORY_ROOT to a temporary directory.""" + settings.REPOSITORY_ROOT = str(tmp_path.absolute()) + return tmp_path diff --git a/api/outdated/outdated/parser.py b/api/outdated/outdated/parser.py index 026eae77..2c28a8e0 100644 --- a/api/outdated/outdated/parser.py +++ b/api/outdated/outdated/parser.py @@ -128,7 +128,7 @@ def parse(self) -> list[models.Version]: elif name == "pnpm-lock.yaml": lockfile_data = safe_load(data) regex = r"\/(@?[^\s@]+)@([^()]+).*" - if float(lockfile_data["lockfileVersion"]) < 6.0: + if float(lockfile_data["lockfileVersion"]) < 6.0: # pragma: no cover raise NotImplementedError( "Outdated does not support pnpm-lock.yaml lockfiles with a version lower than 6", ) diff --git a/api/outdated/outdated/tests/test_tracking.py b/api/outdated/outdated/tests/test_tracking.py index f6a7bd0d..1a4ff2a9 100644 --- a/api/outdated/outdated/tests/test_tracking.py +++ b/api/outdated/outdated/tests/test_tracking.py @@ -1,7 +1,11 @@ +from unittest.mock import ANY, MagicMock + from django.urls import reverse from rest_framework import status from outdated.outdated.models import Project +from outdated.outdated.parser import LockfileParser +from outdated.outdated.tracking import Tracker def test_serializer(client, project_factory, tracker_init_mock, tracker_mock): @@ -62,3 +66,65 @@ def test_serializer(client, project_factory, tracker_init_mock, tracker_mock): assert resp.status_code == status.HTTP_204_NO_CONTENT assert delete_mock.call_count == 2 + + +def test_clone_and_parse(db, project_factory, settings, mocker, tmp_repo_root): + data = { + "poetry": {"lockfile": "poetry.lock", "version": "1.6.1"}, + "yarn": {"lockfile": "yarn.lock", "version": "1.22.19"}, + "pnpm": {"lockfile": "pnpm-lock.yaml", "version": "8.10.0"}, + } + settings.TRACKED_DEPENDENCIES = list(data.keys()) + assert list(tmp_repo_root.iterdir()) == [] + + project: Project = project_factory(repo="github.com/c0rydoras/lockfile-test") + + tracker = Tracker(project) + tracker.clone() + path = tracker.repository_path + assert path != tmp_repo_root.absolute() + assert list(path.iterdir()) == [path / ".git"] + tracker.checkout() + + directories = [] + lockfiles = [] + + for k, v in data.items(): + directory = path / k + assert directory.exists() + assert directory.is_dir() + lockfile = directory / v["lockfile"] + assert lockfile.exists() + assert lockfile.is_file() + directories.append(directory) + lockfiles.append(lockfile) + + lockfiles.sort() + + dirs = list(path.iterdir()) + expected_dirs = [path / ".git", *directories] + dirs.sort() + expected_dirs.sort() + + assert expected_dirs == dirs + + tracker_lockfiles = tracker.lockfiles + + assert len(tracker_lockfiles) == len(data) + + _lockfiles = tracker_lockfiles.copy() + _lockfiles.sort() + + assert _lockfiles == lockfiles + + lockfile_parser_mock: MagicMock = mocker.spy(LockfileParser, "__init__") + + tracker.sync() + + lockfile_parser_mock.assert_called_once_with(ANY, tracker_lockfiles) + + versions = project.versioned_dependencies.all() + + for k, v in data.items(): + version = versions.get(release_version__dependency__name=k) + assert version.version == v["version"] diff --git a/api/outdated/outdated/tracking.py b/api/outdated/outdated/tracking.py index de85e228..6c99c012 100644 --- a/api/outdated/outdated/tracking.py +++ b/api/outdated/outdated/tracking.py @@ -22,14 +22,14 @@ class RepoError(ValueError): class Tracker: def __init__(self, project: Project) -> None: self.project = project - self.local_path = Path(f"/projects/{self.project.clone_path}") + self.local_path = Path(f"{settings.REPOSITORY_ROOT}/{self.project.clone_path}") def _run( self, command: list[str], requires_local_copy: bool = False, ) -> CompletedProcess[str]: - if not self.local_path.exists() and requires_local_copy: + if not self.local_path.exists() and requires_local_copy: # pragma: no cover msg = f"Can't run {command} without local copy of {self.project.repo}" raise RepoError(msg) return run( @@ -68,7 +68,7 @@ def checkout(self): # pragma: no cover @property def lockfiles(self): - if not self.local_path.exists(): + if not self.local_path.exists(): # pragma: no cover msg = f"Unable to retrieve lockfiles for {self.project.repo} because it is not saved locally." raise RepoError(msg) @@ -83,10 +83,14 @@ def lockfiles(self): @property def repository_path(self): - return self.local_path.absolute() if self.local_path.exists() else "/projects/" + return ( + self.local_path.absolute() + if self.local_path.exists() + else Path(settings.REPOSITORY_ROOT) + ) def sync(self): - if not self.local_path.exists(): + if not self.local_path.exists(): # pragma: no cover self.clone() self.checkout() dependencies = LockfileParser(self.lockfiles).parse() diff --git a/api/outdated/settings.py b/api/outdated/settings.py index 69a28b22..d5b2e558 100644 --- a/api/outdated/settings.py +++ b/api/outdated/settings.py @@ -186,6 +186,8 @@ def default(default_dev=env.NOTSET, default_prod=env.NOTSET): ], ) +# Where to put the cloned projects +REPOSITORY_ROOT = "/home/outdated/projects" NPM_FILES = ["yarn.lock", "pnpm-lock.yaml"] PYPI_FILES = ["poetry.lock"]