add config validator subcommand (#80)

* add config validator subcommand

* add --exit-code flag

* docs & faq update
This commit is contained in:
2023-01-09 17:22:29 +02:00
committed by GitHub
parent 1f07a89316
commit d942a70272
36 changed files with 1393 additions and 47 deletions

View File

@ -4,15 +4,12 @@ from pytest_mock import MockerFixture
from ahriman.application.handlers import Dump
from ahriman.core.configuration import Configuration
from ahriman.core.repository import Repository
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump",
return_value=configuration.dump())

View File

@ -0,0 +1,104 @@
import argparse
import json
from pytest_mock import MockerFixture
from ahriman.application.handlers import Validate
from ahriman.core.configuration import Configuration
from ahriman.core.configuration.schema import CONFIGURATION_SCHEMA, GITREMOTE_REMOTE_PULL_SCHEMA
from ahriman.core.configuration.validator import Validator
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
"""
default arguments for these test cases
Args:
args(argparse.Namespace): command line arguments fixture
Returns:
argparse.Namespace: generated arguments for these test cases
"""
args.exit_code = False
return args
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
mocker.patch.object(Validator, "errors", {"node": ["error"]})
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
application_mock = mocker.patch("ahriman.core.configuration.validator.Validator.validate", return_value=False)
Validate.run(args, "x86_64", configuration, report=False, unsafe=False)
application_mock.assert_called_once_with(configuration.dump())
print_mock.assert_called_once_with(verbose=True)
def test_run_skip(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must skip print if no errors found
"""
args = _default_args(args)
mocker.patch("ahriman.core.configuration.validator.Validator.validate", return_value=True)
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
Validate.run(args, "x86_64", configuration, report=False, unsafe=False)
print_mock.assert_not_called()
def test_schema(configuration: Configuration) -> None:
"""
must generate full schema correctly
"""
schema = Validate.schema("x86_64", configuration)
# defaults
assert schema.pop("console")
assert schema.pop("email")
assert schema.pop("github")
assert schema.pop("gitremote")
assert schema.pop("html")
assert schema.pop("rsync")
assert schema.pop("s3")
assert schema.pop("telegram")
assert schema == CONFIGURATION_SCHEMA
def test_schema_erase_required() -> None:
"""
must remove required field from dictionaries recursively
"""
# the easiest way is to just dump to string and check
assert "required" not in json.dumps(Validate.schema_erase_required(CONFIGURATION_SCHEMA))
def test_schema_insert(configuration: Configuration) -> None:
"""
must insert child schema to root
"""
result = Validate.schema_insert("x86_64", configuration, CONFIGURATION_SCHEMA, "remote-pull",
lambda _: GITREMOTE_REMOTE_PULL_SCHEMA)
assert result["gitremote"] == GITREMOTE_REMOTE_PULL_SCHEMA
def test_schema_insert_skip(configuration: Configuration) -> None:
"""
must do nothing in case if there is no such section or option
"""
configuration.remove_section("remote-pull")
result = Validate.schema_insert("x86_64", configuration, CONFIGURATION_SCHEMA, "remote-pull",
lambda _: GITREMOTE_REMOTE_PULL_SCHEMA)
assert result == CONFIGURATION_SCHEMA
def test_disallow_auto_architecture_run() -> None:
"""
must not allow multi architecture run
"""
assert not Validate.ALLOW_AUTO_ARCHITECTURE_RUN

View File

@ -413,6 +413,18 @@ def test_subparsers_repo_config(parser: argparse.ArgumentParser) -> None:
assert args.unsafe
def test_subparsers_repo_config_validate(parser: argparse.ArgumentParser) -> None:
"""
repo-config-validate command must imply lock, report, quiet and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "repo-config-validate"])
assert args.architecture == ["x86_64"]
assert args.lock is None
assert not args.report
assert args.quiet
assert args.unsafe
def test_subparsers_repo_rebuild_architecture(parser: argparse.ArgumentParser) -> None:
"""
repo-rebuild command must correctly parse architecture list

View File

@ -0,0 +1,19 @@
import pytest
from ahriman.core.configuration import Configuration
from ahriman.core.configuration.schema import CONFIGURATION_SCHEMA
from ahriman.core.configuration.validator import Validator
@pytest.fixture
def validator(configuration: Configuration) -> Validator:
"""
fixture for validator
Args:
configuration(Configuration): configuration fixture
Returns:
Validator: validator test instance
"""
return Validator(instance=configuration, schema=CONFIGURATION_SCHEMA)

View File

@ -0,0 +1,70 @@
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock
from ahriman.core.configuration.validator import Validator
def test_types_mapping() -> None:
"""
must set custom types
"""
assert "path" in Validator.types_mapping
assert Path in Validator.types_mapping["path"].included_types
def test_normalize_coerce_absolute_path(validator: Validator) -> None:
"""
must convert string value to path by using configuration converters
"""
convert_mock = MagicMock()
validator.instance.converters["path"] = convert_mock
validator._normalize_coerce_absolute_path("value")
convert_mock.assert_called_once_with("value")
def test_normalize_coerce_boolean(validator: Validator, mocker: MockerFixture) -> None:
"""
must convert string value to boolean by using configuration converters
"""
convert_mock = mocker.patch("ahriman.core.configuration.Configuration._convert_to_boolean")
validator._normalize_coerce_boolean("1")
convert_mock.assert_called_once_with("1")
def test_normalize_coerce_integer(validator: Validator) -> None:
"""
must convert string value to integer by using configuration converters
"""
assert validator._normalize_coerce_integer("1") == 1
assert validator._normalize_coerce_integer("42") == 42
def test_normalize_coerce_list(validator: Validator) -> None:
"""
must convert string value to list by using configuration converters
"""
convert_mock = MagicMock()
validator.instance.converters["list"] = convert_mock
validator._normalize_coerce_list("value")
convert_mock.assert_called_once_with("value")
def test_validate_path_exists(validator: Validator, mocker: MockerFixture) -> None:
"""
must validate that paths exists
"""
error_mock = mocker.patch("ahriman.core.configuration.validator.Validator._error")
mocker.patch("pathlib.Path.exists", return_value=False)
validator._validate_path_exists(False, "field", Path("1"))
mocker.patch("pathlib.Path.exists", return_value=False)
validator._validate_path_exists(True, "field", Path("2"))
mocker.patch("pathlib.Path.exists", return_value=True)
validator._validate_path_exists(True, "field", Path("3"))
error_mock.assert_called_once_with("field", "Path 2 must exist")

View File

@ -1,7 +1,7 @@
import pytest
from ahriman.core.formatters import AurPrinter, ConfigurationPrinter, PackagePrinter, PatchPrinter, StatusPrinter, \
StringPrinter, TreePrinter, UpdatePrinter, UserPrinter, VersionPrinter
StringPrinter, TreePrinter, UpdatePrinter, UserPrinter, ValidationPrinter, VersionPrinter
from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
@ -126,6 +126,29 @@ def user_printer(user: User) -> UserPrinter:
return UserPrinter(user)
@pytest.fixture
def validation_printer() -> ValidationPrinter:
"""
fixture for validation printer
Returns:
ValidationPrinter: validation printer test instance
"""
return ValidationPrinter("root", [
"root error",
{
"child": [
"child error",
{
"grandchild": [
"grandchild error",
],
},
],
},
])
@pytest.fixture
def version_printer(package_ahriman: Package) -> VersionPrinter:
"""

