"""End-to-end tests for raycast_api.signing.Signer. Hand-computed canonical/HMAC values for synthetic specs prove the spec fields actually drive behaviour (not just defaults). """ from __future__ import annotations import hashlib import hmac as _hmac import pytest from raycast_api.signing import Signer from raycast_api.signing_spec import RotRange, SigningSpec RAYCAST_SPEC = SigningSpec( rot_fn_name="Sur", signing_fn_name="Nkt", rot_ranges=[ RotRange(0x41, 0x5A, 13), RotRange(0x61, 0x7A, 13), RotRange(0x30, 0x39, 5), ], ) FAKE_SECRET = "DEAD" + "BEEF" * 15 TS = "1778858809" DEVICE = "20eca913cada74f879e6535304f9d44da380c28eb855065c0d71017a3d7c3099" class TestCanonicalString: """Canonical string composition matches rot(ts) + join + rot(device) + join + rot(sha256(body)).""" def test_canonical_string_shape(self) -> None: body = b"hello" signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) canonical = signer.canonical_string(TS, DEVICE, body) body_hex = hashlib.sha256(body).hexdigest() from raycast_api.signing.transforms import apply_rot expected = ".".join( apply_rot(p, RAYCAST_SPEC.rot_ranges) for p in (TS, DEVICE, body_hex) ) assert canonical == expected class TestSpecDriven: """Changing a spec field changes the signature — proves nothing is hardcoded.""" def test_join_char_change_changes_signature(self) -> None: a = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) b_spec = SigningSpec( rot_fn_name="Sur", signing_fn_name="Nkt", rot_ranges=RAYCAST_SPEC.rot_ranges, join_char=":", ) b = Signer(spec=b_spec, secret=FAKE_SECRET) assert a.sign(timestamp=TS, device_id=DEVICE, body=b"x") != b.sign( timestamp=TS, device_id=DEVICE, body=b"x" ) def test_rot_ranges_change_changes_signature(self) -> None: a = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) b_spec = SigningSpec( rot_fn_name="Sur", signing_fn_name="Nkt", rot_ranges=[ RotRange(0x41, 0x5A, 1), RotRange(0x61, 0x7A, 1), RotRange(0x30, 0x39, 1), ], ) b = Signer(spec=b_spec, secret=FAKE_SECRET) assert a.sign(timestamp=TS, device_id=DEVICE, body=b"x") != b.sign( timestamp=TS, device_id=DEVICE, body=b"x" ) def test_secret_change_changes_signature(self) -> None: a = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) b = Signer(spec=RAYCAST_SPEC, secret="CAFE" + "BABE" * 15) assert a.sign(timestamp=TS, device_id=DEVICE, body=b"x") != b.sign( timestamp=TS, device_id=DEVICE, body=b"x" ) def test_body_bytes_change_changes_signature(self) -> None: signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) sig_a = signer.sign(timestamp=TS, device_id=DEVICE, body=b'{"k":1}') sig_b = signer.sign(timestamp=TS, device_id=DEVICE, body=b'{"k":2}') assert sig_a != sig_b def test_timestamp_string_used_verbatim(self) -> None: signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) a = signer.sign(timestamp="1778858809", device_id=DEVICE, body=b"x") b = signer.sign(timestamp="01778858809", device_id=DEVICE, body=b"x") assert a != b class TestConstruction: def test_empty_rot_ranges_rejected(self) -> None: bad = SigningSpec(rot_fn_name="x", signing_fn_name="y", rot_ranges=[]) with pytest.raises(ValueError): Signer(spec=bad, secret=FAKE_SECRET) def test_signer_is_reusable_for_concurrent_messages(self) -> None: signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) outputs = { signer.sign(timestamp=TS, device_id=DEVICE, body=f"{i}".encode()) for i in range(8) } assert len(outputs) == 8 def test_hmac_uses_utf8_encoded_secret_not_hex_decoded(self) -> None: signer = Signer(spec=RAYCAST_SPEC, secret=FAKE_SECRET) body = b'{"buffer_id":"x"}' canonical = signer.canonical_string(TS, DEVICE, body) expected = _hmac.new( FAKE_SECRET.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256 ).hexdigest() assert signer.sign(timestamp=TS, device_id=DEVICE, body=body) == expected