98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import replace
|
|
from datetime import UTC, datetime
|
|
import hashlib
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from govoplan_xrechnung.backend.profiles import KoSITProfileApproval, KoSITProfileRegistry
|
|
from govoplan_xrechnung.backend.validation import (
|
|
KoSITValidationProfile,
|
|
XRechnungValidationError,
|
|
configuration_tree_sha256,
|
|
)
|
|
|
|
|
|
def _profile(tmp_path: Path, profile_id: str = "profile-a") -> KoSITValidationProfile:
|
|
root = tmp_path / profile_id
|
|
root.mkdir()
|
|
java = root / "java"
|
|
java.write_bytes(b"#!/bin/sh\n")
|
|
java.chmod(0o700)
|
|
jar = root / "validator.jar"
|
|
jar.write_bytes(b"validator")
|
|
config = root / "config"
|
|
config.mkdir()
|
|
scenarios = config / "scenarios.xml"
|
|
scenarios.write_text("<scenarios/>", encoding="utf-8")
|
|
return KoSITValidationProfile(
|
|
profile_id=profile_id,
|
|
xrechnung_version="configured-version",
|
|
validator_version="configured-validator",
|
|
configuration_version="configured-rules",
|
|
java_executable=java.resolve(),
|
|
validator_jar=jar.resolve(),
|
|
validator_jar_sha256=hashlib.sha256(jar.read_bytes()).hexdigest(),
|
|
configuration_root=config.resolve(),
|
|
configuration_tree_sha256=configuration_tree_sha256(config),
|
|
scenarios_file=scenarios.resolve(),
|
|
minimum_validation_steps=1,
|
|
)
|
|
|
|
|
|
def _approval(tmp_path: Path) -> KoSITProfileApproval:
|
|
return KoSITProfileApproval.approve(
|
|
_profile(tmp_path),
|
|
approved_at=datetime(2026, 8, 23, tzinfo=UTC),
|
|
approved_by="configuration-board",
|
|
decision_reference="decision:xrechnung-profile-a",
|
|
accept_from=datetime(2026, 9, 1, tzinfo=UTC),
|
|
accept_until=datetime(2027, 1, 1, tzinfo=UTC),
|
|
)
|
|
|
|
|
|
def test_registry_requires_explicit_selection_without_default(tmp_path: Path) -> None:
|
|
registry = KoSITProfileRegistry((_approval(tmp_path),))
|
|
|
|
with pytest.raises(XRechnungValidationError, match="no default"):
|
|
registry.select(received_at=datetime(2026, 10, 1, tzinfo=UTC))
|
|
|
|
|
|
def test_explicit_or_configured_default_selects_only_inside_window(tmp_path: Path) -> None:
|
|
approval = _approval(tmp_path)
|
|
registry = KoSITProfileRegistry(
|
|
(approval,),
|
|
default_profile_id=approval.profile.profile_id,
|
|
)
|
|
|
|
selected = registry.select(received_at=datetime(2026, 10, 1, tzinfo=UTC))
|
|
assert selected.profile_id == "profile-a"
|
|
|
|
with pytest.raises(XRechnungValidationError, match="receive time"):
|
|
registry.select(received_at=datetime(2027, 1, 1, tzinfo=UTC))
|
|
|
|
|
|
@pytest.mark.parametrize("status", ["suspended", "retired"])
|
|
def test_non_approved_profile_cannot_be_selected(tmp_path: Path, status: str) -> None:
|
|
approval = replace(_approval(tmp_path), status=status)
|
|
registry = KoSITProfileRegistry((approval,), default_profile_id="profile-a")
|
|
|
|
with pytest.raises(XRechnungValidationError, match="not approved"):
|
|
registry.select(received_at=datetime(2026, 10, 1, tzinfo=UTC))
|
|
|
|
|
|
def test_changed_artifact_is_rejected_after_approval(tmp_path: Path) -> None:
|
|
approval = _approval(tmp_path)
|
|
registry = KoSITProfileRegistry((approval,), default_profile_id="profile-a")
|
|
approval.profile.validator_jar.write_bytes(b"changed")
|
|
|
|
with pytest.raises(XRechnungValidationError, match="digest"):
|
|
registry.select(received_at=datetime(2026, 10, 1, tzinfo=UTC))
|
|
|
|
|
|
def test_default_must_reference_allow_list(tmp_path: Path) -> None:
|
|
with pytest.raises(ValueError, match="not present"):
|
|
KoSITProfileRegistry((_approval(tmp_path),), default_profile_id="missing")
|