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
203 lines
6.1 KiB
Python
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"))
|