"""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