feat(import): user-extensible BOR_IMPORT_EXTENSIONS — any well-formed extension, A9 family stays the default
This commit is contained in:
+42
-25
@@ -8,7 +8,7 @@ import pytest
|
||||
from pydantic import ValidationError
|
||||
from pydantic_settings import SettingsError
|
||||
|
||||
from app.config import _ALLOWED_IMPORT_EXTENSIONS, Settings # pyright: ignore[reportPrivateUsage]
|
||||
from app.config import _DEFAULT_IMPORT_EXTENSIONS, Settings # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
|
||||
def _settings(**kwargs: Any) -> Settings:
|
||||
@@ -57,15 +57,15 @@ NEW_A9_FORMATS = (
|
||||
)
|
||||
|
||||
|
||||
def test_allowed_import_extensions_contains_all_seventeen_formats() -> None:
|
||||
"""The validator's base set is the full A9 set: the original seven
|
||||
plus the ten added 2026-08-27 (quadlet family + ``j2``). The
|
||||
never-widen contract bounds :py:data:`import_extensions` against
|
||||
exactly this set."""
|
||||
def test_default_import_extensions_is_the_full_a9_family() -> None:
|
||||
"""Phase 56: the built-in default is the full A9 set — the original
|
||||
seven plus the ten added 2026-08-27 (quadlet family + ``j2``). It is
|
||||
the default and the ``.env.example`` example, NOT a ceiling: the
|
||||
validator accepts any well-formed extension beyond it."""
|
||||
assert {
|
||||
"md", "markdown", "txt", "yaml", "yml", "json", "py",
|
||||
*NEW_A9_FORMATS,
|
||||
} == _ALLOWED_IMPORT_EXTENSIONS
|
||||
} == _DEFAULT_IMPORT_EXTENSIONS
|
||||
|
||||
|
||||
def test_default_import_extensions_include_the_ten_new_formats() -> None:
|
||||
@@ -151,34 +151,51 @@ def test_import_extensions_env_override_is_a_csv_list(monkeypatch) -> None:
|
||||
assert s.import_extension_set == {".md", ".yml"}
|
||||
|
||||
|
||||
def test_import_extensions_rejects_unknown_format(monkeypatch) -> None:
|
||||
"""A typo in the CSV fails at startup (loudly), not by silently
|
||||
walking zero files."""
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "md,docx")
|
||||
with pytest.raises(ValidationError, match="docx"):
|
||||
_settings()
|
||||
def test_import_extensions_accepts_novel_extension(monkeypatch) -> None:
|
||||
"""Phase 56 (owner permission 2026-08-31): the A9 family is the
|
||||
default, not the ceiling — a novel well-formed extension (``sh``) is
|
||||
accepted and simply becomes importable."""
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "md,sh")
|
||||
s = _settings()
|
||||
assert s.import_extension_set == {".md", ".sh"}
|
||||
|
||||
|
||||
def test_import_extensions_rejects_empty(monkeypatch) -> None:
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", " ")
|
||||
with pytest.raises(ValidationError):
|
||||
_settings()
|
||||
def test_import_extensions_normalizes_case_and_leading_dot(monkeypatch) -> None:
|
||||
"""Case and a leading dot are both tolerated (unchanged tolerance)."""
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "MD,.Py")
|
||||
s = _settings()
|
||||
assert s.import_extension_set == {".md", ".py"}
|
||||
|
||||
|
||||
def test_import_extensions_rejects_empty(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""A blank list would silently import nothing — fail loudly at
|
||||
startup, naming the field (empty, whitespace-only, and comma-only
|
||||
all parse to zero formats)."""
|
||||
for value in ("", " ", ",,"):
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", value)
|
||||
with pytest.raises(ValidationError, match="import_extensions"):
|
||||
_settings()
|
||||
|
||||
|
||||
def test_import_extensions_validator_accepts_new_a9_formats(monkeypatch) -> None:
|
||||
"""A9 revised 2026-08-27: the new names are first-class — the
|
||||
never-widen contract now holds against the widened base set, so a
|
||||
narrowing CSV with quadlet/jinja names is accepted."""
|
||||
"""A9 revised 2026-08-27: quadlet/jinja names are first-class default
|
||||
formats — a CSV using them (a narrowing of the default family) is
|
||||
accepted."""
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "md,container,j2")
|
||||
s = _settings()
|
||||
assert s.import_extension_set == {".md", ".container", ".j2"}
|
||||
|
||||
|
||||
def test_import_extensions_validator_still_rejects_unknown(monkeypatch) -> None:
|
||||
"""Truly unknown extensions still fail loudly at startup (the
|
||||
validator is intact — only the allowed base set widened)."""
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "md,xyz")
|
||||
with pytest.raises(ValidationError, match="xyz"):
|
||||
def test_import_extensions_rejects_malformed_tokens(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""The shape guard (``^[a-z0-9]{1,16}$``) is the typo guard — it
|
||||
keeps punctuation and path-ish values out of the set, naming the
|
||||
offending token(s), while any extension a file could actually be
|
||||
suffixed with still goes through."""
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "md,sh!")
|
||||
with pytest.raises(ValidationError, match="sh!"):
|
||||
_settings()
|
||||
monkeypatch.setenv("BOR_IMPORT_EXTENSIONS", "md,../x")
|
||||
with pytest.raises(ValidationError, match=r"/x"):
|
||||
_settings()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user