from __future__ import annotations import base64 import hashlib import hmac import time from unittest.mock import patch from yuxi.channel.common.crypto import ( hmac_sha256_base64, hmac_sha256_sign, sha1_sorted_sign, verify_hmac_digest, verify_timestamp, ) class TestHmacSha256Sign: def test_sign_with_str_inputs(self): secret = "secret" message = "message" expected = hmac.new(secret.encode("utf-8"), message.encode("utf-8"), hashlib.sha256).digest() assert hmac_sha256_sign(secret, message) == expected def test_sign_with_bytes_inputs(self): secret = b"secret" message = b"message" expected = hmac.new(secret, message, hashlib.sha256).digest() assert hmac_sha256_sign(secret, message) == expected def test_sign_with_mixed_inputs(self): secret = "secret" message = b"message" expected = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).digest() assert hmac_sha256_sign(secret, message) == expected class TestHmacSha256Base64: def test_base64_signature(self): secret = "secret" message = "message" expected = base64.b64encode(hmac_sha256_sign(secret, message)).decode("utf-8") assert hmac_sha256_base64(secret, message) == expected def test_base64_signature_is_str(self): result = hmac_sha256_base64("s", "m") assert isinstance(result, str) class TestVerifyHmacDigest: def test_valid_bytes_signature(self): secret = "secret" message = "message" signature = hmac_sha256_sign(secret, message) assert verify_hmac_digest(secret, message, signature) is True def test_valid_base64_string_signature(self): secret = "secret" message = "message" signature = hmac_sha256_base64(secret, message) assert verify_hmac_digest(secret, message, signature) is True def test_invalid_signature(self): secret = "secret" message = "message" assert verify_hmac_digest(secret, message, b"wrong") is False def test_none_signature(self): assert verify_hmac_digest("secret", "message", None) is False def test_different_secret(self): signature = hmac_sha256_sign("secret", "message") assert verify_hmac_digest("other", "message", signature) is False class TestSha1SortedSign: def test_sorted_concatenation(self): expected = hashlib.sha1("".join(sorted(["b", "a", "c"])).encode("utf-8")).hexdigest() assert sha1_sorted_sign("a", "b", "c") == expected def test_empty_parts(self): assert sha1_sorted_sign() == hashlib.sha1(b"").hexdigest() def test_single_part(self): assert sha1_sorted_sign("only") == hashlib.sha1(b"only").hexdigest() class TestVerifyTimestamp: def test_valid_int_timestamp(self): now = int(time.time()) assert verify_timestamp(now) is True def test_valid_str_timestamp(self): now = int(time.time()) assert verify_timestamp(str(now)) is True def test_timestamp_within_tolerance(self): now = 1000 with patch("time.time", return_value=now): assert verify_timestamp(now) is True assert verify_timestamp(now - 300) is True assert verify_timestamp(now + 300) is True def test_timestamp_outside_tolerance(self): now = 1000 with patch("time.time", return_value=now): assert verify_timestamp(now - 301) is False assert verify_timestamp(now + 301) is False def test_invalid_string_timestamp(self): assert verify_timestamp("not-a-number") is False def test_none_timestamp(self): assert verify_timestamp(None) is False def test_float_string_timestamp(self): assert verify_timestamp("1234.5") is False def test_custom_tolerance(self): now = 1000 with patch("time.time", return_value=now): assert verify_timestamp(now - 10, tolerance=10) is True assert verify_timestamp(now - 11, tolerance=10) is False def test_negative_timestamp(self): now = 1000 with patch("time.time", return_value=now): assert verify_timestamp(-1) is False