Files
hermes-caldav-mcp/tests/test_wallet_protection.py
xeno 93b9c9f913
Some checks failed
Offline checks / test (3.11) (push) Has been cancelled
Offline checks / test (3.13) (push) Has been cancelled
feat: publish Hermes Nextcloud calendar MCP under AGPL-3.0
2026-09-30 20:27:25 +00:00

127 lines
4.4 KiB
Python

import os
import pytest
from hermes_caldav_mcp import wallet_protection as protection
from hermes_caldav_mcp.types import SafeError
from hermes_caldav_mcp.wallets import WalletChoice
@pytest.fixture(autouse=True)
def no_native_windows(monkeypatch):
monkeypatch.setattr(protection, "open_manager", lambda _: False)
@pytest.mark.parametrize(
"header,state",
[
(b"GnomeKeyring\n\r\0\n\0\0\0\0", "protected"),
(b"[keyring]\n", "unprotected"),
(b"unknown format", "unknown"),
(b"GnomeKeyring\n\r\0\n\1\0\0\0", "unknown"),
],
)
def test_gnome_checks_exact_collection_header_only(tmp_path, header, state):
root = tmp_path / "keyrings"
root.mkdir()
(root / "login.keyring").write_bytes(header + b"synthetic secret payload")
result = protection.inspect_file("gnome", "login", tmp_path)
assert result.state == state
assert "synthetic" not in repr(result)
def test_other_wallet_file_does_not_prove_selected_wallet(tmp_path):
root = tmp_path / "keyrings"
root.mkdir()
(root / "other.keyring").write_bytes(b"GnomeKeyring\n\r\0\n\0\0\0\0")
assert protection.inspect_file("gnome", "login", tmp_path).state == "unknown"
assert protection.inspect_file("gnome", "../other", tmp_path).state == "unknown"
def test_kwallet_encrypted_header_does_not_prove_nonempty_password(tmp_path):
root = tmp_path / "kwalletd"
root.mkdir()
(root / "kdewallet.kwl").write_bytes(b"KWALLET\n\r\0\r\n\0\1\0\0" + b"x" * 80)
result = protection.inspect_file("kde", "kdewallet", tmp_path)
assert result.state == "unknown"
assert result.encrypted_format
@pytest.mark.parametrize(
"version,recognized",
[
(bytes([0, 0, 0, 0]), True),
(bytes([0, 1, 3, 2]), True),
(bytes([0, 1, 2, 0]), True),
(bytes([0, 1, 1, 0]), False),
(bytes([9, 1, 3, 2]), False),
(bytes([0, 1, 3, 99]), False),
],
)
def test_kwallet_known_ciphers_are_recognized_without_claiming_password_protection(
tmp_path, version, recognized
):
root = tmp_path / "kwalletd"
root.mkdir()
(root / "kdewallet.kwl").write_bytes(b"KWALLET\n\r\0\r\n" + version + b"x" * 80)
result = protection.inspect_file("kde", "kdewallet", tmp_path)
assert result.state == "unknown"
assert result.encrypted_format is recognized
def test_symlink_and_fifo_are_not_read_as_wallets(tmp_path):
root = tmp_path / "keyrings"
root.mkdir()
target = tmp_path / "other"
target.write_bytes(b"GnomeKeyring\n\r\0\n\0\0\0\0")
path = root / "login.keyring"
path.symlink_to(target)
assert protection.inspect_file("gnome", "login", tmp_path).state == "unknown"
path.unlink()
os.mkfifo(path)
assert protection.inspect_file("gnome", "login", tmp_path).state == "unknown"
@pytest.mark.parametrize("state", ["unknown", "unprotected"])
def test_override_requires_explicit_nondefault_choice(monkeypatch, state):
replies = iter(["", "3"])
monkeypatch.setattr("builtins.input", lambda _: next(replies))
monkeypatch.setattr(
protection, "inspect_storage", lambda _: protection.StorageStatus(state)
)
with pytest.raises(SafeError):
protection.ensure_storage(WalletChoice("kde"))
def test_unprotected_override_is_distinct_from_verified_storage(monkeypatch):
replies = iter(["2"])
monkeypatch.setattr("builtins.input", lambda _: next(replies))
monkeypatch.setattr(
protection, "inspect_storage", lambda _: protection.StorageStatus("unprotected")
)
assert protection.ensure_storage(WalletChoice("gnome")) is False
def test_protected_storage_needs_no_confirmation(monkeypatch):
monkeypatch.setattr(
protection,
"inspect_storage",
lambda _: protection.StorageStatus("protected", True),
)
monkeypatch.setattr("builtins.input", lambda _: pytest.fail("Unnecessary prompt"))
assert protection.ensure_storage(WalletChoice("gnome")) is True
def test_guided_password_setup_rechecks_before_accepting(monkeypatch):
replies = iter(["", "1"])
states = iter(
[
protection.StorageStatus("unknown"),
protection.StorageStatus("protected", True),
]
)
monkeypatch.setattr("builtins.input", lambda _: next(replies))
monkeypatch.setattr(protection, "inspect_storage", lambda _: next(states))
monkeypatch.setattr(protection, "open_manager", lambda _: True)
assert protection.ensure_storage(WalletChoice("gnome")) is True