Files
zensical/python/tests/unit/test_llmstxt_config.py
T
Timothée Mazzucotelli 9f9b25b7ab feature: add llmstxt MkDocs plugin replacement
Also defer llmstxt validation until initial page processing completes.

Signed-off-by: Timothée Mazzucotelli <dev@pawamoy.fr>
2026-09-29 18:35:39 +00:00

153 lines
5.1 KiB
Python
Vendored

# Copyright (c) 2025-2026 Zensical and contributors
# SPDX-License-Identifier: MIT
# All contributions are certified under the DCO
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
"""Configuration compatibility for native LLM text output."""
from __future__ import annotations
from typing import Any
import pytest
from zensical.config import ConfigurationError, _convert_plugins
def convert(
value: Any, site_url: str | None = "https://example.org/docs/"
) -> dict[str, Any]:
"""Normalize plugin settings with the project fields they use."""
project = {
"site_url": site_url,
"extra": {"polyfills": []},
"root_dir": ".",
}
return _convert_plugins(value, project)["llmstxt"]["config"]
def test_plugin_presence_controls_generation() -> None:
assert convert([])["enabled"] is False
assert convert(["llmstxt"])["enabled"] is True
assert (
convert({"llmstxt": {"enabled": False}}, site_url=None)["enabled"]
is False
)
def test_sections_preserve_order_patterns_and_descriptions() -> None:
settings = convert(
{
"llmstxt": {
"base_url": "https://other.org/v1",
"markdown_description": "Long **description**.",
"full_output": "ai/full.txt",
"sections": {
"Z first": [{"index.md": "Home"}, "guide/*.md"],
"A second": ["other.md"],
},
}
}
)
assert settings["autoclean"] is True
assert settings["base_url"] == "https://other.org/v1"
assert settings["full_output"] == "ai/full.txt"
assert [section["title"] for section in settings["sections"]] == [
"Z first",
"A second",
]
inputs = settings["sections"][0]["inputs"]
assert inputs[0] == {
"pattern": "index.md",
"description": "Home",
"expression": None,
}
assert inputs[1]["pattern"] == "guide/*.md"
assert inputs[1]["expression"].startswith(r"\A(?s:")
@pytest.mark.parametrize(
"preprocess",
[
None,
"custom.py",
False,
42,
["custom.py"],
{"function": "preprocess"},
object(),
],
)
def test_silently_ignores_preprocess(
preprocess: Any,
capsys: pytest.CaptureFixture[str],
caplog: pytest.LogCaptureFixture,
) -> None:
expected = convert(["llmstxt"])
settings = convert({"llmstxt": {"preprocess": preprocess}})
assert settings == expected
captured = capsys.readouterr()
assert captured.out == ""
assert captured.err == ""
assert not caplog.records
@pytest.mark.parametrize(
("settings", "message"),
[
({"enabled": "yes"}, "enabled must be a boolean"),
({"autoclean": 1}, "autoclean must be a boolean"),
({"base_url": 42}, "base_url must be a string"),
({"markdown_description": []}, "markdown_description must be a string"),
({"full_output": True}, "full_output must be a string"),
({"full_output": "../full.txt"}, "relative file path"),
({"full_output": "/full.txt"}, "relative file path"),
({"full_output": "full/../file.txt"}, "relative file path"),
({"full_output": "llms.txt"}, "must differ from llms.txt"),
({"sections": []}, "sections must be a mapping"),
({"sections": {"Pages": "index.md"}}, "map strings to lists"),
({"sections": {"Pages": [False]}}, "single-entry mappings"),
(
{"sections": {"Pages": [{"a.md": "A", "b.md": "B"}]}},
"single-entry mappings",
),
(
{"sections": {"Pages": [{"a.md": 1}]}},
"descriptions must be strings",
),
({"sections": {"Pages": [""]}}, "non-empty strings"),
({"unknown": True}, "unknown llmstxt option"),
],
)
def test_invalid_settings_report_configuration_errors(
settings: dict[str, Any], message: str
) -> None:
with pytest.raises(ConfigurationError, match=message):
convert({"llmstxt": settings})
def test_enabled_plugin_requires_site_url() -> None:
with pytest.raises(ConfigurationError, match="requires site_url"):
convert(["llmstxt"], site_url=None)