Files
ArchiveBox/archivebox/tests/test_config_views.py
Nick Sweeting b749b26c5d wip
2026-03-23 03:58:32 -07:00

334 lines
14 KiB
Python

from datetime import timedelta
from types import SimpleNamespace
import pytest
from django.test import RequestFactory
from django.utils import timezone
from archivebox.config import views as config_views
from archivebox.core import views as core_views
from archivebox.machine.models import Binary
pytestmark = pytest.mark.django_db
def test_get_db_binaries_by_name_collapses_youtube_dl_aliases(monkeypatch):
now = timezone.now()
records = [
SimpleNamespace(
name="youtube-dl",
version="",
binprovider="",
abspath="/usr/bin/youtube-dl",
status=Binary.StatusChoices.INSTALLED,
modified_at=now,
),
SimpleNamespace(
name="yt-dlp",
version="2026.03.01",
binprovider="pip",
abspath="/usr/bin/yt-dlp",
status=Binary.StatusChoices.INSTALLED,
modified_at=now + timedelta(seconds=1),
),
]
monkeypatch.setattr(config_views.Binary, "objects", SimpleNamespace(all=lambda: records))
binaries = config_views.get_db_binaries_by_name()
assert "yt-dlp" in binaries
assert "youtube-dl" not in binaries
assert binaries["yt-dlp"].version == "2026.03.01"
def test_binaries_list_view_uses_db_version_and_hides_youtube_dl_alias(monkeypatch):
request = RequestFactory().get("/admin/environment/binaries/")
request.user = SimpleNamespace(is_superuser=True)
db_binary = SimpleNamespace(
name="youtube-dl",
version="2026.03.01",
binprovider="pip",
abspath="/usr/bin/yt-dlp",
status=Binary.StatusChoices.INSTALLED,
sha256="",
modified_at=timezone.now(),
)
monkeypatch.setattr(config_views, "get_db_binaries_by_name", lambda: {"yt-dlp": db_binary})
context = config_views.binaries_list_view.__wrapped__(request)
assert len(context["table"]["Binary Name"]) == 1
assert str(context["table"]["Binary Name"][0].link_item) == "yt-dlp"
assert context["table"]["Found Version"][0] == "✅ 2026.03.01"
assert context["table"]["Provided By"][0] == "pip"
assert context["table"]["Found Abspath"][0] == "/usr/bin/yt-dlp"
def test_binaries_list_view_only_shows_persisted_records(monkeypatch):
request = RequestFactory().get("/admin/environment/binaries/")
request.user = SimpleNamespace(is_superuser=True)
monkeypatch.setattr(config_views, "get_db_binaries_by_name", lambda: {})
context = config_views.binaries_list_view.__wrapped__(request)
assert context["table"]["Binary Name"] == []
assert context["table"]["Found Version"] == []
assert context["table"]["Provided By"] == []
assert context["table"]["Found Abspath"] == []
def test_binary_detail_view_uses_canonical_db_record(monkeypatch):
request = RequestFactory().get("/admin/environment/binaries/youtube-dl/")
request.user = SimpleNamespace(is_superuser=True)
db_binary = SimpleNamespace(
id="019d14cc-6c40-7793-8ff1-0f8bb050e8a3",
name="yt-dlp",
version="2026.03.01",
binprovider="pip",
abspath="/usr/bin/yt-dlp",
sha256="abc123",
status=Binary.StatusChoices.INSTALLED,
modified_at=timezone.now(),
)
monkeypatch.setattr(config_views, "get_db_binaries_by_name", lambda: {"yt-dlp": db_binary})
context = config_views.binary_detail_view.__wrapped__(request, key="youtube-dl")
section = context["data"][0]
assert context["title"] == "yt-dlp"
assert section["fields"]["name"] == "yt-dlp"
assert section["fields"]["version"] == "2026.03.01"
assert section["fields"]["binprovider"] == "pip"
assert section["fields"]["abspath"] == "/usr/bin/yt-dlp"
assert "/admin/machine/binary/019d14cc-6c40-7793-8ff1-0f8bb050e8a3/change/?_changelist_filters=q%3Dyt-dlp" in section["description"]
def test_binary_detail_view_marks_unrecorded_binary(monkeypatch):
request = RequestFactory().get("/admin/environment/binaries/wget/")
request.user = SimpleNamespace(is_superuser=True)
monkeypatch.setattr(config_views, "get_db_binaries_by_name", lambda: {})
context = config_views.binary_detail_view.__wrapped__(request, key="wget")
section = context["data"][0]
assert section["description"] == "No persisted Binary record found"
assert section["fields"]["status"] == "unrecorded"
assert section["fields"]["binprovider"] == "not recorded"
def test_plugin_detail_view_renders_config_in_dedicated_sections(monkeypatch):
request = RequestFactory().get("/admin/environment/plugins/builtin.example/")
request.user = SimpleNamespace(is_superuser=True)
plugin_config = {
"title": "Example Plugin",
"description": "Example config used to verify plugin metadata rendering.",
"type": "object",
"required_plugins": ["chrome"],
"required_binaries": ["example-cli"],
"output_mimetypes": ["text/plain", "application/json"],
"properties": {
"EXAMPLE_ENABLED": {
"type": "boolean",
"description": "Enable the example plugin.",
"x-fallback": "CHECK_SSL_VALIDITY",
},
"EXAMPLE_BINARY": {
"type": "string",
"default": "gallery-dl",
"description": "Filesystem path for example output.",
"x-aliases": ["USE_EXAMPLE_BINARY"],
},
},
}
monkeypatch.setattr(
config_views,
"get_filesystem_plugins",
lambda: {
"builtin.example": {
"id": "builtin.example",
"name": "example",
"source": "builtin",
"path": "/plugins/example",
"hooks": ["on_Snapshot__01_example.py"],
"config": plugin_config,
},
},
)
monkeypatch.setattr(config_views, "get_machine_admin_url", lambda: "/admin/machine/machine/test-machine/change/")
context = config_views.plugin_detail_view.__wrapped__(request, key="builtin.example")
assert context["title"] == "example"
assert len(context["data"]) == 5
summary_section, hooks_section, metadata_section, config_section, properties_section = context["data"]
assert summary_section["fields"] == {
"id": "builtin.example",
"name": "example",
"source": "builtin",
}
assert "/plugins/example" in summary_section["description"]
assert "https://archivebox.github.io/abx-plugins/#example" in summary_section["description"]
assert hooks_section["name"] == "Hooks"
assert hooks_section["fields"] == {}
assert (
"https://github.com/ArchiveBox/abx-plugins/tree/main/abx_plugins/plugins/example/on_Snapshot__01_example.py"
in hooks_section["description"]
)
assert "on_Snapshot__01_example.py" in hooks_section["description"]
assert metadata_section["name"] == "Plugin Metadata"
assert metadata_section["fields"] == {}
assert "Example Plugin" in metadata_section["description"]
assert "Example config used to verify plugin metadata rendering." in metadata_section["description"]
assert "https://archivebox.github.io/abx-plugins/#chrome" in metadata_section["description"]
assert "/admin/environment/binaries/example-cli/" in metadata_section["description"]
assert "text/plain" in metadata_section["description"]
assert "application/json" in metadata_section["description"]
assert config_section["name"] == "config.json"
assert config_section["fields"] == {}
assert "<pre style=" in config_section["description"]
assert "EXAMPLE_ENABLED" in config_section["description"]
assert '<span style="color: #0550ae;">"properties"</span>' in config_section["description"]
assert properties_section["name"] == "Config Properties"
assert properties_section["fields"] == {}
assert "/admin/machine/machine/test-machine/change/" in properties_section["description"]
assert "/admin/machine/binary/" in properties_section["description"]
assert "/admin/environment/binaries/" in properties_section["description"]
assert "EXAMPLE_ENABLED" in properties_section["description"]
assert "boolean" in properties_section["description"]
assert "Enable the example plugin." in properties_section["description"]
assert "/admin/environment/config/EXAMPLE_ENABLED/" in properties_section["description"]
assert "/admin/environment/config/CHECK_SSL_VALIDITY/" in properties_section["description"]
assert "/admin/environment/config/USE_EXAMPLE_BINARY/" in properties_section["description"]
assert "/admin/environment/binaries/gallery-dl/" in properties_section["description"]
assert "EXAMPLE_BINARY" in properties_section["description"]
def test_get_config_definition_link_keeps_core_config_search_link(monkeypatch):
monkeypatch.setattr(core_views, "find_plugin_for_config_key", lambda key: None)
url, label = core_views.get_config_definition_link("CHECK_SSL_VALIDITY")
assert "github.com/search" in url
assert "CHECK_SSL_VALIDITY" in url
assert label == "archivebox/config"
def test_get_config_definition_link_uses_plugin_config_json_for_plugin_options(monkeypatch):
plugin_dir = core_views.BUILTIN_PLUGINS_DIR / "parse_dom_outlinks"
monkeypatch.setattr(core_views, "find_plugin_for_config_key", lambda key: "parse_dom_outlinks")
monkeypatch.setattr(core_views, "iter_plugin_dirs", lambda: [plugin_dir])
url, label = core_views.get_config_definition_link("PARSE_DOM_OUTLINKS_ENABLED")
assert url == "https://github.com/ArchiveBox/abx-plugins/tree/main/abx_plugins/plugins/parse_dom_outlinks/config.json"
assert label == "abx_plugins/plugins/parse_dom_outlinks/config.json"
def test_live_config_value_view_renames_source_field_and_uses_plugin_definition_link(monkeypatch):
request = RequestFactory().get("/admin/environment/config/PARSE_DOM_OUTLINKS_ENABLED/")
request.user = SimpleNamespace(is_superuser=True)
monkeypatch.setattr(core_views, "get_all_configs", lambda: {})
monkeypatch.setattr(core_views, "get_flat_config", lambda: {})
monkeypatch.setattr(core_views, "get_config", lambda: {"PARSE_DOM_OUTLINKS_ENABLED": True})
monkeypatch.setattr(core_views, "find_config_default", lambda key: "True")
monkeypatch.setattr(core_views, "find_config_type", lambda key: "bool")
monkeypatch.setattr(core_views, "find_config_source", lambda key, merged: "Default")
monkeypatch.setattr(core_views, "key_is_safe", lambda key: True)
monkeypatch.setattr(core_views.CONSTANTS, "CONFIG_FILE", SimpleNamespace(exists=lambda: False))
from archivebox.machine.models import Machine
from archivebox.config.configset import BaseConfigSet
monkeypatch.setattr(Machine, "current", classmethod(lambda cls: SimpleNamespace(id="machine-id", config={})))
monkeypatch.setattr(BaseConfigSet, "load_from_file", classmethod(lambda cls, path: {}))
monkeypatch.setattr(
core_views,
"get_config_definition_link",
lambda key: (
"https://github.com/ArchiveBox/abx-plugins/tree/main/abx_plugins/plugins/parse_dom_outlinks/config.json",
"abx_plugins/plugins/parse_dom_outlinks/config.json",
),
)
context = core_views.live_config_value_view.__wrapped__(request, key="PARSE_DOM_OUTLINKS_ENABLED")
section = context["data"][0]
assert "Currently read from" in section["fields"]
assert "Source" not in section["fields"]
assert section["fields"]["Currently read from"] == "Default"
assert "abx_plugins/plugins/parse_dom_outlinks/config.json" in section["help_texts"]["Type"]
def test_find_config_source_prefers_environment_over_machine_and_file(monkeypatch):
monkeypatch.setenv("CHECK_SSL_VALIDITY", "false")
from archivebox.machine.models import Machine
from archivebox.config.configset import BaseConfigSet
monkeypatch.setattr(
Machine,
"current",
classmethod(lambda cls: SimpleNamespace(id="machine-id", config={"CHECK_SSL_VALIDITY": "true"})),
)
monkeypatch.setattr(
BaseConfigSet,
"load_from_file",
classmethod(lambda cls, path: {"CHECK_SSL_VALIDITY": "true"}),
)
assert core_views.find_config_source("CHECK_SSL_VALIDITY", {"CHECK_SSL_VALIDITY": False}) == "Environment"
def test_live_config_value_view_priority_text_matches_runtime_precedence(monkeypatch):
request = RequestFactory().get("/admin/environment/config/CHECK_SSL_VALIDITY/")
request.user = SimpleNamespace(is_superuser=True)
monkeypatch.setattr(core_views, "get_all_configs", lambda: {})
monkeypatch.setattr(core_views, "get_flat_config", lambda: {"CHECK_SSL_VALIDITY": True})
monkeypatch.setattr(core_views, "get_config", lambda: {"CHECK_SSL_VALIDITY": False})
monkeypatch.setattr(core_views, "find_config_default", lambda key: "True")
monkeypatch.setattr(core_views, "find_config_type", lambda key: "bool")
monkeypatch.setattr(core_views, "key_is_safe", lambda key: True)
from archivebox.machine.models import Machine
from archivebox.config.configset import BaseConfigSet
monkeypatch.setattr(
Machine,
"current",
classmethod(lambda cls: SimpleNamespace(id="machine-id", config={"CHECK_SSL_VALIDITY": "true"})),
)
monkeypatch.setattr(
BaseConfigSet,
"load_from_file",
classmethod(lambda cls, path: {"CHECK_SSL_VALIDITY": "true"}),
)
monkeypatch.setattr(core_views.CONSTANTS, "CONFIG_FILE", SimpleNamespace(exists=lambda: True))
monkeypatch.setenv("CHECK_SSL_VALIDITY", "false")
context = core_views.live_config_value_view.__wrapped__(request, key="CHECK_SSL_VALIDITY")
section = context["data"][0]
assert section["fields"]["Currently read from"] == "Environment"
help_text = section["help_texts"]["Currently read from"]
assert help_text.index("Environment") < help_text.index("Machine") < help_text.index("Config File") < help_text.index("Default")
assert "Configuration Sources (highest priority first):" in section["help_texts"]["Value"]