diff --git a/repo_management/models.py b/repo_management/models.py index f72d923ccf415df0c5cfd4c33aafc758865c9549..2add599f2da232c29586ae4f5c222a5a24f1ed42 100644 --- a/repo_management/models.py +++ b/repo_management/models.py @@ -1,199 +1,458 @@ import io from typing import List, Optional, Tuple -from pydantic import BaseModel +from pyalpm import vercmp +from pydantic import BaseModel, validator from repo_management import defaults +class Arch(BaseModel): + """A model describing a single 'arch' attribute + + Attributes + ---------- + arch: str + The attribute can be used to describe the (required) data below an %ARCH% identifier in a 'desc' file, which + identifies a package's architecture + """ + + arch: str + + +class Backup(BaseModel): + """A model describing a single 'backup' attribute + + Attributes + ---------- + backup: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %BACKUP% identifier in a 'desc' file, which + identifies which file(s) of a package pacman will create backups for + """ + + backup: Optional[List[str]] + + class Base(BaseModel): - """A model describing the %BASE% header in a 'desc' file, which type it represents and whether it is required or - not""" + """A model describing a single 'base' attribute + Attributes + ---------- base: str + The attribute can be used to describe the (required) data below a %BASE% identifier in a 'desc' file, which + identifies a package's pkgbase + """ + base: str -class Version(BaseModel): - """A model describing the %VERSION% header in a 'desc' file, which type it represents and whether it is required or - not""" - version: str +class BuildDate(BaseModel): + """A model describing a single 'builddate' attribute + Attributes + ---------- + builddate: int + The attribute can be used to describe the (required) data below a %BUILDDATE% identifier in a 'desc' file, + which identifies a package's build date (represented in seconds since the epoch) + """ -class MakeDepends(BaseModel): - """A model describing the %MAKEDEPENDS% header in a 'desc' file, which type it represents and whether it is required - or not""" + builddate: int - makedepends: Optional[List[str]] + @validator("builddate") + def builddate_greater_zero(cls, builddate: int) -> int: + if builddate < 0: + raise ValueError("The build date must be greater than zero.") + + return builddate class CheckDepends(BaseModel): - """A model describing the %CHECKDEPENDS% header in a 'desc' file, which type it represents and whether it is - required or not""" + """A model describing a single 'checkdepends' attribute + Attributes + ---------- checkdepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %CHECKDEPENDS% identifier in a 'desc' file, + which identifies a package's checkdepends + """ + checkdepends: Optional[List[str]] -class FileName(BaseModel): - """A model describing the %FILENAME% header in a 'desc' file, which type it represents and whether it is required or - not""" - filename: str +class Conflicts(BaseModel): + """A model describing a single 'conflicts' attribute + Attributes + ---------- + conflicts: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %CONFLICTS% identifier in a 'desc' file, which + identifies what other package(s) a package conflicts with + """ -class Name(BaseModel): - """A model describing the %NAME% header in a 'desc' file, which type it represents and whether it is required or - not""" + conflicts: Optional[List[str]] - name: str + +class CSize(BaseModel): + """A model describing a single 'csize' attribute + + Attributes + ---------- + csize: int + The attribute can be used to describe the (required) data below a %CSIZE% identifier in a 'desc' file, which + identifies a package's size + """ + + csize: int + + @validator("csize") + def csize_greater_equal_zero(cls, csize: int) -> int: + if csize < 0: + raise ValueError("The csize must be greater than or equal zero.") + + return csize + + +class Depends(BaseModel): + """A model describing a single 'depends' attribute + + Attributes + ---------- + depends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %DEPENDS% identifier in a 'desc' file, which + identifies what other package(s) a package depends on + """ + + depends: Optional[List[str]] class Desc(BaseModel): - """A model describing the %DESC% header in a 'desc' file, which type it represents and whether it is required or - not""" + """A model describing a single 'desc' attribute + Attributes + ---------- desc: str + The attribute can be used to describe the (required) data below a %DESC% identifier in a 'desc' file, which + identifies a package's description + """ + desc: str -class Groups(BaseModel): - """A model describing the %GROUPS% header in a 'desc' file, which type it represents and whether it is required or - not""" - groups: Optional[List[str]] +class FileName(BaseModel): + """A model describing a single 'filename' attribute + Attributes + ---------- + filename: str + The attribute can be used to describe the (required) data below a %FILENAME% identifier in a 'desc' file, which + identifies a package's file name + """ -class CSize(BaseModel): - """A model describing the %CSIZE% header in a 'desc' file, which type it represents and whether it is required or - not""" + filename: str - csize: int + +class Files(BaseModel): + """A model describing a single 'files' attribute + + Attributes + ---------- + files: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %FILES% identifier in a 'files' file, which + identifies which file(s) belong to a package + """ + + files: Optional[List[str]] + + +class Groups(BaseModel): + """A model describing a single 'groups' attribute + + Attributes + ---------- + groups: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %GROUPS% identifier in a 'desc' file, which + identifies a package's groups + """ + + groups: Optional[List[str]] class ISize(BaseModel): - """A model describing the %ISIZE% header in a 'desc' file, which type it represents and whether it is required or - not""" + """A model describing a single 'isize' attribute + Attributes + ---------- isize: int + The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which + identifies a package's installed size + """ + isize: int -class Md5Sum(BaseModel): - """A model describing the %MD5SUM% header in a 'desc' file, which type it represents and whether it is required or - not""" + @validator("isize") + def isize_greater_equal_zero(cls, isize: int) -> int: + if isize < 0: + raise ValueError("The isize must be greater than or equal zero.") - md5sum: str + return isize -class Sha256Sum(BaseModel): - """A model describing the %SHA256SUM% header in a 'desc' file, which type it represents and whether it is required - or not""" +class License(BaseModel): + """A model describing a single 'license' attribute - sha256sum: str + Attributes + ---------- + license: List[str] + The attribute can be used to describe the (required) data below a %LICENSE% identifier in a 'desc' file, which + identifies a package's license(s) + """ + license: List[str] -class PgpSig(BaseModel): - """A model describing the %PGPSIG% header in a 'desc' file, which type it represents and whether it is required or - not""" - pgpsig: str +class MakeDepends(BaseModel): + """A model describing a single 'makedepends' attribute + Attributes + ---------- + makedepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %MAKEDEPENDS% identifier in a 'desc' file, + which identifies a package's makedepends + """ -class Url(BaseModel): - """A model describing the %URL% header in a 'desc' file, which type it represents and whether it is required or - not""" + makedepends: Optional[List[str]] - url: str +class Md5Sum(BaseModel): + """A model describing a single 'md5sum' attribute -class License(BaseModel): - """A model describing the %LICENSE% header in a 'desc' file, which type it represents and whether it is required or - not""" + Attributes + ---------- + md5sum: str + The attribute can be used to describe the (required) data below an %MD5SUM% identifier in a 'desc' file, which + identifies a package's md5 checksum + """ - license: Optional[List[str]] + md5sum: str -class Arch(BaseModel): - """A model describing the %ARCH% header in a 'desc' file, which type it represents and whether it is required or - not""" +class Name(BaseModel): + """A model describing a single 'name' attribute - arch: str + Attributes + ---------- + name: str + The attribute can be used to describe the (required) data below a %NAME% identifier in a 'desc' file, which + identifies a package's name + """ + name: str -class BuildDate(BaseModel): - """A model describing the %BUILDDATE% header in a 'desc' file, which type it represents and whether it is required - or not""" + @validator("name") + def name_contains_only_allowed_chars(cls, name: str) -> str: + disallowed_start_chars = [".", "-"] + for char in disallowed_start_chars: + if name.startswith(char): + raise ValueError(f"The package name '{name}' can not start with any of '{disallowed_start_chars}'.") + + allowed_chars = ["@", ".", "_", "+", "-"] + remaining_chars: List[str] = [] + for char in name: + if (not char.isalnum() or (not char.isdigit() and not char.islower())) and char not in allowed_chars: + remaining_chars += [char] + if remaining_chars: + raise ValueError( + f"The package name '{name}' can not contain '{remaining_chars}' but must consist only of alphanumeric " + f"chars and any of '{allowed_chars}'." + ) - builddate: int + return name class Packager(BaseModel): - """A model describing the %PACKAGER% header in a 'desc' file, which type it represents and whether it is required - or not""" + """A model describing a single 'packager' attribute + Attributes + ---------- packager: str + The attribute can be used to describe the (required) data below a %PACKAGER% identifier in a 'desc' file, which + identifies a package's packager + """ + + packager: str + + +class PgpSig(BaseModel): + """A model describing a single 'pgpsig' attribute + + Attributes + ---------- + pgpsig: str + The attribute can be used to describe the (required) data below a %PGPSIG% identifier in a 'desc' file, which + identifies a package's PGP signature + """ + + pgpsig: str + + +class Provides(BaseModel): + """A model describing a single 'provides' attribute + + Attributes + ---------- + provides: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %PROVIDES% identifier in a 'desc' file, which + identifies what other package(s) a package provides + """ + + provides: Optional[List[str]] class Replaces(BaseModel): - """A model describing the %REPLACES% header in a 'desc' file, which type it represents and whether it is required or - not""" + """A model describing a single 'replaces' attribute + Attributes + ---------- replaces: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %REPLACES% identifier in a 'desc' file, which + identifies what other package(s) a package replaces + """ + replaces: Optional[List[str]] -class Conflicts(BaseModel): - """A model describing the %CONFLICTS% header in a 'desc' file, which type it represents and whether it is required or - not""" - conflicts: Optional[List[str]] +class RepoDbMemberType(BaseModel): + """A model describing a single 'member_type' attribute, which is used to identify/ distinguish different types of + repository database file types (e.g. 'desc' and 'files' files, which are contained in a repository database file). + Attributes + ---------- + member_type: defaults.RepoDbMemberType + A member of the IntEnum defaults.RepoDbMemberType + """ -class Provides(BaseModel): - """A model describing the %PROVIDES% header in a 'desc' file, which type it represents and whether it is required or - not""" + member_type: defaults.RepoDbMemberType - provides: Optional[List[str]] +class Sha256Sum(BaseModel): + """A model describing a single 'sha256sum' attribute -class Depends(BaseModel): - """A model describing the %DEPENDS% header in a 'desc' file, which type it represents and whether it is required or - not""" + Attributes + ---------- + sha256sum: str + The attribute can be used to describe the (required) data below an %SHA256SUM% identifier in a 'desc' file, + which identifies a package's sha256 checksum + """ - depends: Optional[List[str]] + sha256sum: str class OptDepends(BaseModel): - """A model describing the %OPTDEPENDS% header in a 'desc' file, which type it represents and whether it is required - or not""" + """A model describing a single 'optdepends' attribute + Attributes + ---------- optdepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %OPTDEPENDS% identifier in a 'desc' file, + which identifies what other package(s) a package optionally depends on + """ + optdepends: Optional[List[str]] -class Backup(BaseModel): - """A model describing the %BACKUP% header in a 'desc' file, which type it represents and whether it is required - or not""" - backup: Optional[List[str]] +class Url(BaseModel): + """A model describing a single 'url' attribute + Attributes + ---------- + url: str + The attribute can be used to describe the (required) data below a %URL% identifier in a 'desc' file, which + identifies a package's URL + """ -class Files(BaseModel): - """A model describing the %FILES% header in a 'files' file, which type it represents and whether it is required or - not""" + url: str - files: Optional[List[str]] +class Version(BaseModel): + """A model describing a single 'version' attribute + + Attributes + ---------- + version: str + The attribute can be used to describe the (required) data below a %VERSION% identifier in a 'desc' file, which + identifies a package's version (this is the accumulation of epoch, pkgver and pkgrel) + """ -class PackageFiles(Name, Files): - pass + version: str + + @validator("version") + def version_is_valid(cls, version: str) -> str: + allowed_chars = [":", ".", "_", "+", "-"] + + if version.endswith("-0"): + raise ValueError("The first pkgrel of a package release always needs to start at 1.") + for char in allowed_chars: + if version.startswith(char): + raise ValueError("The first character of a package version must not be '{char}'.") + + remaining_chars: List[str] = [] + for char in version: + if not char.isalnum() and char not in allowed_chars: + remaining_chars += [char] + if remaining_chars: + raise ValueError( + f"Package versions can not contain '{remaining_chars}' but must consist of alphanumeric chars and any " + f"of '{allowed_chars}'." + ) + + return version + + def is_older_than(self, version: str) -> bool: + """Check whether the version is older than a provided version + + Parameters + ---------- + version: str + Another version string to compare that of self to + + Returns + ------- + True if self.version is older than the provided version, False otherwise. + """ + + if vercmp(self.version, version) < 0: + return True + else: + return False + + def is_newer_than(self, version: str) -> bool: + """Check whether the version is newer than a provided version + + Parameters + ---------- + version: str + Another version string to compare that of self to + + Returns + ------- + True if self.version is newer than the provided version, False otherwise. + """ + + if vercmp(self.version, version) > 0: + return True + else: + return False class OutputPackage( Arch, Backup, BuildDate, + CheckDepends, Conflicts, CSize, Depends, Desc, - CheckDepends, FileName, Files, Groups, @@ -208,8 +467,73 @@ class OutputPackage( Sha256Sum, Url, ): - """A model describing all required attributes for a package in the context of an output file, that describes a - (potential) list of packages based upon its pkgbase + """A model describing all required attributes that define a package in the context of an output file + + Attributes + ---------- + arch: str + The attribute can be used to describe the (required) data below an %ARCH% identifier in a 'desc' file, which + identifies a package's architecture + backup: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %BACKUP% identifier in a 'desc' file, which + identifies which file(s) of a package pacman will create backups for + builddate: int + The attribute can be used to describe the (required) data below a %BUILDDATE% identifier in a 'desc' file, + which identifies a package's build date (represented in seconds since the epoch) + checkdepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %CHECKDEPENDS% identifier in a 'desc' file, + which identifies a package's checkdepends + conflicts: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %CONFLICTS% identifier in a 'desc' file, which + identifies what other package(s) a package conflicts with + csize: int + The attribute can be used to describe the (required) data below a %CSIZE% identifier in a 'desc' file, which + identifies a package's size + depends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %DEPENDS% identifier in a 'desc' file, which + identifies what other package(s) a package depends on + desc: str + The attribute can be used to describe the (required) data below a %DESC% identifier in a 'desc' file, which + identifies a package's description + filename: str + The attribute can be used to describe the (required) data below a %FILENAME% identifier in a 'desc' file, which + identifies a package's file name + files: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %FILES% identifier in a 'files' file, which + identifies which file(s) belong to a package + groups: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %GROUPS% identifier in a 'desc' file, which + identifies a package's groups + isize: int + The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which + identifies a package's installed size + license: List[str] + The attribute can be used to describe the (required) data below a %LICENSE% identifier in a 'desc' file, which + identifies a package's license(s) + md5sum: str + The attribute can be used to describe the (required) data below an %MD5SUM% identifier in a 'desc' file, which + identifies a package's md5 checksum + name: str + The attribute can be used to describe the (required) data below a %NAME% identifier in a 'desc' file, which + identifies a package's name + optdepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %OPTDEPENDS% identifier in a 'desc' file, + which identifies what other package(s) a package optionally depends on + pgpsig: str + The attribute can be used to describe the (required) data below a %PGPSIG% identifier in a 'desc' file, which + identifies a package's PGP signature + provides: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %PROVIDES% identifier in a 'desc' file, which + identifies what other package(s) a package provides + replaces: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %REPLACES% identifier in a 'desc' file, which + identifies what other package(s) a package replaces + sha256sum: str + The attribute can be used to describe the (required) data below an %SHA256SUM% identifier in a 'desc' file, + which identifies a package's sha256 checksum + url: str + The attribute can be used to describe the (required) data below a %URL% identifier in a 'desc' file, which + identifies a package's URL """ pass @@ -220,11 +544,11 @@ class PackageDesc( Backup, Base, BuildDate, + CheckDepends, Conflicts, CSize, Depends, Desc, - CheckDepends, FileName, Groups, ISize, @@ -241,8 +565,83 @@ class PackageDesc( Url, Version, ): - """A model describing all headers in a 'desc' file, which type they represent and whether they are required or - not""" + """A model describing all identifiers in a 'desc' file + + Attributes + ---------- + arch: str + The attribute can be used to describe the (required) data below an %ARCH% identifier in a 'desc' file, which + identifies a package's architecture + backup: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %BACKUP% identifier in a 'desc' file, which + identifies which file(s) of a package pacman will create backups for + base: str + The attribute can be used to describe the (required) data below a %BASE% identifier in a 'desc' file, which + identifies a package's pkgbase + builddate: int + The attribute can be used to describe the (required) data below a %BUILDDATE% identifier in a 'desc' file, + which identifies a package's build date (represented in seconds since the epoch) + checkdepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %CHECKDEPENDS% identifier in a 'desc' file, + which identifies a package's checkdepends + conflicts: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %CONFLICTS% identifier in a 'desc' file, which + identifies what other package(s) a package conflicts with + csize: int + The attribute can be used to describe the (required) data below a %CSIZE% identifier in a 'desc' file, which + identifies a package's size + depends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %DEPENDS% identifier in a 'desc' file, which + identifies what other package(s) a package depends on + desc: str + The attribute can be used to describe the (required) data below a %DESC% identifier in a 'desc' file, which + identifies a package's description + filename: str + The attribute can be used to describe the (required) data below a %FILENAME% identifier in a 'desc' file, which + identifies a package's file name + groups: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %GROUPS% identifier in a 'desc' file, which + identifies a package's groups + isize: int + The attribute can be used to describe the (required) data below an %ISIZE% identifier in a 'desc' file, which + identifies a package's installed size + license: List[str] + The attribute can be used to describe the (required) data below a %LICENSE% identifier in a 'desc' file, which + identifies a package's license(s) + makedepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %MAKEDEPENDS% identifier in a 'desc' file, + which identifies a package's makedepends + md5sum: str + The attribute can be used to describe the (required) data below an %MD5SUM% identifier in a 'desc' file, which + identifies a package's md5 checksum + name: str + The attribute can be used to describe the (required) data below a %NAME% identifier in a 'desc' file, which + identifies a package's name + optdepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %OPTDEPENDS% identifier in a 'desc' file, + which identifies what other package(s) a package optionally depends on + packager: str + The attribute can be used to describe the (required) data below a %PACKAGER% identifier in a 'desc' file, which + identifies a package's packager + pgpsig: str + The attribute can be used to describe the (required) data below a %PGPSIG% identifier in a 'desc' file, which + identifies a package's PGP signature + provides: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %PROVIDES% identifier in a 'desc' file, which + identifies what other package(s) a package provides + replaces: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %REPLACES% identifier in a 'desc' file, which + identifies what other package(s) a package replaces + sha256sum: str + The attribute can be used to describe the (required) data below an %SHA256SUM% identifier in a 'desc' file, + which identifies a package's sha256 checksum + url: str + The attribute can be used to describe the (required) data below a %URL% identifier in a 'desc' file, which + identifies a package's URL + version: str + The attribute can be used to describe the (required) data below a %VERSION% identifier in a 'desc' file, which + identifies a package's version (this is the accumulation of epoch, pkgver and pkgrel) + """ def get_output_package(self, files: Optional[Files]) -> OutputPackage: """Transform the PackageDesc model and an optional Files model into an OutputPackage model @@ -270,16 +669,19 @@ class PackageDesc( return OutputPackage(**desc_dict) -class RepoDbMemberType(BaseModel): - """A model describing an attribute used to identify/ distinguish different types of repo database file types (e.g. - 'desc' and 'files' files, which are contained in a repository database file). - The file types are distinguished with the help of the IntEnum defaults.REpoDbFileType - """ +class RepoDbMemberData(Name, RepoDbMemberType): + """A model describing a set of attributes to provide the data of a 'desc' or 'files' file + Attributes + ---------- + name: str + A package name member_type: defaults.RepoDbMemberType + A member of the IntEnum defaults.RepoDbMemberType + data: io.StringIO + The contents of a 'desc' or 'files' file provided as a StringIO instance + """ - -class RepoDbMemberData(Name, RepoDbMemberType): data: io.StringIO class Config: @@ -294,6 +696,23 @@ class OutputPackageBase( ): """A model describing all required attributes for an output file, that describes a list of packages based upon a pkgbase + + Attributes + ---------- + base: str + The attribute can be used to describe the (required) data below a %BASE% identifier in a 'desc' file, which + identifies a package's pkgbase + makedepends: Optional[List[str]] + The attribute can be used to describe the (optional) data below a %MAKEDEPENDS% identifier in a 'desc' file, + which identifies a package's makedepends + packager: str + The attribute can be used to describe the (required) data below a %PACKAGER% identifier in a 'desc' file, which + identifies a package's packager + packages: List[OutputPackage] + A list of OutputPackage instances that belong to the pkgbase identified by base + version: str + The attribute can be used to describe the (required) data below a %VERSION% identifier in a 'desc' file, which + identifies a package's version (this is the accumulation of epoch, pkgver and pkgrel) """ packages: List[OutputPackage] diff --git a/tests/test_convert.py b/tests/test_convert.py index d4467d0c3ae27deef40c921845774a79021149a7..6f5ed25f64932ddb8cef0feb6b5fd0a1d8a69e1d 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -119,7 +119,7 @@ def test__desc_data_to_dict( desc="foo", filename="foo", isize=1, - licenses=["foo"], + license=["foo"], md5sum="foo", name="foo", packager="foo", @@ -139,7 +139,7 @@ def test__desc_data_to_dict( desc="foo", filename="foo", isize=1, - licenses=["foo"], + license=["foo"], md5sum="foo", name="foo", packager="foo", @@ -182,7 +182,7 @@ def test_repodbfile_render_desc_template() -> None: desc="foo", filename="foo", isize=1, - licenses=["foo"], + license=["foo"], md5sum="foo", name="foo", packager="foo", diff --git a/tests/test_files.py b/tests/test_files.py index d4c404634f9de8912398eb04707b5f59b909736a..6a6c5ec0b8a6e5a845c35f7476629a0bbc6c40b2 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -148,7 +148,7 @@ def test__write_db_file(empty_dir: Path) -> None: filename="foo", files=["foo", "bar"], isize=1, - licenses=["foo"], + license=["foo"], md5sum="foo", name="foo", pgpsig="foo", @@ -173,7 +173,7 @@ def test__write_db_file(empty_dir: Path) -> None: filename="foo", files=["foo", "bar"], isize=1, - licenses=["foo"], + license=["foo"], md5sum="foo", name="foo", pgpsig="foo", diff --git a/tests/test_models.py b/tests/test_models.py index 6beb08fb15c3371a5a07bac2a5534f84f6f28893..9514a9c0d71324e69a6bb6176d74dfd3a65b4218 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,7 @@ -from typing import List, Optional, Tuple +from contextlib import nullcontext as does_not_raise +from typing import ContextManager, List, Optional, Tuple -from pytest import mark +from pytest import mark, raises from repo_management import models @@ -143,3 +144,97 @@ def test_output_package_base_get_packages_as_models( output_package_base: models.OutputPackageBase, ) -> None: assert models_list == output_package_base.get_packages_as_models() + + +@mark.parametrize( + "name, expectation", + [ + (".foo", raises(ValueError)), + ("-foo", raises(ValueError)), + ("foo'", raises(ValueError)), + ("foo", does_not_raise()), + ], +) +def test_name(name: str, expectation: ContextManager[str]) -> None: + with expectation: + assert name == models.Name(name=name).name + + +@mark.parametrize( + "builddate, expectation", + [ + (-1, raises(ValueError)), + (1, does_not_raise()), + ], +) +def test_builddate(builddate: int, expectation: ContextManager[str]) -> None: + with expectation: + assert builddate == models.BuildDate(builddate=builddate).builddate + + +@mark.parametrize( + "csize, expectation", + [ + (-1, raises(ValueError)), + (1, does_not_raise()), + ], +) +def test_csize(csize: int, expectation: ContextManager[str]) -> None: + with expectation: + assert csize == models.CSize(csize=csize).csize + + +@mark.parametrize( + "isize, expectation", + [ + (-1, raises(ValueError)), + (1, does_not_raise()), + ], +) +def test_isize(isize: int, expectation: ContextManager[str]) -> None: + with expectation: + assert isize == models.ISize(isize=isize).isize + + +@mark.parametrize( + "version, expectation", + [ + ("1.2.3-0", raises(ValueError)), + (".1.2.3-1", raises(ValueError)), + ("-1.2.3-1", raises(ValueError)), + (":1.2.3-1", raises(ValueError)), + ("_1.2.3-1", raises(ValueError)), + ("+1.2.3-1", raises(ValueError)), + ("1.2.'3-1", raises(ValueError)), + ("1.2.3-1", does_not_raise()), + ("1:1.2.3-1", does_not_raise()), + ("1:1.2.3r500.x.y.z.1-1", does_not_raise()), + ], +) +def test_version_version_is_valid(version: str, expectation: ContextManager[str]) -> None: + with expectation: + assert version == models.Version(version=version).version + + +@mark.parametrize( + "version, other_version, expectation", + [ + ("1.2.3-1", "1.2.3-2", True), + ("1.2.3-2", "1.2.3-1", False), + ], +) +def test_version_is_older_than(version: str, other_version: str, expectation: bool) -> None: + model = models.Version(version=version) + assert model.is_older_than(other_version) is expectation + + +@mark.parametrize( + "version, other_version, expectation", + [ + ("1.2.3-1", "1.2.3-2", False), + ("1.2.3-2", "1.2.3-1", True), + ], +) +def test_version_is_newer_than(version: str, other_version: str, expectation: bool) -> None: + model = models.Version(version=version) + assert model.is_newer_than(other_version) is expectation