View File

@ -1,7 +1,9 @@
from unittest.mock import MagicMock
from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call as MockCall
from ahriman.core.formatters import PackagePrinter
from ahriman.core.formatters import Printer
from ahriman.models.property import Property
def test_print(package_ahriman_printer: PackagePrinter) -> None:
@ -31,6 +33,24 @@ def test_print_verbose(package_ahriman_printer: PackagePrinter) -> None:
log_mock.assert_called()
def test_print_indent(mocker: MockerFixture) -> None:
"""
must correctly use indentation
"""
log_mock = MagicMock()
mocker.patch("ahriman.core.formatters.Printer.properties", return_value=[Property("key", "value", indent=0)])
Printer().print(verbose=True, log_fn=log_mock)
mocker.patch("ahriman.core.formatters.Printer.properties", return_value=[Property("key", "value", indent=1)])
Printer().print(verbose=True, log_fn=log_mock)
mocker.patch("ahriman.core.formatters.Printer.properties", return_value=[Property("key", "value", indent=2)])
Printer().print(verbose=True, log_fn=log_mock)
log_mock.assert_has_calls([MockCall("key: value"), MockCall("\tkey: value"), MockCall("\t\tkey: value")])
def test_properties() -> None:
"""
must return empty properties list

View File

@ -0,0 +1,28 @@
from ahriman.core.formatters import ValidationPrinter
from ahriman.models.property import Property
def test_properties(validation_printer: ValidationPrinter) -> None:
"""
must return non-empty properties list
"""
assert validation_printer.properties()
def test_title(validation_printer: ValidationPrinter) -> None:
"""
must return non-empty title
"""
assert validation_printer.title() is not None
def test_get_error_messages(validation_printer: ValidationPrinter) -> None:
"""
must get error messages from plain list
"""
result = ValidationPrinter.get_error_messages(validation_printer.node, validation_printer.errors)
assert list(result) == [
Property("root", "root error", is_required=True, indent=1),
Property("child", "child error", is_required=True, indent=2),
Property("grandchild", "grandchild error", is_required=True, indent=3),
]