Integration tests (350 passing): - test_evidence_summary.py: HTML/PDF generation, XSS safety, anchor rendering - test_tor.py: Tor module unit tests (mocked, no Tor needed) - test_c2pa_importer.py: Import result dataclass, trust evaluation, graceful degradation - test_file_attestation.py: All file types (PNG, PDF, CSV, empty, large), determinism - test_paths.py: Registry correctness, env var override, all paths under BASE_DIR - test_killswitch_coverage.py: Tor keys, trusted keys, carrier history destruction Playwright e2e infrastructure: - tests/e2e/ with conftest (live server, auth fixtures), helpers (test file generators) - test_auth.py: Setup flow, login/logout, protected routes - test_attest.py: Image/PDF/CSV attestation, verify, attestation log - test_dropbox.py: Token creation, source upload, branding check - test_keys.py: Identity display, trust store - test_fieldkit.py: Status dashboard, killswitch page - test_navigation.py: All nav links, responsive layout Run: pytest (unit/integration) or pytest -m e2e tests/e2e/ (browser) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
245 lines
9.3 KiB
Python
245 lines
9.3 KiB
Python
"""Verify the killswitch covers the new paths added in v0.3.0.
|
|
|
|
These tests inspect the execution plan of execute_purge() by running it against
|
|
a populated temporary directory and asserting that the relevant step names
|
|
appear in PurgeResult.steps_completed.
|
|
|
|
Each test is independent and uses its own tmp_path fixture, following the same
|
|
pattern as test_killswitch.py.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
|
from cryptography.hazmat.primitives.serialization import (
|
|
Encoding,
|
|
NoEncryption,
|
|
PrivateFormat,
|
|
PublicFormat,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixture
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture()
|
|
def populated_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
|
"""Create a minimal populated .fieldwitness directory for killswitch tests."""
|
|
import fieldwitness.paths as paths
|
|
|
|
data_dir = tmp_path / ".fieldwitness"
|
|
data_dir.mkdir()
|
|
monkeypatch.setattr(paths, "BASE_DIR", data_dir)
|
|
|
|
# Identity
|
|
identity_dir = data_dir / "identity"
|
|
identity_dir.mkdir()
|
|
key = Ed25519PrivateKey.generate()
|
|
priv_pem = key.private_bytes(Encoding.PEM, PrivateFormat.PKCS8, NoEncryption())
|
|
(identity_dir / "private.pem").write_bytes(priv_pem)
|
|
pub_pem = key.public_key().public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)
|
|
(identity_dir / "public.pem").write_bytes(pub_pem)
|
|
|
|
# Channel key
|
|
stego_dir = data_dir / "stego"
|
|
stego_dir.mkdir()
|
|
(stego_dir / "channel.key").write_text("channel-key-material")
|
|
|
|
# Trusted keys directory with a dummy collaborator key
|
|
trusted_dir = data_dir / "trusted_keys"
|
|
trusted_dir.mkdir()
|
|
fp_dir = trusted_dir / "aabbcc112233"
|
|
fp_dir.mkdir()
|
|
(fp_dir / "public.pem").write_bytes(pub_pem)
|
|
(fp_dir / "meta.json").write_text('{"alias": "Alice"}')
|
|
|
|
# Carrier history
|
|
(data_dir / "carrier_history.json").write_text('{"carriers": []}')
|
|
|
|
# Tor hidden service directory with a dummy key
|
|
tor_dir = data_dir / "fieldkit" / "tor" / "hidden_service"
|
|
tor_dir.mkdir(parents=True)
|
|
(tor_dir / "hs_ed25519_secret_key").write_text("ED25519-V3:fakekeydata")
|
|
|
|
# Flask instance secret
|
|
instance_dir = data_dir / "instance"
|
|
instance_dir.mkdir()
|
|
(instance_dir / ".secret_key").write_bytes(b"flask-secret")
|
|
|
|
# Auth DB
|
|
auth_dir = data_dir / "auth"
|
|
auth_dir.mkdir()
|
|
(auth_dir / "fieldwitness.db").write_bytes(b"sqlite3 db")
|
|
|
|
# Attestations
|
|
att_dir = data_dir / "attestations"
|
|
att_dir.mkdir()
|
|
(att_dir / "log.bin").write_bytes(b"attestation data")
|
|
|
|
# Chain
|
|
chain_dir = data_dir / "chain"
|
|
chain_dir.mkdir()
|
|
(chain_dir / "chain.bin").write_bytes(b"chain data")
|
|
|
|
# Temp
|
|
temp_dir = data_dir / "temp"
|
|
temp_dir.mkdir()
|
|
(temp_dir / "upload.tmp").write_bytes(b"temp file")
|
|
|
|
# Config
|
|
(data_dir / "config.json").write_text("{}")
|
|
|
|
return data_dir
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_killswitch_covers_tor_keys
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestKillswitchCoversTorKeys:
|
|
def test_tor_key_step_in_keys_only_plan(self, populated_dir: Path):
|
|
"""KEYS_ONLY purge must include destroy_tor_hidden_service_key."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
assert "destroy_tor_hidden_service_key" in result.steps_completed
|
|
|
|
def test_tor_key_step_in_all_plan(self, populated_dir: Path):
|
|
"""ALL purge must also include destroy_tor_hidden_service_key."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.ALL, reason="test")
|
|
|
|
assert "destroy_tor_hidden_service_key" in result.steps_completed
|
|
|
|
def test_tor_hidden_service_dir_destroyed_by_keys_only(self, populated_dir: Path):
|
|
"""The actual directory on disk must be gone after KEYS_ONLY purge."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
tor_dir = populated_dir / "fieldkit" / "tor" / "hidden_service"
|
|
assert tor_dir.exists(), "Test setup: tor hidden service dir must exist before purge"
|
|
|
|
execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
assert not tor_dir.exists(), (
|
|
"Tor hidden service key directory must be destroyed by KEYS_ONLY purge"
|
|
)
|
|
|
|
def test_tor_key_step_runs_before_data_steps(self, populated_dir: Path):
|
|
"""Tor key destruction must precede data-layer steps (ordered destruction)."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.ALL, reason="test")
|
|
|
|
completed = result.steps_completed
|
|
tor_idx = completed.index("destroy_tor_hidden_service_key")
|
|
# Attestation log and chain are data steps; they should come after key steps.
|
|
if "destroy_attestation_log" in completed:
|
|
att_idx = completed.index("destroy_attestation_log")
|
|
assert tor_idx < att_idx, (
|
|
"Tor key must be destroyed before attestation log in the ordered plan"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_killswitch_covers_trusted_keys
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestKillswitchCoversTrustedKeys:
|
|
def test_trusted_keys_step_in_keys_only_plan(self, populated_dir: Path):
|
|
"""KEYS_ONLY purge must include destroy_trusted_keys."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
assert "destroy_trusted_keys" in result.steps_completed
|
|
|
|
def test_trusted_keys_step_in_all_plan(self, populated_dir: Path):
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.ALL, reason="test")
|
|
|
|
assert "destroy_trusted_keys" in result.steps_completed
|
|
|
|
def test_trusted_keys_dir_destroyed_by_keys_only(self, populated_dir: Path):
|
|
"""The trusted_keys directory must be gone after KEYS_ONLY purge."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
trusted_dir = populated_dir / "trusted_keys"
|
|
assert trusted_dir.exists(), "Test setup: trusted_keys dir must exist before purge"
|
|
|
|
execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
assert not trusted_dir.exists(), (
|
|
"trusted_keys directory must be destroyed by KEYS_ONLY purge"
|
|
)
|
|
|
|
def test_trusted_keys_destroyed_recursively(self, populated_dir: Path):
|
|
"""Sub-directories with per-key material must also be gone."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
key_subdir = populated_dir / "trusted_keys" / "aabbcc112233"
|
|
assert key_subdir.exists()
|
|
|
|
execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
assert not key_subdir.exists()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# test_killswitch_covers_carrier_history
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestKillswitchCoversCarrierHistory:
|
|
def test_carrier_history_step_in_all_plan(self, populated_dir: Path):
|
|
"""ALL purge must include destroy_carrier_history."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.ALL, reason="test")
|
|
|
|
assert "destroy_carrier_history" in result.steps_completed
|
|
|
|
def test_carrier_history_file_destroyed_by_all(self, populated_dir: Path):
|
|
"""The carrier_history.json file must be gone after ALL purge."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
carrier_file = populated_dir / "carrier_history.json"
|
|
assert carrier_file.exists(), "Test setup: carrier_history.json must exist before purge"
|
|
|
|
execute_purge(PurgeScope.ALL, reason="test")
|
|
|
|
assert not carrier_file.exists(), (
|
|
"carrier_history.json must be destroyed by ALL purge"
|
|
)
|
|
|
|
def test_carrier_history_not_destroyed_by_keys_only(self, populated_dir: Path):
|
|
"""KEYS_ONLY purge must NOT destroy carrier_history — it is not key material."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
carrier_file = populated_dir / "carrier_history.json"
|
|
|
|
execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
# carrier_history is a data file, not key material — KEYS_ONLY preserves it.
|
|
assert carrier_file.exists(), (
|
|
"carrier_history.json must be preserved by KEYS_ONLY purge "
|
|
"(it is not key material)"
|
|
)
|
|
|
|
def test_carrier_history_step_absent_from_keys_only_plan(self, populated_dir: Path):
|
|
"""destroy_carrier_history must not appear in KEYS_ONLY completed steps."""
|
|
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
|
|
|
result = execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
|
|
|
assert "destroy_carrier_history" not in result.steps_completed
|