fieldwitness/tests/test_killswitch_coverage.py
Aaron D. Lee 16318daea3
Some checks failed
CI / lint (push) Failing after 12s
CI / typecheck (push) Failing after 12s
Add comprehensive test suite: integration tests + Playwright e2e
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>
2026-04-02 20:22:12 -04:00

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