Rebrand SooSeF to FieldWitness
Complete project rebrand for better positioning in the press freedom and digital security space. FieldWitness communicates both field deployment and evidence testimony — appropriate for the target audience of journalists, NGOs, and human rights organizations. Rename mapping: - soosef → fieldwitness (package, CLI, all imports) - soosef.stegasoo → fieldwitness.stego - soosef.verisoo → fieldwitness.attest - ~/.soosef/ → ~/.fwmetadata/ (innocuous data dir name) - SOOSEF_DATA_DIR → FIELDWITNESS_DATA_DIR - SoosefConfig → FieldWitnessConfig - SoosefError → FieldWitnessError Also includes: - License switch from MIT to GPL-3.0 - C2PA bridge module (Phase 0-2 MVP): cert.py, export.py, vendor_assertions.py - README repositioned to lead with provenance/federation, stego backgrounded - Threat model skeleton at docs/security/threat-model.md - Planning docs: docs/planning/c2pa-integration.md, docs/planning/gtm-feasibility.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Shared test fixtures for SooSeF tests."""
|
||||
"""Shared test fixtures for FieldWitness tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -9,16 +9,16 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tmp_soosef_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Set SOOSEF_DATA_DIR to a temporary directory.
|
||||
def tmp_fieldwitness_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Set FIELDWITNESS_DATA_DIR to a temporary directory.
|
||||
|
||||
This must be used before importing any module that reads soosef.paths
|
||||
This must be used before importing any module that reads fieldwitness.paths
|
||||
at import time. For modules that read paths lazily (most of them),
|
||||
monkeypatching the paths module directly is more reliable.
|
||||
"""
|
||||
data_dir = tmp_path / ".soosef"
|
||||
data_dir = tmp_path / ".fieldwitness"
|
||||
data_dir.mkdir()
|
||||
monkeypatch.setenv("SOOSEF_DATA_DIR", str(data_dir))
|
||||
monkeypatch.setenv("FIELDWITNESS_DATA_DIR", str(data_dir))
|
||||
return data_dir
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from io import BytesIO
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
from soosef.verisoo.hashing import hash_image, perceptual_distance, is_same_image
|
||||
from fieldwitness.attest.hashing import hash_image, perceptual_distance, is_same_image
|
||||
|
||||
|
||||
def create_test_image(width: int = 100, height: int = 100, color: tuple = (255, 0, 0)) -> bytes:
|
||||
@@ -11,10 +11,10 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
||||
Ed25519PublicKey,
|
||||
)
|
||||
|
||||
from soosef.exceptions import ChainError, ChainIntegrityError
|
||||
from soosef.federation.chain import ChainStore
|
||||
from soosef.federation.models import ChainState
|
||||
from soosef.federation.serialization import canonical_bytes, compute_record_hash
|
||||
from fieldwitness.exceptions import ChainError, ChainIntegrityError
|
||||
from fieldwitness.federation.chain import ChainStore
|
||||
from fieldwitness.federation.models import ChainState
|
||||
from fieldwitness.federation.serialization import canonical_bytes, compute_record_hash
|
||||
|
||||
|
||||
def test_genesis_record(chain_dir: Path, private_key: Ed25519PrivateKey):
|
||||
@@ -236,10 +236,10 @@ def test_verify_chain_detects_signer_change(chain_dir: Path):
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from uuid_utils import uuid7
|
||||
|
||||
from soosef.federation.entropy import collect_entropy_witnesses
|
||||
from soosef.federation.models import AttestationChainRecord
|
||||
from soosef.federation.serialization import canonical_bytes as cb
|
||||
from soosef.federation.serialization import serialize_record
|
||||
from fieldwitness.federation.entropy import collect_entropy_witnesses
|
||||
from fieldwitness.federation.models import AttestationChainRecord
|
||||
from fieldwitness.federation.serialization import canonical_bytes as cb
|
||||
from fieldwitness.federation.serialization import serialize_record
|
||||
|
||||
state = store.state()
|
||||
prev_hash = state.head_hash
|
||||
@@ -289,7 +289,7 @@ def test_verify_chain_detects_signer_change(chain_dir: Path):
|
||||
|
||||
def test_key_rotation_in_chain(chain_dir: Path):
|
||||
"""Chain with a proper key rotation record verifies successfully."""
|
||||
from soosef.federation.chain import CONTENT_TYPE_KEY_ROTATION
|
||||
from fieldwitness.federation.chain import CONTENT_TYPE_KEY_ROTATION
|
||||
|
||||
store = ChainStore(chain_dir)
|
||||
key1 = Ed25519PrivateKey.generate()
|
||||
|
||||
@@ -14,8 +14,8 @@ from pathlib import Path
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
|
||||
|
||||
from soosef.exceptions import ChainError
|
||||
from soosef.federation.chain import MAX_RECORD_SIZE, ChainStore
|
||||
from fieldwitness.exceptions import ChainError
|
||||
from fieldwitness.federation.chain import MAX_RECORD_SIZE, ChainStore
|
||||
|
||||
|
||||
def test_concurrent_append_no_fork(chain_dir: Path):
|
||||
|
||||
@@ -26,11 +26,11 @@ from click.testing import CliRunner
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def soosef_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Redirect soosef paths to a tmp directory."""
|
||||
import soosef.paths as paths
|
||||
def fieldwitness_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Redirect fieldwitness paths to a tmp directory."""
|
||||
import fieldwitness.paths as paths
|
||||
|
||||
data_dir = tmp_path / ".soosef"
|
||||
data_dir = tmp_path / ".fieldwitness"
|
||||
data_dir.mkdir()
|
||||
monkeypatch.setattr(paths, "BASE_DIR", data_dir)
|
||||
return data_dir
|
||||
@@ -59,8 +59,8 @@ def _write_deadman_state(
|
||||
|
||||
def test_enforcement_loop_no_op_when_disarmed(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Loop should not call check() when the switch is not armed."""
|
||||
from soosef.cli import _deadman_enforcement_loop
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import _deadman_enforcement_loop
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
# Redirect the module-level DEADMAN_STATE constant so DeadmanSwitch() default is our tmp file
|
||||
state_file = tmp_path / "deadman.json"
|
||||
@@ -90,8 +90,8 @@ def test_enforcement_loop_no_op_when_disarmed(tmp_path: Path, monkeypatch: pytes
|
||||
|
||||
def test_enforcement_loop_fires_when_overdue(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Loop must call DeadmanSwitch.check() when armed and past interval + grace."""
|
||||
from soosef.cli import _deadman_enforcement_loop
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import _deadman_enforcement_loop
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
@@ -120,8 +120,8 @@ def test_enforcement_loop_fires_when_overdue(tmp_path: Path, monkeypatch: pytest
|
||||
|
||||
def test_enforcement_loop_exits_after_firing(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""After firing, the loop must return and not call check() a second time."""
|
||||
from soosef.cli import _deadman_enforcement_loop
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import _deadman_enforcement_loop
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
@@ -145,8 +145,8 @@ def test_enforcement_loop_exits_after_firing(tmp_path: Path, monkeypatch: pytest
|
||||
|
||||
def test_enforcement_loop_tolerates_exceptions(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Transient errors in check() must not kill the loop."""
|
||||
from soosef.cli import _deadman_enforcement_loop
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import _deadman_enforcement_loop
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
@@ -183,8 +183,8 @@ def test_enforcement_loop_tolerates_exceptions(tmp_path: Path, monkeypatch: pyte
|
||||
def test_start_deadman_thread_is_daemon(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Thread must be a daemon so it dies with the process."""
|
||||
# Patch the loop to exit immediately so the thread doesn't hang in tests
|
||||
import soosef.cli as cli_mod
|
||||
from soosef.cli import _start_deadman_thread
|
||||
import fieldwitness.cli as cli_mod
|
||||
from fieldwitness.cli import _start_deadman_thread
|
||||
|
||||
monkeypatch.setattr(cli_mod, "_deadman_enforcement_loop", lambda interval_seconds: None)
|
||||
|
||||
@@ -207,10 +207,10 @@ def test_check_deadman_disarmed(
|
||||
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""check-deadman exits 0 and prints helpful message when not armed."""
|
||||
from soosef.cli import main
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import main
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
# Point at an empty tmp dir so the real ~/.soosef/fieldkit/deadman.json isn't read
|
||||
# Point at an empty tmp dir so the real ~/.fieldwitness/fieldkit/deadman.json isn't read
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
|
||||
@@ -223,8 +223,8 @@ def test_check_deadman_armed_ok(
|
||||
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""check-deadman exits 0 when armed and check-in is current."""
|
||||
from soosef.cli import main
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import main
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
@@ -247,8 +247,8 @@ def test_check_deadman_overdue_in_grace(
|
||||
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""check-deadman exits 0 but prints OVERDUE warning when past interval but in grace."""
|
||||
from soosef.cli import main
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import main
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
@@ -273,8 +273,8 @@ def test_check_deadman_fires_when_expired(
|
||||
tmp_path: Path, cli_runner: CliRunner, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""check-deadman exits 2 when the switch has fully expired."""
|
||||
from soosef.cli import main
|
||||
from soosef.fieldkit import deadman as deadman_mod
|
||||
from fieldwitness.cli import main
|
||||
from fieldwitness.fieldkit import deadman as deadman_mod
|
||||
|
||||
state_file = tmp_path / "deadman.json"
|
||||
monkeypatch.setattr(deadman_mod, "_paths", type("P", (), {"DEADMAN_STATE": state_file}))
|
||||
|
||||
@@ -7,11 +7,11 @@ from pathlib import Path
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
import soosef.paths as _paths
|
||||
from soosef.cli import main
|
||||
from soosef.exceptions import KeystoreError
|
||||
from soosef.keystore.manager import KeystoreManager
|
||||
from soosef.keystore.models import RotationResult
|
||||
import fieldwitness.paths as _paths
|
||||
from fieldwitness.cli import main
|
||||
from fieldwitness.exceptions import KeystoreError
|
||||
from fieldwitness.keystore.manager import KeystoreManager
|
||||
from fieldwitness.keystore.models import RotationResult
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
@@ -21,7 +21,7 @@ from soosef.keystore.models import RotationResult
|
||||
def _make_manager(tmp_path: Path) -> KeystoreManager:
|
||||
"""Return a KeystoreManager pointing at isolated temp directories."""
|
||||
identity_dir = tmp_path / "identity"
|
||||
channel_key_file = tmp_path / "stegasoo" / "channel.key"
|
||||
channel_key_file = tmp_path / "stego" / "channel.key"
|
||||
return KeystoreManager(identity_dir=identity_dir, channel_key_file=channel_key_file)
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ class TestRotateChannelKey:
|
||||
ks.rotate_channel_key()
|
||||
|
||||
def test_raises_for_env_var_key(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
monkeypatch.setenv("STEGASOO_CHANNEL_KEY", "a" * 64)
|
||||
monkeypatch.setenv("FIELDWITNESS_CHANNEL_KEY", "a" * 64)
|
||||
ks = _make_manager(tmp_path)
|
||||
# has_channel_key() returns True (env), but file doesn't exist
|
||||
with pytest.raises(KeystoreError, match="environment variable"):
|
||||
@@ -182,7 +182,7 @@ class TestRotateChannelKey:
|
||||
assert oct(key_file.stat().st_mode & 0o777) == oct(0o600)
|
||||
|
||||
def test_archived_key_matches_old_fingerprint(self, tmp_path: Path):
|
||||
from soosef.stegasoo.crypto import get_channel_fingerprint
|
||||
from fieldwitness.stego.crypto import get_channel_fingerprint
|
||||
|
||||
ks = _make_manager(tmp_path)
|
||||
ks.generate_channel_key()
|
||||
@@ -197,7 +197,7 @@ class TestRotateChannelKey:
|
||||
assert old_fp == result.old_fingerprint
|
||||
|
||||
def test_new_channel_key_active_after_rotation(self, tmp_path: Path):
|
||||
from soosef.stegasoo.crypto import get_channel_fingerprint
|
||||
from fieldwitness.stego.crypto import get_channel_fingerprint
|
||||
|
||||
ks = _make_manager(tmp_path)
|
||||
ks.generate_channel_key()
|
||||
@@ -226,14 +226,14 @@ class TestRotateChannelKey:
|
||||
|
||||
|
||||
class TestRotateCLI:
|
||||
def _init_soosef(self, tmp_path: Path) -> Path:
|
||||
def _init_fieldwitness(self, tmp_path: Path) -> Path:
|
||||
"""Create the minimal directory + key structure for CLI tests.
|
||||
|
||||
Temporarily sets paths.BASE_DIR so the lazy-resolved KeystoreManager
|
||||
writes keys to the same location the CLI will read from when invoked
|
||||
with --data-dir pointing at the same directory.
|
||||
"""
|
||||
data_dir = tmp_path / ".soosef"
|
||||
data_dir = tmp_path / ".fieldwitness"
|
||||
original_base = _paths.BASE_DIR
|
||||
try:
|
||||
_paths.BASE_DIR = data_dir
|
||||
@@ -245,7 +245,7 @@ class TestRotateCLI:
|
||||
return data_dir
|
||||
|
||||
def test_rotate_identity_cli_success(self, tmp_path: Path):
|
||||
data_dir = self._init_soosef(tmp_path)
|
||||
data_dir = self._init_fieldwitness(tmp_path)
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(
|
||||
@@ -261,7 +261,7 @@ class TestRotateCLI:
|
||||
assert "IMPORTANT:" in result.output
|
||||
|
||||
def test_rotate_identity_cli_no_identity(self, tmp_path: Path):
|
||||
data_dir = tmp_path / ".soosef"
|
||||
data_dir = tmp_path / ".fieldwitness"
|
||||
data_dir.mkdir()
|
||||
runner = CliRunner()
|
||||
|
||||
@@ -274,7 +274,7 @@ class TestRotateCLI:
|
||||
assert "Error" in result.output
|
||||
|
||||
def test_rotate_channel_cli_success(self, tmp_path: Path):
|
||||
data_dir = self._init_soosef(tmp_path)
|
||||
data_dir = self._init_fieldwitness(tmp_path)
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(
|
||||
@@ -290,7 +290,7 @@ class TestRotateCLI:
|
||||
assert "IMPORTANT:" in result.output
|
||||
|
||||
def test_rotate_channel_cli_no_key(self, tmp_path: Path):
|
||||
data_dir = tmp_path / ".soosef"
|
||||
data_dir = tmp_path / ".fieldwitness"
|
||||
data_dir.mkdir()
|
||||
runner = CliRunner()
|
||||
|
||||
@@ -303,7 +303,7 @@ class TestRotateCLI:
|
||||
assert "Error" in result.output
|
||||
|
||||
def test_rotate_identity_aborts_without_confirmation(self, tmp_path: Path):
|
||||
data_dir = self._init_soosef(tmp_path)
|
||||
data_dir = self._init_fieldwitness(tmp_path)
|
||||
runner = CliRunner()
|
||||
|
||||
# Simulate the user typing "n" at the confirmation prompt
|
||||
@@ -316,7 +316,7 @@ class TestRotateCLI:
|
||||
assert result.exit_code != 0
|
||||
|
||||
def test_rotate_channel_aborts_without_confirmation(self, tmp_path: Path):
|
||||
data_dir = self._init_soosef(tmp_path)
|
||||
data_dir = self._init_fieldwitness(tmp_path)
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(
|
||||
|
||||
@@ -16,11 +16,11 @@ from cryptography.hazmat.primitives.serialization import (
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def populated_soosef(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Create a populated ~/.soosef directory with identity, chain, attestations, etc."""
|
||||
import soosef.paths as paths
|
||||
def populated_fieldwitness(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
"""Create a populated ~/.fieldwitness directory with identity, chain, attestations, etc."""
|
||||
import fieldwitness.paths as paths
|
||||
|
||||
data_dir = tmp_path / ".soosef"
|
||||
data_dir = tmp_path / ".fieldwitness"
|
||||
data_dir.mkdir()
|
||||
monkeypatch.setattr(paths, "BASE_DIR", data_dir)
|
||||
|
||||
@@ -34,12 +34,12 @@ def populated_soosef(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
(identity_dir / "public.pem").write_bytes(pub_pem)
|
||||
|
||||
# Create channel key
|
||||
stegasoo_dir = data_dir / "stegasoo"
|
||||
stegasoo_dir.mkdir()
|
||||
(stegasoo_dir / "channel.key").write_text("test-channel-key")
|
||||
stego_dir = data_dir / "stego"
|
||||
stego_dir.mkdir()
|
||||
(stego_dir / "channel.key").write_text("test-channel-key")
|
||||
|
||||
# Create chain data
|
||||
from soosef.federation.chain import ChainStore
|
||||
from fieldwitness.federation.chain import ChainStore
|
||||
|
||||
chain_dir = data_dir / "chain"
|
||||
store = ChainStore(chain_dir)
|
||||
@@ -53,7 +53,7 @@ def populated_soosef(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
|
||||
# Create other dirs
|
||||
(data_dir / "auth").mkdir()
|
||||
(data_dir / "auth" / "soosef.db").write_bytes(b"dummy db")
|
||||
(data_dir / "auth" / "fieldwitness.db").write_bytes(b"dummy db")
|
||||
(data_dir / "temp").mkdir()
|
||||
(data_dir / "temp" / "file.tmp").write_bytes(b"tmp")
|
||||
(data_dir / "instance").mkdir()
|
||||
@@ -63,11 +63,11 @@ def populated_soosef(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||
return data_dir
|
||||
|
||||
|
||||
def test_purge_all_destroys_chain_data(populated_soosef: Path):
|
||||
def test_purge_all_destroys_chain_data(populated_fieldwitness: Path):
|
||||
"""CRITICAL: execute_purge(ALL) must destroy chain directory."""
|
||||
from soosef.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
|
||||
chain_dir = populated_soosef / "chain"
|
||||
chain_dir = populated_fieldwitness / "chain"
|
||||
assert chain_dir.exists()
|
||||
assert (chain_dir / "chain.bin").exists()
|
||||
|
||||
@@ -77,46 +77,46 @@ def test_purge_all_destroys_chain_data(populated_soosef: Path):
|
||||
assert "destroy_chain_data" in result.steps_completed
|
||||
|
||||
|
||||
def test_purge_all_destroys_identity(populated_soosef: Path):
|
||||
def test_purge_all_destroys_identity(populated_fieldwitness: Path):
|
||||
"""execute_purge(ALL) must destroy identity keys."""
|
||||
from soosef.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
|
||||
assert (populated_soosef / "identity" / "private.pem").exists()
|
||||
assert (populated_fieldwitness / "identity" / "private.pem").exists()
|
||||
|
||||
result = execute_purge(PurgeScope.ALL, reason="test")
|
||||
|
||||
assert not (populated_soosef / "identity").exists()
|
||||
assert not (populated_fieldwitness / "identity").exists()
|
||||
assert "destroy_identity_keys" in result.steps_completed
|
||||
|
||||
|
||||
def test_purge_all_destroys_attestation_log(populated_soosef: Path):
|
||||
"""execute_purge(ALL) must destroy the Verisoo attestation log."""
|
||||
from soosef.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
def test_purge_all_destroys_attestation_log(populated_fieldwitness: Path):
|
||||
"""execute_purge(ALL) must destroy the Attest attestation log."""
|
||||
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
|
||||
result = execute_purge(PurgeScope.ALL, reason="test")
|
||||
|
||||
assert not (populated_soosef / "attestations").exists()
|
||||
assert not (populated_fieldwitness / "attestations").exists()
|
||||
assert "destroy_attestation_log" in result.steps_completed
|
||||
|
||||
|
||||
def test_purge_keys_only_preserves_chain(populated_soosef: Path):
|
||||
def test_purge_keys_only_preserves_chain(populated_fieldwitness: Path):
|
||||
"""KEYS_ONLY purge destroys keys but preserves chain and attestation data."""
|
||||
from soosef.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
|
||||
result = execute_purge(PurgeScope.KEYS_ONLY, reason="test")
|
||||
|
||||
# Keys gone
|
||||
assert not (populated_soosef / "identity").exists()
|
||||
assert not (populated_fieldwitness / "identity").exists()
|
||||
assert "destroy_identity_keys" in result.steps_completed
|
||||
|
||||
# Chain and attestations preserved (KEYS_ONLY doesn't touch data)
|
||||
assert (populated_soosef / "chain" / "chain.bin").exists()
|
||||
assert (populated_soosef / "attestations" / "log.bin").exists()
|
||||
assert (populated_fieldwitness / "chain" / "chain.bin").exists()
|
||||
assert (populated_fieldwitness / "attestations" / "log.bin").exists()
|
||||
|
||||
|
||||
def test_purge_reports_all_steps(populated_soosef: Path):
|
||||
def test_purge_reports_all_steps(populated_fieldwitness: Path):
|
||||
"""execute_purge(ALL) reports all expected steps including chain."""
|
||||
from soosef.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
from fieldwitness.fieldkit.killswitch import PurgeScope, execute_purge
|
||||
|
||||
result = execute_purge(PurgeScope.ALL, reason="test")
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
from soosef.federation.models import AttestationChainRecord, ChainState, EntropyWitnesses
|
||||
from soosef.federation.serialization import (
|
||||
from fieldwitness.federation.models import AttestationChainRecord, ChainState, EntropyWitnesses
|
||||
from fieldwitness.federation.serialization import (
|
||||
canonical_bytes,
|
||||
compute_record_hash,
|
||||
deserialize_record,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Stegasoo Library Unit Tests
|
||||
Stego Library Unit Tests
|
||||
|
||||
Tests core functionality: encode/decode, LSB/DCT modes, channel keys, validation.
|
||||
"""
|
||||
@@ -10,8 +10,8 @@ from pathlib import Path
|
||||
import pytest
|
||||
from PIL import Image
|
||||
|
||||
import soosef.stegasoo as stegasoo
|
||||
from soosef.stegasoo import (
|
||||
import fieldwitness.stego as stego
|
||||
from fieldwitness.stego import (
|
||||
decode,
|
||||
decode_text,
|
||||
encode,
|
||||
@@ -33,7 +33,7 @@ REF_PATH = TEST_DATA / "ref.jpg"
|
||||
# Test credentials
|
||||
TEST_PASSPHRASE = "tower booty sunny windy toasty spicy"
|
||||
TEST_PIN = "727643678"
|
||||
TEST_MESSAGE = "Hello, Stegasoo!"
|
||||
TEST_MESSAGE = "Hello, Stego!"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -61,11 +61,11 @@ class TestVersion:
|
||||
"""Test version info."""
|
||||
|
||||
def test_version_exists(self):
|
||||
assert hasattr(stegasoo, "__version__")
|
||||
assert stegasoo.__version__
|
||||
assert hasattr(stego, "__version__")
|
||||
assert stego.__version__
|
||||
|
||||
def test_version_format(self):
|
||||
parts = stegasoo.__version__.split(".")
|
||||
parts = stego.__version__.split(".")
|
||||
assert len(parts) >= 2
|
||||
assert all(p.isdigit() for p in parts[:2])
|
||||
|
||||
@@ -76,7 +76,7 @@ class TestGeneration:
|
||||
def test_generate_passphrase_default(self):
|
||||
passphrase = generate_passphrase()
|
||||
words = passphrase.split()
|
||||
assert len(words) == stegasoo.DEFAULT_PASSPHRASE_WORDS
|
||||
assert len(words) == stego.DEFAULT_PASSPHRASE_WORDS
|
||||
|
||||
def test_generate_passphrase_custom_length(self):
|
||||
passphrase = generate_passphrase(words=8)
|
||||
@@ -389,7 +389,7 @@ class TestEdgeCases:
|
||||
|
||||
def test_unicode_message(self, carrier_bytes, ref_bytes):
|
||||
"""Test encoding Unicode messages."""
|
||||
unicode_msg = "Hello 🦖 Stegasoo! 日本語 émojis"
|
||||
unicode_msg = "Hello 🦖 Stego! 日本語 émojis"
|
||||
|
||||
result = encode(
|
||||
message=unicode_msg,
|
||||
@@ -521,17 +521,17 @@ class TestVideoSupport:
|
||||
|
||||
def test_video_support_flag_exists(self):
|
||||
"""HAS_VIDEO_SUPPORT flag should exist."""
|
||||
assert hasattr(stegasoo, "HAS_VIDEO_SUPPORT")
|
||||
assert isinstance(stegasoo.HAS_VIDEO_SUPPORT, bool)
|
||||
assert hasattr(stego, "HAS_VIDEO_SUPPORT")
|
||||
assert isinstance(stego.HAS_VIDEO_SUPPORT, bool)
|
||||
|
||||
def test_video_constants_exist(self):
|
||||
"""Video-related constants should exist."""
|
||||
assert hasattr(stegasoo, "EMBED_MODE_VIDEO_LSB")
|
||||
assert hasattr(stegasoo, "EMBED_MODE_VIDEO_AUTO")
|
||||
assert hasattr(stego, "EMBED_MODE_VIDEO_LSB")
|
||||
assert hasattr(stego, "EMBED_MODE_VIDEO_AUTO")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not stegasoo.HAS_VIDEO_SUPPORT,
|
||||
not stego.HAS_VIDEO_SUPPORT,
|
||||
reason="Video support not available (ffmpeg or dependencies missing)",
|
||||
)
|
||||
class TestVideoFormatDetection:
|
||||
@@ -542,21 +542,21 @@ class TestVideoFormatDetection:
|
||||
if test_video_bytes is None:
|
||||
pytest.skip("Could not create test video")
|
||||
|
||||
from soosef.stegasoo import detect_video_format
|
||||
from fieldwitness.stego import detect_video_format
|
||||
|
||||
fmt = detect_video_format(test_video_bytes)
|
||||
assert fmt in ("mp4", "mov")
|
||||
|
||||
def test_detect_video_format_unknown(self):
|
||||
"""Should return 'unknown' for non-video data."""
|
||||
from soosef.stegasoo import detect_video_format
|
||||
from fieldwitness.stego import detect_video_format
|
||||
|
||||
fmt = detect_video_format(b"not a video")
|
||||
assert fmt == "unknown"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not stegasoo.HAS_VIDEO_SUPPORT,
|
||||
not stego.HAS_VIDEO_SUPPORT,
|
||||
reason="Video support not available (ffmpeg or dependencies missing)",
|
||||
)
|
||||
class TestVideoInfo:
|
||||
@@ -567,7 +567,7 @@ class TestVideoInfo:
|
||||
if test_video_bytes is None:
|
||||
pytest.skip("Could not create test video")
|
||||
|
||||
from soosef.stegasoo import get_video_info
|
||||
from fieldwitness.stego import get_video_info
|
||||
|
||||
info = get_video_info(test_video_bytes)
|
||||
|
||||
@@ -583,7 +583,7 @@ class TestVideoInfo:
|
||||
if test_video_bytes is None:
|
||||
pytest.skip("Could not create test video")
|
||||
|
||||
from soosef.stegasoo import validate_video
|
||||
from fieldwitness.stego import validate_video
|
||||
|
||||
result = validate_video(test_video_bytes, check_duration=False)
|
||||
|
||||
@@ -592,7 +592,7 @@ class TestVideoInfo:
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not stegasoo.HAS_VIDEO_SUPPORT,
|
||||
not stego.HAS_VIDEO_SUPPORT,
|
||||
reason="Video support not available (ffmpeg or dependencies missing)",
|
||||
)
|
||||
class TestVideoCapacity:
|
||||
@@ -603,7 +603,7 @@ class TestVideoCapacity:
|
||||
if test_video_bytes is None:
|
||||
pytest.skip("Could not create test video")
|
||||
|
||||
from soosef.stegasoo import calculate_video_capacity
|
||||
from fieldwitness.stego import calculate_video_capacity
|
||||
|
||||
capacity_info = calculate_video_capacity(test_video_bytes)
|
||||
|
||||
@@ -615,7 +615,7 @@ class TestVideoCapacity:
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not stegasoo.HAS_VIDEO_SUPPORT,
|
||||
not stego.HAS_VIDEO_SUPPORT,
|
||||
reason="Video support not available (ffmpeg or dependencies missing)",
|
||||
)
|
||||
class TestVideoEncodeDecode:
|
||||
@@ -626,7 +626,7 @@ class TestVideoEncodeDecode:
|
||||
if test_video_bytes is None:
|
||||
pytest.skip("Could not create test video")
|
||||
|
||||
from soosef.stegasoo import decode_video, encode_video
|
||||
from fieldwitness.stego import decode_video, encode_video
|
||||
|
||||
message = "Secret video message!"
|
||||
|
||||
@@ -660,7 +660,7 @@ class TestVideoEncodeDecode:
|
||||
if test_video_bytes is None:
|
||||
pytest.skip("Could not create test video")
|
||||
|
||||
from soosef.stegasoo import decode_video, encode_video
|
||||
from fieldwitness.stego import decode_video, encode_video
|
||||
|
||||
message = "Secret video message!"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Tests for Stegasoo audio steganography.
|
||||
Tests for Stego audio steganography.
|
||||
|
||||
Tests cover:
|
||||
- Audio LSB roundtrip (encode + decode)
|
||||
@@ -23,10 +23,10 @@ import numpy as np
|
||||
import pytest
|
||||
import soundfile as sf
|
||||
|
||||
from soosef.stegasoo.constants import AUDIO_ENABLED, EMBED_MODE_AUDIO_LSB, EMBED_MODE_AUDIO_SPREAD
|
||||
from soosef.stegasoo.models import AudioCapacityInfo, AudioEmbedStats, AudioInfo
|
||||
from fieldwitness.stego.constants import AUDIO_ENABLED, EMBED_MODE_AUDIO_LSB, EMBED_MODE_AUDIO_SPREAD
|
||||
from fieldwitness.stego.models import AudioCapacityInfo, AudioEmbedStats, AudioInfo
|
||||
|
||||
pytestmark = pytest.mark.skipif(not AUDIO_ENABLED, reason="Audio support disabled (STEGASOO_AUDIO)")
|
||||
pytestmark = pytest.mark.skipif(not AUDIO_ENABLED, reason="Audio support disabled (FIELDWITNESS_AUDIO)")
|
||||
|
||||
# Path to real test data files
|
||||
_TEST_DATA = Path(__file__).parent.parent / "test_data"
|
||||
@@ -177,7 +177,7 @@ class TestAudioLSB:
|
||||
"""Tests for audio LSB steganography."""
|
||||
|
||||
def test_calculate_capacity(self, carrier_wav):
|
||||
from soosef.stegasoo.audio_steganography import calculate_audio_lsb_capacity
|
||||
from fieldwitness.stego.audio_steganography import calculate_audio_lsb_capacity
|
||||
|
||||
capacity = calculate_audio_lsb_capacity(carrier_wav)
|
||||
assert capacity > 0
|
||||
@@ -186,7 +186,7 @@ class TestAudioLSB:
|
||||
|
||||
def test_embed_extract_roundtrip(self, carrier_wav):
|
||||
"""Test basic LSB embed/extract roundtrip."""
|
||||
from soosef.stegasoo.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
from fieldwitness.stego.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
|
||||
payload = b"Hello, audio steganography!"
|
||||
key = b"\x42" * 32
|
||||
@@ -205,7 +205,7 @@ class TestAudioLSB:
|
||||
|
||||
def test_embed_extract_stereo(self, carrier_wav_stereo):
|
||||
"""Test LSB roundtrip with stereo audio."""
|
||||
from soosef.stegasoo.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
from fieldwitness.stego.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
|
||||
payload = b"Stereo test message"
|
||||
key = b"\xAB" * 32
|
||||
@@ -218,7 +218,7 @@ class TestAudioLSB:
|
||||
|
||||
def test_wrong_key_fails(self, carrier_wav):
|
||||
"""Test that wrong key produces no valid extraction."""
|
||||
from soosef.stegasoo.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
from fieldwitness.stego.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
|
||||
payload = b"Secret message"
|
||||
correct_key = b"\x42" * 32
|
||||
@@ -231,7 +231,7 @@ class TestAudioLSB:
|
||||
|
||||
def test_two_bits_per_sample(self, carrier_wav):
|
||||
"""Test embedding with 2 bits per sample."""
|
||||
from soosef.stegasoo.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
from fieldwitness.stego.audio_steganography import embed_in_audio_lsb, extract_from_audio_lsb
|
||||
|
||||
payload = b"Two bits per sample test"
|
||||
key = b"\x55" * 32
|
||||
@@ -243,7 +243,7 @@ class TestAudioLSB:
|
||||
|
||||
def test_generate_sample_indices(self):
|
||||
"""Test deterministic sample index generation."""
|
||||
from soosef.stegasoo.audio_steganography import generate_sample_indices
|
||||
from fieldwitness.stego.audio_steganography import generate_sample_indices
|
||||
|
||||
key = b"\x42" * 32
|
||||
indices1 = generate_sample_indices(key, 10000, 100)
|
||||
@@ -263,7 +263,7 @@ class TestAudioSpread:
|
||||
"""Tests for audio spread spectrum steganography (v2 per-channel)."""
|
||||
|
||||
def test_calculate_capacity_default_tier(self, carrier_wav_long):
|
||||
from soosef.stegasoo.spread_steganography import calculate_audio_spread_capacity
|
||||
from fieldwitness.stego.spread_steganography import calculate_audio_spread_capacity
|
||||
|
||||
capacity = calculate_audio_spread_capacity(carrier_wav_long)
|
||||
assert isinstance(capacity, AudioCapacityInfo)
|
||||
@@ -274,7 +274,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_calculate_capacity_per_tier(self, carrier_wav_long):
|
||||
"""Capacity should increase as chip length decreases."""
|
||||
from soosef.stegasoo.spread_steganography import calculate_audio_spread_capacity
|
||||
from fieldwitness.stego.spread_steganography import calculate_audio_spread_capacity
|
||||
|
||||
cap_lossless = calculate_audio_spread_capacity(carrier_wav_long, chip_tier=0)
|
||||
cap_high = calculate_audio_spread_capacity(carrier_wav_long, chip_tier=1)
|
||||
@@ -290,7 +290,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_spread_roundtrip_default_tier(self, carrier_wav_long):
|
||||
"""Test spread spectrum embed/extract roundtrip (default tier 2)."""
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
embed_in_audio_spread,
|
||||
extract_from_audio_spread,
|
||||
)
|
||||
@@ -311,7 +311,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_spread_roundtrip_tier_0(self, carrier_wav_long):
|
||||
"""Test spread spectrum at tier 0 (chip=256, lossless)."""
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
embed_in_audio_spread,
|
||||
extract_from_audio_spread,
|
||||
)
|
||||
@@ -329,7 +329,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_spread_roundtrip_tier_1(self, carrier_wav_long):
|
||||
"""Test spread spectrum at tier 1 (chip=512, high lossy)."""
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
embed_in_audio_spread,
|
||||
extract_from_audio_spread,
|
||||
)
|
||||
@@ -347,7 +347,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_wrong_seed_fails(self, carrier_wav_long):
|
||||
"""Test that wrong seed produces no valid extraction."""
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
embed_in_audio_spread,
|
||||
extract_from_audio_spread,
|
||||
)
|
||||
@@ -363,7 +363,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_per_channel_stereo_roundtrip(self, carrier_wav_stereo_long):
|
||||
"""Test that stereo per-channel embedding/extraction works."""
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
embed_in_audio_spread,
|
||||
extract_from_audio_spread,
|
||||
)
|
||||
@@ -387,7 +387,7 @@ class TestAudioSpread:
|
||||
The difference between left and right channels should be preserved
|
||||
(not zeroed out as the old mono-broadcast approach would do).
|
||||
"""
|
||||
from soosef.stegasoo.spread_steganography import embed_in_audio_spread
|
||||
from fieldwitness.stego.spread_steganography import embed_in_audio_spread
|
||||
|
||||
payload = b"Spatial preservation test"
|
||||
seed = b"\xCD" * 32
|
||||
@@ -415,7 +415,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_capacity_scales_with_channels(self, carrier_wav_long, carrier_wav_stereo_long):
|
||||
"""Stereo should have roughly double the capacity of mono."""
|
||||
from soosef.stegasoo.spread_steganography import calculate_audio_spread_capacity
|
||||
from fieldwitness.stego.spread_steganography import calculate_audio_spread_capacity
|
||||
|
||||
mono_cap = calculate_audio_spread_capacity(carrier_wav_long, chip_tier=0)
|
||||
stereo_cap = calculate_audio_spread_capacity(carrier_wav_stereo_long, chip_tier=0)
|
||||
@@ -427,7 +427,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_lfe_skip_5_1(self, carrier_wav_5_1):
|
||||
"""LFE channel (index 3) should be unmodified in 6-channel audio."""
|
||||
from soosef.stegasoo.spread_steganography import embed_in_audio_spread
|
||||
from fieldwitness.stego.spread_steganography import embed_in_audio_spread
|
||||
|
||||
payload = b"LFE skip test"
|
||||
seed = b"\xEE" * 32
|
||||
@@ -449,7 +449,7 @@ class TestAudioSpread:
|
||||
|
||||
def test_lfe_skip_roundtrip(self, carrier_wav_5_1):
|
||||
"""5.1 audio embed/extract roundtrip with LFE skipping."""
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
embed_in_audio_spread,
|
||||
extract_from_audio_spread,
|
||||
)
|
||||
@@ -477,7 +477,7 @@ class TestHeaderV2:
|
||||
"""Tests for v2 header construction and parsing."""
|
||||
|
||||
def test_header_v2_build_parse_roundtrip(self):
|
||||
from soosef.stegasoo.spread_steganography import _build_header_v2, _parse_header
|
||||
from fieldwitness.stego.spread_steganography import _build_header_v2, _parse_header
|
||||
|
||||
data_length = 12345
|
||||
chip_tier = 1
|
||||
@@ -496,7 +496,7 @@ class TestHeaderV2:
|
||||
assert lfe is False
|
||||
|
||||
def test_header_v2_with_lfe_flag(self):
|
||||
from soosef.stegasoo.spread_steganography import _build_header_v2, _parse_header
|
||||
from fieldwitness.stego.spread_steganography import _build_header_v2, _parse_header
|
||||
|
||||
header = _build_header_v2(999, 0, 5, lfe_skipped=True)
|
||||
magic_valid, version, length, tier, nch, lfe = _parse_header(header)
|
||||
@@ -508,7 +508,7 @@ class TestHeaderV2:
|
||||
assert lfe is True
|
||||
|
||||
def test_header_v0_build_parse(self):
|
||||
from soosef.stegasoo.spread_steganography import _build_header_v0, _parse_header
|
||||
from fieldwitness.stego.spread_steganography import _build_header_v0, _parse_header
|
||||
|
||||
header = _build_header_v0(4567)
|
||||
assert len(header) == 16
|
||||
@@ -521,7 +521,7 @@ class TestHeaderV2:
|
||||
assert nch is None
|
||||
|
||||
def test_header_bad_magic(self):
|
||||
from soosef.stegasoo.spread_steganography import _parse_header
|
||||
from fieldwitness.stego.spread_steganography import _parse_header
|
||||
|
||||
bad_header = b"XXXX" + b"\x00" * 16
|
||||
magic_valid, version, length, tier, nch, lfe = _parse_header(bad_header)
|
||||
@@ -537,7 +537,7 @@ class TestRoundRobin:
|
||||
"""Tests for round-robin bit distribution."""
|
||||
|
||||
def test_distribute_and_collect_identity(self):
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
_collect_bits_round_robin,
|
||||
_distribute_bits_round_robin,
|
||||
)
|
||||
@@ -550,7 +550,7 @@ class TestRoundRobin:
|
||||
assert reassembled == bits, f"Failed for {num_ch} channels"
|
||||
|
||||
def test_distribute_round_robin_ordering(self):
|
||||
from soosef.stegasoo.spread_steganography import _distribute_bits_round_robin
|
||||
from fieldwitness.stego.spread_steganography import _distribute_bits_round_robin
|
||||
|
||||
bits = [0, 1, 2, 3, 4, 5] # using ints for clarity
|
||||
per_ch = _distribute_bits_round_robin(bits, 3)
|
||||
@@ -560,7 +560,7 @@ class TestRoundRobin:
|
||||
assert per_ch[2] == [2, 5]
|
||||
|
||||
def test_distribute_uneven(self):
|
||||
from soosef.stegasoo.spread_steganography import (
|
||||
from fieldwitness.stego.spread_steganography import (
|
||||
_collect_bits_round_robin,
|
||||
_distribute_bits_round_robin,
|
||||
)
|
||||
@@ -584,31 +584,31 @@ class TestChannelManagement:
|
||||
"""Tests for embeddable channel selection."""
|
||||
|
||||
def test_mono(self):
|
||||
from soosef.stegasoo.spread_steganography import _embeddable_channels
|
||||
from fieldwitness.stego.spread_steganography import _embeddable_channels
|
||||
|
||||
assert _embeddable_channels(1) == [0]
|
||||
|
||||
def test_stereo(self):
|
||||
from soosef.stegasoo.spread_steganography import _embeddable_channels
|
||||
from fieldwitness.stego.spread_steganography import _embeddable_channels
|
||||
|
||||
assert _embeddable_channels(2) == [0, 1]
|
||||
|
||||
def test_5_1_skips_lfe(self):
|
||||
from soosef.stegasoo.spread_steganography import _embeddable_channels
|
||||
from fieldwitness.stego.spread_steganography import _embeddable_channels
|
||||
|
||||
channels = _embeddable_channels(6)
|
||||
assert channels == [0, 1, 2, 4, 5]
|
||||
assert 3 not in channels # LFE skipped
|
||||
|
||||
def test_7_1_skips_lfe(self):
|
||||
from soosef.stegasoo.spread_steganography import _embeddable_channels
|
||||
from fieldwitness.stego.spread_steganography import _embeddable_channels
|
||||
|
||||
channels = _embeddable_channels(8)
|
||||
assert 3 not in channels
|
||||
assert len(channels) == 7
|
||||
|
||||
def test_quad_no_skip(self):
|
||||
from soosef.stegasoo.spread_steganography import _embeddable_channels
|
||||
from fieldwitness.stego.spread_steganography import _embeddable_channels
|
||||
|
||||
# 4 channels < 6, so no LFE skip
|
||||
assert _embeddable_channels(4) == [0, 1, 2, 3]
|
||||
@@ -623,17 +623,17 @@ class TestFormatDetection:
|
||||
"""Tests for audio format detection."""
|
||||
|
||||
def test_detect_wav(self, carrier_wav):
|
||||
from soosef.stegasoo.audio_utils import detect_audio_format
|
||||
from fieldwitness.stego.audio_utils import detect_audio_format
|
||||
|
||||
assert detect_audio_format(carrier_wav) == "wav"
|
||||
|
||||
def test_detect_unknown(self):
|
||||
from soosef.stegasoo.audio_utils import detect_audio_format
|
||||
from fieldwitness.stego.audio_utils import detect_audio_format
|
||||
|
||||
assert detect_audio_format(b"not audio data") == "unknown"
|
||||
|
||||
def test_detect_empty(self):
|
||||
from soosef.stegasoo.audio_utils import detect_audio_format
|
||||
from fieldwitness.stego.audio_utils import detect_audio_format
|
||||
|
||||
assert detect_audio_format(b"") == "unknown"
|
||||
|
||||
@@ -647,7 +647,7 @@ class TestAudioInfo:
|
||||
"""Tests for audio info extraction."""
|
||||
|
||||
def test_get_wav_info(self, carrier_wav):
|
||||
from soosef.stegasoo.audio_utils import get_audio_info
|
||||
from fieldwitness.stego.audio_utils import get_audio_info
|
||||
|
||||
info = get_audio_info(carrier_wav)
|
||||
assert isinstance(info, AudioInfo)
|
||||
@@ -657,7 +657,7 @@ class TestAudioInfo:
|
||||
assert abs(info.duration_seconds - 1.0) < 0.1
|
||||
|
||||
def test_get_stereo_info(self, carrier_wav_stereo):
|
||||
from soosef.stegasoo.audio_utils import get_audio_info
|
||||
from fieldwitness.stego.audio_utils import get_audio_info
|
||||
|
||||
info = get_audio_info(carrier_wav_stereo)
|
||||
assert info.channels == 2
|
||||
@@ -672,25 +672,25 @@ class TestAudioValidation:
|
||||
"""Tests for audio validation."""
|
||||
|
||||
def test_validate_valid_audio(self, carrier_wav):
|
||||
from soosef.stegasoo.audio_utils import validate_audio
|
||||
from fieldwitness.stego.audio_utils import validate_audio
|
||||
|
||||
result = validate_audio(carrier_wav)
|
||||
assert result.is_valid
|
||||
|
||||
def test_validate_empty_audio(self):
|
||||
from soosef.stegasoo.audio_utils import validate_audio
|
||||
from fieldwitness.stego.audio_utils import validate_audio
|
||||
|
||||
result = validate_audio(b"")
|
||||
assert not result.is_valid
|
||||
|
||||
def test_validate_invalid_audio(self):
|
||||
from soosef.stegasoo.audio_utils import validate_audio
|
||||
from fieldwitness.stego.audio_utils import validate_audio
|
||||
|
||||
result = validate_audio(b"not audio data at all")
|
||||
assert not result.is_valid
|
||||
|
||||
def test_validate_audio_embed_mode(self):
|
||||
from soosef.stegasoo.validation import validate_audio_embed_mode
|
||||
from fieldwitness.stego.validation import validate_audio_embed_mode
|
||||
|
||||
assert validate_audio_embed_mode("audio_lsb").is_valid
|
||||
assert validate_audio_embed_mode("audio_spread").is_valid
|
||||
@@ -707,8 +707,8 @@ class TestIntegration:
|
||||
"""End-to-end integration tests using encode_audio/decode_audio."""
|
||||
|
||||
def test_lsb_encode_decode(self, carrier_wav, reference_photo):
|
||||
from soosef.stegasoo.decode import decode_audio
|
||||
from soosef.stegasoo.encode import encode_audio
|
||||
from fieldwitness.stego.decode import decode_audio
|
||||
from fieldwitness.stego.encode import encode_audio
|
||||
|
||||
stego_audio, stats = encode_audio(
|
||||
message="Hello from audio steganography!",
|
||||
@@ -733,8 +733,8 @@ class TestIntegration:
|
||||
assert result.message == "Hello from audio steganography!"
|
||||
|
||||
def test_lsb_wrong_credentials(self, carrier_wav, reference_photo):
|
||||
from soosef.stegasoo.decode import decode_audio
|
||||
from soosef.stegasoo.encode import encode_audio
|
||||
from fieldwitness.stego.decode import decode_audio
|
||||
from fieldwitness.stego.encode import encode_audio
|
||||
|
||||
stego_audio, _ = encode_audio(
|
||||
message="Secret",
|
||||
@@ -756,8 +756,8 @@ class TestIntegration:
|
||||
|
||||
def test_spread_encode_decode(self, carrier_wav_spread_integration, reference_photo):
|
||||
"""Test full spread spectrum encode/decode pipeline."""
|
||||
from soosef.stegasoo.decode import decode_audio
|
||||
from soosef.stegasoo.encode import encode_audio
|
||||
from fieldwitness.stego.decode import decode_audio
|
||||
from fieldwitness.stego.encode import encode_audio
|
||||
|
||||
stego_audio, stats = encode_audio(
|
||||
message="Spread integration test",
|
||||
@@ -782,8 +782,8 @@ class TestIntegration:
|
||||
self, carrier_wav_spread_integration, reference_photo
|
||||
):
|
||||
"""Test spread spectrum with explicit chip tier."""
|
||||
from soosef.stegasoo.decode import decode_audio
|
||||
from soosef.stegasoo.encode import encode_audio
|
||||
from fieldwitness.stego.decode import decode_audio
|
||||
from fieldwitness.stego.encode import encode_audio
|
||||
|
||||
stego_audio, stats = encode_audio(
|
||||
message="Tier 0 integration",
|
||||
@@ -810,8 +810,8 @@ class TestIntegration:
|
||||
|
||||
def test_auto_detect_lsb(self, carrier_wav, reference_photo):
|
||||
"""Test auto-detection finds LSB encoded audio."""
|
||||
from soosef.stegasoo.decode import decode_audio
|
||||
from soosef.stegasoo.encode import encode_audio
|
||||
from fieldwitness.stego.decode import decode_audio
|
||||
from fieldwitness.stego.encode import encode_audio
|
||||
|
||||
stego_audio, _ = encode_audio(
|
||||
message="Auto-detect test",
|
||||
@@ -834,8 +834,8 @@ class TestIntegration:
|
||||
|
||||
def test_spread_with_real_speech(self, speech_wav, reference_photo):
|
||||
"""Test spread spectrum with real speech audio from test_data."""
|
||||
from soosef.stegasoo.decode import decode_audio
|
||||
from soosef.stegasoo.encode import encode_audio
|
||||
from fieldwitness.stego.decode import decode_audio
|
||||
from fieldwitness.stego.encode import encode_audio
|
||||
|
||||
message = "Hidden in a speech about elitism"
|
||||
|
||||
Reference in New Issue
Block a user