mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-09-13 18:46:17 +05:00
458 lines
15 KiB
Python
458 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for archivebox list command.
|
|
Verify list emits snapshot JSONL and applies the documented filters.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
|
|
import pytest
|
|
from django.db import connection
|
|
from django.utils import timezone
|
|
|
|
from archivebox.core.models import Snapshot
|
|
from archivebox.tests.conftest import create_test_url, parse_jsonl_output, run_archivebox_cmd, run_queued_crawls, cli_env
|
|
|
|
from archivebox.tests.test_orm_helpers import use_archivebox_db
|
|
|
|
pytestmark = pytest.mark.django_db(transaction=True)
|
|
|
|
|
|
class CountingStdout:
|
|
encoding = "utf-8"
|
|
|
|
def __init__(self):
|
|
self.rows = 0
|
|
self._pending = ""
|
|
|
|
def isatty(self):
|
|
return False
|
|
|
|
def write(self, text):
|
|
self._pending += text
|
|
lines = self._pending.split("\n")
|
|
self._pending = lines.pop()
|
|
self.rows += sum(1 for line in lines if line.startswith("{"))
|
|
return len(text)
|
|
|
|
def flush(self):
|
|
return None
|
|
|
|
|
|
def test_list_limit_zero_streams_one_million_snapshots_without_materializing(admin_user, monkeypatch):
|
|
"""Regression: archivebox list --limit=0 must stream unbounded result sets."""
|
|
from archivebox.cli.archivebox_snapshot import list_snapshots
|
|
from archivebox.crawls.models import Crawl
|
|
|
|
crawl = Crawl.objects.create(
|
|
urls="https://example.com",
|
|
created_by=admin_user,
|
|
status=Crawl.StatusChoices.SEALED,
|
|
retry_at=None,
|
|
)
|
|
now = timezone.now().isoformat()
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(
|
|
"""
|
|
WITH RECURSIVE seq(n) AS (
|
|
SELECT 1
|
|
UNION ALL
|
|
SELECT n + 1 FROM seq WHERE n < 1000000
|
|
)
|
|
INSERT INTO core_snapshot (
|
|
id,
|
|
url,
|
|
timestamp,
|
|
title,
|
|
bookmarked_at,
|
|
created_at,
|
|
modified_at,
|
|
downloaded_at,
|
|
fs_version,
|
|
crawl_id,
|
|
config,
|
|
current_step,
|
|
depth,
|
|
notes,
|
|
num_uses_failed,
|
|
num_uses_succeeded,
|
|
retry_at,
|
|
status,
|
|
delete_at,
|
|
output_size,
|
|
parent_snapshot_id
|
|
)
|
|
SELECT
|
|
lower(hex(randomblob(16))),
|
|
'https://example.com/page-' || n,
|
|
printf('9%031d', n),
|
|
'',
|
|
%s,
|
|
%s,
|
|
%s,
|
|
NULL,
|
|
'0.9.0',
|
|
%s,
|
|
'{}',
|
|
0,
|
|
0,
|
|
'',
|
|
0,
|
|
0,
|
|
NULL,
|
|
'sealed',
|
|
NULL,
|
|
0,
|
|
NULL
|
|
FROM seq
|
|
""",
|
|
[now, now, now, str(crawl.id).replace("-", "")],
|
|
)
|
|
|
|
stdout = CountingStdout()
|
|
monkeypatch.setattr(sys, "stdout", stdout)
|
|
|
|
assert list_snapshots(limit=0) == 0
|
|
assert stdout.rows == 1000000
|
|
|
|
|
|
def test_list_outputs_existing_snapshots_as_jsonl(initialized_archive):
|
|
"""Test that list prints one JSON object per stored snapshot."""
|
|
env = cli_env(disable_extractors=True)
|
|
for url in ["https://example.com", "https://iana.org"]:
|
|
run_archivebox_cmd(
|
|
["add", "--index-only", "--depth=0", url],
|
|
env=env,
|
|
check=True,
|
|
)
|
|
run_queued_crawls(initialized_archive, env)
|
|
|
|
result = run_archivebox_cmd(
|
|
["list"],
|
|
timeout=30,
|
|
)
|
|
|
|
rows = parse_jsonl_output(result.stdout)
|
|
urls = {row["url"] for row in rows}
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert "https://example.com" in urls
|
|
assert "https://iana.org" in urls
|
|
|
|
|
|
def test_list_filters_by_url_icontains(initialized_archive):
|
|
"""Test that list --url__icontains returns only matching snapshots."""
|
|
env = cli_env(disable_extractors=True)
|
|
for url in ["https://example.com", "https://iana.org"]:
|
|
run_archivebox_cmd(
|
|
["add", "--index-only", "--depth=0", url],
|
|
env=env,
|
|
check=True,
|
|
)
|
|
run_queued_crawls(initialized_archive, env)
|
|
|
|
result = run_archivebox_cmd(
|
|
["list", "--url__icontains", "example.com"],
|
|
timeout=30,
|
|
)
|
|
|
|
rows = parse_jsonl_output(result.stdout)
|
|
assert result.returncode == 0, result.stderr
|
|
assert len(rows) == 1
|
|
assert rows[0]["url"] == "https://example.com"
|
|
|
|
|
|
def test_list_filters_by_crawl_id_and_limit(initialized_archive):
|
|
"""Test that crawl-id and limit filters constrain the result set."""
|
|
env = cli_env(disable_extractors=True)
|
|
for url in ["https://example.com", "https://iana.org"]:
|
|
run_archivebox_cmd(
|
|
["add", "--index-only", "--depth=0", url],
|
|
env=env,
|
|
check=True,
|
|
)
|
|
run_queued_crawls(initialized_archive, env)
|
|
|
|
with use_archivebox_db(initialized_archive):
|
|
crawl_id = str(Snapshot.objects.values_list("crawl_id", flat=True).get(url="https://example.com"))
|
|
|
|
result = run_archivebox_cmd(
|
|
["list", "--crawl-id", crawl_id, "--limit", "1"],
|
|
timeout=30,
|
|
)
|
|
|
|
rows = parse_jsonl_output(result.stdout)
|
|
assert result.returncode == 0, result.stderr
|
|
assert len(rows) == 1
|
|
assert rows[0]["crawl_id"].replace("-", "") == crawl_id.replace("-", "")
|
|
assert rows[0]["url"] == "https://example.com"
|
|
|
|
|
|
def test_list_filters_by_status(initialized_archive):
|
|
"""Test that list can filter using the current snapshot status."""
|
|
env = cli_env(disable_extractors=True)
|
|
run_archivebox_cmd(
|
|
["add", "--index-only", "--depth=0", "https://example.com"],
|
|
env=env,
|
|
check=True,
|
|
)
|
|
run_queued_crawls(initialized_archive, env)
|
|
|
|
with use_archivebox_db(initialized_archive):
|
|
status = Snapshot.objects.values_list("status", flat=True).get()
|
|
|
|
result = run_archivebox_cmd(
|
|
["list", "--status", status],
|
|
timeout=30,
|
|
)
|
|
|
|
rows = parse_jsonl_output(result.stdout)
|
|
assert result.returncode == 0, result.stderr
|
|
assert len(rows) == 1
|
|
assert rows[0]["status"] == status
|
|
|
|
|
|
def test_list_help_lists_filter_options(initialized_archive):
|
|
"""Test that list --help documents the supported filter flags."""
|
|
|
|
result = run_archivebox_cmd(
|
|
["list", "--help"],
|
|
timeout=30,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
assert "--url__icontains" in result.stdout
|
|
assert "--crawl-id" in result.stdout
|
|
assert "--limit" in result.stdout
|
|
assert "--search" in result.stdout
|
|
assert "--json" in result.stdout
|
|
assert "--html" in result.stdout
|
|
assert "--with-headers" in result.stdout
|
|
|
|
|
|
def test_list_allows_sort_with_limit(initialized_archive):
|
|
"""Test that list can sort and then apply limit without queryset slicing errors."""
|
|
env = cli_env(disable_extractors=True)
|
|
for url in ["https://example.com", "https://iana.org", "https://example.net"]:
|
|
run_archivebox_cmd(
|
|
["add", "--index-only", "--depth=0", url],
|
|
env=env,
|
|
check=True,
|
|
)
|
|
run_queued_crawls(initialized_archive, env)
|
|
|
|
result = run_archivebox_cmd(
|
|
["list", "--limit", "2", "--sort", "-created_at"],
|
|
timeout=30,
|
|
)
|
|
|
|
rows = parse_jsonl_output(result.stdout)
|
|
assert result.returncode == 0, result.stderr
|
|
assert len(rows) == 2
|
|
|
|
|
|
def test_snapshot_list_search_meta(initialized_archive):
|
|
"""snapshot list should support metadata search mode."""
|
|
url = create_test_url(domain="meta-search-example.com")
|
|
run_archivebox_cmd(["snapshot", "create", url], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
|
|
_cmd_result = run_archivebox_cmd(
|
|
["snapshot", "list", "--search=meta", "meta-search-example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0, f"Command failed: {stderr}"
|
|
records = parse_jsonl_output(stdout)
|
|
assert len(records) == 1
|
|
assert "meta-search-example.com" in records[0]["url"]
|
|
|
|
|
|
def test_list_search_meta_matches_metadata(initialized_archive):
|
|
"""top-level list --search=meta should apply metadata search to the queryset."""
|
|
url = create_test_url(domain="top-level-meta-search-example.com")
|
|
run_archivebox_cmd(["snapshot", "create", url], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
|
|
_cmd_result = run_archivebox_cmd(
|
|
["list", "--search=meta", "top-level-meta-search-example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0, f"Command failed: {stderr}"
|
|
records = parse_jsonl_output(stdout)
|
|
assert len(records) == 1
|
|
assert "top-level-meta-search-example.com" in records[0]["url"]
|
|
|
|
|
|
def test_search_command_finds_snapshots(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
_cmd_result = run_archivebox_cmd(["search", "example"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0, stderr
|
|
assert "example" in stdout
|
|
|
|
|
|
def test_search_command_returns_no_results_for_missing_term(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
_cmd_result = run_archivebox_cmd(
|
|
["search", "nonexistentterm12345"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
_stdout, _stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code in [0, 1]
|
|
|
|
|
|
def test_search_command_on_empty_archive(initialized_archive):
|
|
_cmd_result = run_archivebox_cmd(["search", "anything"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
_stdout, _stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code in [0, 1]
|
|
|
|
|
|
def test_search_command_outputs_matching_snapshots_as_jsonl(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
_cmd_result = run_archivebox_cmd(["search"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0, stderr
|
|
records = parse_jsonl_output(stdout)
|
|
assert any("example.com" in row.get("url", "") for row in records)
|
|
|
|
|
|
def test_search_command_json_outputs_matching_snapshots(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
result = run_archivebox_cmd(["search", "--json"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
payload = json.loads(result.stdout)
|
|
assert any("example.com" in row.get("url", "") for row in payload)
|
|
|
|
|
|
def test_search_command_json_with_headers_wraps_links_payload(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
result = run_archivebox_cmd(
|
|
["search", "--json", "--with-headers"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
payload = json.loads(result.stdout)
|
|
assert "links" in payload
|
|
assert any("example.com" in row.get("url", "") for row in payload["links"])
|
|
|
|
|
|
def test_search_command_html_outputs_markup(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
result = run_archivebox_cmd(["search", "--html"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
assert "<" in result.stdout
|
|
assert "example.com" in result.stdout
|
|
|
|
|
|
def test_search_command_csv_outputs_requested_column(initialized_archive):
|
|
run_archivebox_cmd(
|
|
["snapshot", "create", "https://example.com"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
|
|
_cmd_result = run_archivebox_cmd(
|
|
["search", "--csv", "url", "--with-headers"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0, stderr
|
|
assert "url" in stdout
|
|
assert "example.com" in stdout
|
|
|
|
|
|
def test_search_command_with_headers_requires_structured_output_format(initialized_archive):
|
|
_cmd_result = run_archivebox_cmd(["search", "--with-headers"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
_stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code != 0
|
|
assert "requires" in stderr.lower()
|
|
assert "json" in stderr.lower()
|
|
|
|
|
|
def test_search_command_sort_option_runs_successfully(initialized_archive):
|
|
for url in ["https://iana.org", "https://example.com"]:
|
|
run_archivebox_cmd(["snapshot", "create", url], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
|
|
_cmd_result = run_archivebox_cmd(
|
|
["search", "--csv", "url", "--sort=url"],
|
|
cwd=initialized_archive,
|
|
default_cli_env=True,
|
|
disable_extractors=True,
|
|
)
|
|
stdout, stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0, stderr
|
|
assert "example.com" in stdout or "iana.org" in stdout
|
|
|
|
|
|
def test_search_command_help_lists_supported_filters(initialized_archive):
|
|
_cmd_result = run_archivebox_cmd(["search", "--help"], cwd=initialized_archive, default_cli_env=True, disable_extractors=True)
|
|
stdout, _stderr, code = _cmd_result.stdout, _cmd_result.stderr, _cmd_result.returncode
|
|
|
|
assert code == 0
|
|
assert "--url__icontains" in stdout
|
|
assert "--crawl-id" in stdout
|
|
assert "--status" in stdout
|
|
assert "--sort" in stdout
|
|
assert "--json" in stdout
|
|
assert "--html" in stdout
|