mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-07-04 21:43:16 +02:00
153 lines
5.3 KiB
Python
153 lines
5.3 KiB
Python
|
|
"""Tests for shared log_integrity HMAC signing and verification."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import hashlib
|
||
|
|
import hmac
|
||
|
|
import json
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
from unittest.mock import patch
|
||
|
|
|
||
|
|
from python_pkg.shared.log_integrity import (
|
||
|
|
_generate_hmac_key,
|
||
|
|
_load_hmac_key,
|
||
|
|
compute_entry_hmac,
|
||
|
|
verify_entry_hmac,
|
||
|
|
)
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
|
||
|
|
class TestLoadHmacKey:
|
||
|
|
"""Tests for _load_hmac_key."""
|
||
|
|
|
||
|
|
def test_loads_key_from_file(self, tmp_path: Path) -> None:
|
||
|
|
"""Test loading HMAC key from existing file."""
|
||
|
|
key_file = tmp_path / "hmac.key"
|
||
|
|
key_file.write_bytes(b"secret_key_bytes")
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
result = _load_hmac_key()
|
||
|
|
assert result == b"secret_key_bytes"
|
||
|
|
|
||
|
|
def test_returns_none_on_missing_file(self, tmp_path: Path) -> None:
|
||
|
|
"""Test returns None when key file doesn't exist."""
|
||
|
|
key_file = tmp_path / "nonexistent.key"
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
result = _load_hmac_key()
|
||
|
|
assert result is None
|
||
|
|
|
||
|
|
|
||
|
|
class TestGenerateHmacKey:
|
||
|
|
"""Tests for _generate_hmac_key."""
|
||
|
|
|
||
|
|
def test_generates_and_writes_key(self, tmp_path: Path) -> None:
|
||
|
|
"""Test key generation creates file with 32-byte key."""
|
||
|
|
key_file = tmp_path / "subdir" / "hmac.key"
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
result = _generate_hmac_key()
|
||
|
|
assert result is not None
|
||
|
|
assert len(result) == 32
|
||
|
|
assert key_file.read_bytes() == result
|
||
|
|
|
||
|
|
def test_returns_none_on_write_failure(self) -> None:
|
||
|
|
"""Test returns None when file cannot be written."""
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
) as mock_path:
|
||
|
|
mock_path.parent.mkdir.side_effect = OSError("permission denied")
|
||
|
|
result = _generate_hmac_key()
|
||
|
|
assert result is None
|
||
|
|
|
||
|
|
|
||
|
|
class TestComputeEntryHmac:
|
||
|
|
"""Tests for compute_entry_hmac."""
|
||
|
|
|
||
|
|
def test_computes_hmac_for_entry(self, tmp_path: Path) -> None:
|
||
|
|
"""Test HMAC computation produces valid hex string."""
|
||
|
|
key_file = tmp_path / "hmac.key"
|
||
|
|
key = b"test_key_12345"
|
||
|
|
key_file.write_bytes(key)
|
||
|
|
entry = {"timestamp": "2025-01-01T00:00:00", "workout_data": {"type": "test"}}
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
result = compute_entry_hmac(entry)
|
||
|
|
assert result is not None
|
||
|
|
# Verify manually
|
||
|
|
payload = json.dumps(entry, sort_keys=True, separators=(",", ":"))
|
||
|
|
expected = hmac.new(key, payload.encode(), hashlib.sha256).hexdigest()
|
||
|
|
assert result == expected
|
||
|
|
|
||
|
|
def test_returns_none_when_no_key(self, tmp_path: Path) -> None:
|
||
|
|
"""Test returns None when key file is missing."""
|
||
|
|
key_file = tmp_path / "nonexistent.key"
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
result = compute_entry_hmac({"data": "test"})
|
||
|
|
assert result is None
|
||
|
|
|
||
|
|
|
||
|
|
class TestVerifyEntryHmac:
|
||
|
|
"""Tests for verify_entry_hmac."""
|
||
|
|
|
||
|
|
def test_valid_hmac(self, tmp_path: Path) -> None:
|
||
|
|
"""Test verification passes with correct HMAC."""
|
||
|
|
key_file = tmp_path / "hmac.key"
|
||
|
|
key = b"verification_key"
|
||
|
|
key_file.write_bytes(key)
|
||
|
|
entry_data = {"timestamp": "2025-01-01", "workout_data": {"type": "test"}}
|
||
|
|
payload = json.dumps(entry_data, sort_keys=True, separators=(",", ":"))
|
||
|
|
correct_hmac = hmac.new(key, payload.encode(), hashlib.sha256).hexdigest()
|
||
|
|
entry = {**entry_data, "hmac": correct_hmac}
|
||
|
|
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
assert verify_entry_hmac(entry) is True
|
||
|
|
|
||
|
|
def test_invalid_hmac(self, tmp_path: Path) -> None:
|
||
|
|
"""Test verification fails with wrong HMAC."""
|
||
|
|
key_file = tmp_path / "hmac.key"
|
||
|
|
key_file.write_bytes(b"verification_key")
|
||
|
|
entry = {"timestamp": "2025-01-01", "hmac": "wrong_hmac_value"}
|
||
|
|
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
assert verify_entry_hmac(entry) is False
|
||
|
|
|
||
|
|
def test_missing_hmac_field(self) -> None:
|
||
|
|
"""Test verification fails when entry has no hmac field."""
|
||
|
|
entry: dict[str, object] = {"timestamp": "2025-01-01"}
|
||
|
|
assert verify_entry_hmac(entry) is False
|
||
|
|
|
||
|
|
def test_non_string_hmac_field(self) -> None:
|
||
|
|
"""Test verification fails when hmac field is not a string."""
|
||
|
|
entry: dict[str, object] = {"timestamp": "2025-01-01", "hmac": 12345}
|
||
|
|
assert verify_entry_hmac(entry) is False
|
||
|
|
|
||
|
|
def test_missing_key_file(self, tmp_path: Path) -> None:
|
||
|
|
"""Test verification fails when key file doesn't exist."""
|
||
|
|
key_file = tmp_path / "nonexistent.key"
|
||
|
|
entry = {"timestamp": "2025-01-01", "hmac": "some_hmac"}
|
||
|
|
with patch(
|
||
|
|
"python_pkg.shared.log_integrity.HMAC_KEY_FILE",
|
||
|
|
key_file,
|
||
|
|
):
|
||
|
|
assert verify_entry_hmac(entry) is False
|