Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

203 lines
6.1 KiB
Python

"""Tests for BaseExporter, ExportResult, ExportOptions, and BaseFileVerifier."""
import hashlib
import tempfile
from pathlib import Path
import pytest
from local_deep_research.exporters.base import (
BaseExporter,
ExportOptions,
ExportResult,
)
from local_deep_research.security.file_integrity.base_verifier import (
BaseFileVerifier,
FileType,
)
# --- Concrete subclasses for testing abstract bases ---
class StubExporter(BaseExporter):
@property
def format_name(self) -> str:
return "stub"
@property
def file_extension(self) -> str:
return ".stub"
@property
def mimetype(self) -> str:
return "application/x-stub"
def export(self, markdown_content, options=None):
return ExportResult(
content=markdown_content.encode(),
filename="out.stub",
mimetype=self.mimetype,
)
class StubVerifier(BaseFileVerifier):
def should_verify(self, file_path: Path) -> bool:
return file_path.suffix == ".stub"
def get_file_type(self) -> FileType:
return FileType.EXPORT
def allows_modifications(self) -> bool:
return False
# --- ExportResult tests ---
class TestExportResult:
def test_fields(self):
result = ExportResult(
content=b"hello", filename="f.txt", mimetype="text/plain"
)
assert result.content == b"hello"
assert result.filename == "f.txt"
assert result.mimetype == "text/plain"
# --- ExportOptions tests ---
class TestExportOptions:
def test_defaults(self):
opts = ExportOptions()
assert opts.title is None
assert opts.metadata is None
assert opts.custom_options == {}
def test_with_values(self):
opts = ExportOptions(
title="My Report",
metadata={"author": "Alice"},
custom_options={"css": "body{}"},
)
assert opts.title == "My Report"
assert opts.metadata == {"author": "Alice"}
assert opts.custom_options == {"css": "body{}"}
# --- BaseExporter._validate_content_size tests ---
class TestValidateContentSize:
def test_under_limit_no_error(self):
exporter = StubExporter()
exporter._validate_content_size("short content")
def test_over_limit_raises(self):
exporter = StubExporter()
oversized = "x" * (exporter.MAX_CONTENT_SIZE + 1)
with pytest.raises(ValueError, match="exceeds maximum size"):
exporter._validate_content_size(oversized)
def test_exactly_at_limit_no_error(self):
exporter = StubExporter()
exact = "x" * exporter.MAX_CONTENT_SIZE
exporter._validate_content_size(exact)
# --- BaseExporter._generate_safe_filename tests ---
class TestGenerateSafeFilename:
def test_with_title(self):
exporter = StubExporter()
filename = exporter._generate_safe_filename("My Report")
assert filename == "My_Report.stub"
def test_without_title(self):
exporter = StubExporter()
assert exporter._generate_safe_filename(None) == "research_report.stub"
assert exporter._generate_safe_filename("") == "research_report.stub"
def test_special_chars_stripped(self):
exporter = StubExporter()
filename = exporter._generate_safe_filename("Hello! @World# (2024)")
# Only word chars, spaces, hyphens survive; spaces become underscores
assert "!" not in filename
assert "@" not in filename
assert "#" not in filename
assert filename.endswith(".stub")
def test_long_title_truncated(self):
exporter = StubExporter()
long_title = "A" * 100
filename = exporter._generate_safe_filename(long_title)
# safe_title is truncated to 50 chars, plus extension
assert filename == "A" * 50 + ".stub"
# --- BaseExporter._prepend_title_if_needed tests ---
class TestPrependTitleIfNeeded:
def test_no_title_returns_content(self):
exporter = StubExporter()
content = "Some body text"
assert exporter._prepend_title_if_needed(content, None) == content
assert exporter._prepend_title_if_needed(content, "") == content
def test_content_starts_with_same_title(self):
exporter = StubExporter()
content = "# My Title\n\nBody text"
result = exporter._prepend_title_if_needed(content, "My Title")
assert result == content # unchanged
def test_content_starts_with_different_heading(self):
exporter = StubExporter()
content = "## Section One\n\nBody text"
result = exporter._prepend_title_if_needed(content, "My Title")
# Has a heading already, so title is NOT prepended
assert result == content
def test_content_without_heading_gets_title(self):
exporter = StubExporter()
content = "Just some plain text"
result = exporter._prepend_title_if_needed(content, "My Title")
assert result == "# My Title\n\nJust some plain text"
def test_content_with_leading_whitespace_and_heading(self):
exporter = StubExporter()
content = " # Existing Heading\n\nBody"
result = exporter._prepend_title_if_needed(content, "New Title")
# lstrip() finds '#', so title is NOT prepended
assert result == content
# --- BaseFileVerifier tests ---
class TestBaseFileVerifier:
def test_calculate_checksum(self):
verifier = StubVerifier()
with tempfile.NamedTemporaryFile(delete=False, suffix=".stub") as f:
f.write(b"test data for checksum")
f.flush()
tmp_path = Path(f.name)
try:
result = verifier.calculate_checksum(tmp_path)
expected = hashlib.sha256(b"test data for checksum").hexdigest()
assert result == expected
finally:
tmp_path.unlink()
def test_get_algorithm(self):
verifier = StubVerifier()
assert verifier.get_algorithm() == "sha256"
def test_checksum_file_not_found(self):
verifier = StubVerifier()
with pytest.raises(FileNotFoundError):
verifier.calculate_checksum(Path("/nonexistent/file.stub"))