mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-12 19:50:57 +05:00
293 lines
13 KiB
Python
293 lines
13 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
from django.contrib.auth.models import AnonymousUser
|
|
from django.template.loader import render_to_string
|
|
from django.test import RequestFactory
|
|
|
|
from archivebox.base_models.admin import KeyValueWidget
|
|
from archivebox.config.common import get_config
|
|
from archivebox.core.setup_wizard import get_base_url_mismatch_context, get_setup_wizard_context
|
|
from archivebox.core.templatetags.core_tags import system_warnings_banner
|
|
|
|
STATIC_DIR = Path(__file__).parents[1] / "templates" / "static"
|
|
SETUP_WIZARD_CSS = (STATIC_DIR / "setup_wizard.css").read_text()
|
|
SETUP_WIZARD_JS = (STATIC_DIR / "setup_wizard.js").read_text()
|
|
|
|
|
|
def test_key_value_widget_renders_enum_autocomplete_metadata():
|
|
html = str(
|
|
KeyValueWidget().render(
|
|
"config",
|
|
{"CHROME_WAIT_FOR": "load"},
|
|
attrs={"id": "id_config"},
|
|
),
|
|
)
|
|
|
|
assert '"enum": ["domcontentloaded", "load", "networkidle0", "networkidle2"]' in html
|
|
assert 'class="kv-value-options"' in html
|
|
assert 'class="kv-help"' in html
|
|
assert "configureValueInput_id_config" in html
|
|
assert "describeMeta_id_config" in html
|
|
assert "validateValueAgainstMeta_id_config" in html
|
|
|
|
|
|
def test_key_value_widget_renders_numeric_and_pattern_constraints():
|
|
html = str(KeyValueWidget().render("config", {}, attrs={"id": "id_config"}))
|
|
|
|
assert '"minimum": 0' in html
|
|
assert '"pattern": "^\\\\d+,\\\\d+$"' in html
|
|
assert "Expected: " in html
|
|
assert "Example: " in html
|
|
assert "setValueValidationState_id_config" in html
|
|
assert "coerceValueForStorage_id_config" in html
|
|
|
|
|
|
def test_key_value_widget_accepts_common_boolean_spellings():
|
|
html = str(KeyValueWidget().render("config", {"CHECK_SSL_VALIDITY": "True"}, attrs={"id": "id_config"}))
|
|
|
|
assert "enumValues = ['True', 'False']" in html
|
|
assert "raw.toLowerCase()" in html
|
|
assert "lowered === 'true' || raw === '1'" in html
|
|
assert "lowered === 'false' || raw === '0'" in html
|
|
|
|
|
|
def test_key_value_widget_shows_array_and_object_examples_and_binary_rules():
|
|
html = str(KeyValueWidget().render("config", {"NODE_BINARY": "node"}, attrs={"id": "id_config"}))
|
|
|
|
assert 'Example: ["--extra-arg"]' in html
|
|
assert "Example: wget or /usr/bin/wget" in html
|
|
assert "validateBinaryValue_id_config" in html
|
|
assert "meta.key.endsWith('_BINARY')" in html
|
|
assert "Binary paths cannot contain quotes" in html
|
|
|
|
|
|
def test_key_value_widget_falls_back_to_binary_validation_for_unknown_binary_keys():
|
|
html = str(
|
|
KeyValueWidget().render(
|
|
"config",
|
|
{"NODE_BINARY": "/opt/homebrew/bin/node"},
|
|
attrs={"id": "id_config"},
|
|
),
|
|
)
|
|
|
|
assert "function getMetaForKey_id_config" in html
|
|
assert "if (key.endsWith('_BINARY'))" in html
|
|
assert "Path to binary executable" in html
|
|
|
|
|
|
def test_key_value_widget_prefills_known_config_values_from_query_string():
|
|
html = str(KeyValueWidget().render("config", {}, attrs={"id": "id_config"}))
|
|
|
|
assert "new URLSearchParams(window.location.search)" in html
|
|
assert "prefillConfigFromQuery_id_config" in html
|
|
assert "configMeta_id_config[key]" in html
|
|
assert "valueInput.value = value" in html
|
|
assert "params.forEach(function(value, key)" in html
|
|
assert "configMeta_id_config[key]" in html
|
|
assert "consumedConfigKeys.forEach" in html
|
|
assert "window.history.replaceState(null, '', cleanUrl.pathname + cleanUrl.search + cleanUrl.hash)" in html
|
|
|
|
|
|
def test_unconfigured_superuser_banner_uses_browser_assisted_setup_wizard():
|
|
html = render_to_string(
|
|
"core/system_warnings_banner.html",
|
|
{
|
|
"mode": "unconfigured",
|
|
"can_configure": True,
|
|
"canonical_host": "archivebox.example.test:8000",
|
|
"suggested_base_url": "http://archivebox.example.test:8000",
|
|
"machine_admin_url": "/admin/machine/machine/current/change/",
|
|
"public_index": True,
|
|
"public_add_view": False,
|
|
"permissions": "private",
|
|
},
|
|
)
|
|
|
|
assert html.count('id="archivebox-setup-wizard"') == 1
|
|
assert 'id="archivebox-system-warning-banner"' not in html
|
|
assert "⚠ base_url not set" not in html
|
|
assert "Setup access to your ArchiveBox Server: <code>archivebox.example.test:8000</code>" in html
|
|
assert 'id="archivebox-setup-base-url"' in html
|
|
assert 'id="archivebox-setup-security-mode"' in html
|
|
assert 'id="archivebox-setup-public-index"' in html
|
|
assert 'id="archivebox-setup-public-add"' in html
|
|
assert 'id="archivebox-setup-permissions"' in html
|
|
assert 'id="archivebox-setup-effective-mode"' in html
|
|
assert 'id="archivebox-setup-browser-url"' in html
|
|
assert 'id="archivebox-setup-configured-url"' in html
|
|
assert 'id="archivebox-setup-url-match"' in html
|
|
assert 'name="archivebox-hosting-location"' in html
|
|
assert 'name="archivebox-dns-mode"' in html
|
|
assert 'name="archivebox-tls-mode"' in html
|
|
assert (
|
|
html.index('name="archivebox-dns-mode" value="localhost"')
|
|
< html.index(
|
|
'name="archivebox-dns-mode" value="single"',
|
|
)
|
|
< html.index('name="archivebox-dns-mode" value="wildcard"')
|
|
)
|
|
assert (
|
|
html.index('name="archivebox-tls-mode" value="localhost"')
|
|
< html.index(
|
|
'name="archivebox-tls-mode" value="none"',
|
|
)
|
|
< html.index('name="archivebox-tls-mode" value="single"')
|
|
< html.index('name="archivebox-tls-mode" value="wildcard"')
|
|
)
|
|
assert 'id="archivebox-setup-later"' not in html
|
|
assert 'id="archivebox-setup-review" disabled' in html
|
|
assert "as BASE_URL below" not in html
|
|
assert "Required:" not in html
|
|
assert "safe-onedomain-nojsreplay" in html
|
|
assert "simplest self-hosted setup" in html
|
|
assert "safe-subdomains-fullreplay" in html
|
|
assert "unsafe-onedomain-noadmin" in html
|
|
assert "danger-onedomain-fullreplay" in html
|
|
assert "Wildcard DNS" in html
|
|
assert "Modern browsers support wildcard <code>*.archivebox.localhost</code> with no DNS or HTTPS setup required." in html
|
|
assert "A archivebox.example.com" in html
|
|
assert "JavaScript still runs during capture" in html
|
|
assert "will not replay JavaScript unless wildcard DNS is used" in html
|
|
assert "Wildcard TLS" in html
|
|
assert "How will HTTPS traffic reach this ArchiveBox server?" in html
|
|
assert "This mode is not allowed unless also using Single-domain DNS." in html
|
|
assert "No separate ingress service / SSL termination" in html
|
|
assert "Cloudflare/AWS/Nginx/Caddy/Traefik" in html
|
|
assert "In-browser WARC viewing will be disabled unless using <code>localhost</code> or HTTPS" in html
|
|
assert "BASE_URL" in html
|
|
assert "SERVER_SECURITY_MODE" in html
|
|
assert "PUBLIC_INDEX" in html
|
|
assert "PUBLIC_ADD_VIEW" in html
|
|
assert "Be careful archiving any secret URLs or private share URLs" in html
|
|
assert "Beware capturing / viewing archives of URLs that contain malicious JS" in html
|
|
assert "PERMISSIONS" in html
|
|
assert "/admin/machine/machine/current/change/" in html
|
|
assert "setup_wizard.css" in html
|
|
assert "setup_wizard.js?v=20260723-2" in html
|
|
|
|
|
|
def test_setup_wizard_assets_enforce_selection_and_access_requirements():
|
|
assert "border-color:#15803d; background:#f0fdf4" in SETUP_WIZARD_CSS
|
|
assert "accent-color:#15803d" in SETUP_WIZARD_CSS
|
|
assert "#archivebox-setup-title code { font-size:inherit; line-height:inherit; }" in SETUP_WIZARD_CSS
|
|
|
|
assert "updateQuestionDefaults" not in SETUP_WIZARD_JS
|
|
assert "canonicalHost" not in SETUP_WIZARD_JS
|
|
assert "Browser URL matches BASE_URL." in SETUP_WIZARD_JS
|
|
assert "Browser URL matches admin.BASE_URL as expected." in SETUP_WIZARD_JS
|
|
assert "Browser URL does not match BASE_URL" in SETUP_WIZARD_JS
|
|
assert "saved URLs may contain private share tokens or other secrets." in SETUP_WIZARD_JS
|
|
assert "archive intranet URLs" in SETUP_WIZARD_JS
|
|
assert "tlsMode === 'single' && dnsMode !== 'single'" in SETUP_WIZARD_JS
|
|
assert "Single-domain HTTPS is only allowed with Single-domain DNS." in SETUP_WIZARD_JS
|
|
assert "expectedBrowserOrigin: usesSubdomains ? adminOrigin : parsed.origin" in SETUP_WIZARD_JS
|
|
assert "Waiting for a matching browser URL and valid setup options" in SETUP_WIZARD_JS
|
|
assert "Finish the selected DNS, ingress, and TLS setup" in SETUP_WIZARD_JS
|
|
|
|
for target in ("adminUrl", "apiUrl", "indexUrl", "snapshotHealthUrl", "originalHealthUrl", "wildcardHealthUrl"):
|
|
assert f"probeUrl(preview.{target}" in SETUP_WIZARD_JS
|
|
assert "probeUrl(webOrigin + '/web/https://example.com'" not in SETUP_WIZARD_JS
|
|
assert "credentials: 'omit'" in SETUP_WIZARD_JS
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("request_host", "secure", "expected_display_host"),
|
|
(
|
|
("admin.archivebox.localhost:8000", False, "archivebox.localhost:8000"),
|
|
("127.0.0.1:8000", False, "archivebox.localhost:8000"),
|
|
("0.0.0.0:8000", False, "archivebox.localhost:8000"),
|
|
("archivebox.io:80", False, "archivebox.io"),
|
|
("archivebox.io:443", True, "archivebox.io"),
|
|
("192.0.2.10:443", True, "192.0.2.10"),
|
|
),
|
|
)
|
|
def test_unconfigured_banner_displays_canonical_url_host(request_host, secure, expected_display_host):
|
|
request = RequestFactory().get("/admin/", secure=secure, HTTP_HOST=request_host)
|
|
request.user = AnonymousUser()
|
|
|
|
context = get_setup_wizard_context(request, get_config(include_machine=False))
|
|
|
|
assert context["display_host"] == expected_display_host
|
|
|
|
|
|
def test_unconfigured_banner_does_not_show_setup_wizard_to_non_superusers():
|
|
html = render_to_string(
|
|
"core/system_warnings_banner.html",
|
|
{
|
|
"mode": "unconfigured",
|
|
"can_configure": False,
|
|
"suggested_base_url": "http://archivebox.example.test:8000",
|
|
},
|
|
)
|
|
|
|
assert 'id="archivebox-setup-wizard"' not in html
|
|
assert html.count('id="archivebox-system-warning-banner"') == 1
|
|
assert "Ask an ArchiveBox superuser to finish server setup" in html
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("context", "expected_text"),
|
|
(
|
|
({"mode": "low_disk", "free_gb": "0.50"}, "Only <code"),
|
|
({"mode": "high_memory", "mem_pct": "97.0"}, "Virtual memory at"),
|
|
({"mode": "high_load", "load_15": "8.0", "load_threshold": 6, "cpu_count": 2}, "15-min loadavg"),
|
|
({"mode": "unsafe"}, "ArchiveBox single-domain mode"),
|
|
),
|
|
)
|
|
def test_system_warning_modes_share_one_banner(context, expected_text):
|
|
html = render_to_string("core/system_warnings_banner.html", context)
|
|
|
|
assert html.count('id="archivebox-system-warning-banner"') == 1
|
|
assert expected_text in html
|
|
|
|
|
|
def test_configured_base_url_mismatch_banner_shows_both_origins():
|
|
config = get_config(include_machine=False).model_copy(update={"BASE_URL": "https://archivebox.example.test"})
|
|
request = RequestFactory().get("/admin/", HTTP_HOST="archivebox.internal:8000")
|
|
request.user = AnonymousUser()
|
|
|
|
context = get_base_url_mismatch_context(request, config)
|
|
assert context == {
|
|
"mode": "base_url_mismatch",
|
|
"browser_url": "http://archivebox.internal:8000",
|
|
"configured_base_url": "https://archivebox.example.test",
|
|
}
|
|
assert system_warnings_banner({"CONFIG": config, "request": request}) == context
|
|
|
|
html = render_to_string("core/system_warnings_banner.html", context)
|
|
assert "base_url mismatch" in html
|
|
assert "Browser URL:" in html
|
|
assert "http://archivebox.internal:8000" in html
|
|
assert "Configured BASE_URL:" in html
|
|
assert "https://archivebox.example.test" in html
|
|
|
|
|
|
def test_configured_base_url_accepts_its_isolated_admin_subdomain():
|
|
config = get_config(include_machine=False).model_copy(
|
|
update={"BASE_URL": "https://archivebox.example.test", "SERVER_SECURITY_MODE": "safe-subdomains-fullreplay"},
|
|
)
|
|
request = RequestFactory().get("/admin/", secure=True, HTTP_HOST="admin.archivebox.example.test")
|
|
request.user = AnonymousUser()
|
|
|
|
assert get_base_url_mismatch_context(request, config) is None
|
|
|
|
|
|
def test_configured_onedomain_base_url_warns_on_admin_alias():
|
|
config = get_config(include_machine=False).model_copy(
|
|
update={"BASE_URL": "https://archivebox.example.test", "SERVER_SECURITY_MODE": "safe-onedomain-nojsreplay"},
|
|
)
|
|
request = RequestFactory().get("/admin/", secure=True, HTTP_HOST="admin.archivebox.example.test")
|
|
request.user = AnonymousUser()
|
|
|
|
assert get_base_url_mismatch_context(request, config)["mode"] == "base_url_mismatch"
|
|
|
|
|
|
def test_auto_onedomain_mode_is_not_reported_as_unsafe():
|
|
config = get_config(include_machine=False).model_copy(
|
|
update={"BASE_URL": "http://archivebox.example.test", "SERVER_SECURITY_MODE": "auto"},
|
|
)
|
|
|
|
assert config.USES_SUBDOMAIN_ROUTING is False
|
|
assert system_warnings_banner({"CONFIG": config})["mode"] != "unsafe"
|