hivehook 0.1.0

Official Hivehook Rust SDK
use hivehook::Webhook;
use std::time::{SystemTime, UNIX_EPOCH};

const SECRET: &str = "whsec_test123";
const PAYLOAD: &str = r#"{"event":"test"}"#;

fn now() -> i64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs() as i64
}

#[test]
fn header_constants() {
    assert_eq!("X-Hivehook-Signature", Webhook::HEADER_SIGNATURE);
    assert_eq!("X-Hivehook-Timestamp", Webhook::HEADER_TIMESTAMP);
    assert_eq!("X-Hivehook-Message-ID", Webhook::HEADER_MESSAGE_ID);
}

#[test]
fn sign_produces_v1_prefix() {
    let sig = Webhook::sign(PAYLOAD, SECRET, 1700000000);
    assert!(sig.starts_with("v1="));
    assert_eq!(67, sig.len());
}

#[test]
fn sign_is_deterministic() {
    let sig1 = Webhook::sign(PAYLOAD, SECRET, 1700000000);
    let sig2 = Webhook::sign(PAYLOAD, SECRET, 1700000000);
    assert_eq!(sig1, sig2);
}

#[test]
fn different_secrets_produce_different_signatures() {
    let sig1 = Webhook::sign(PAYLOAD, SECRET, 1700000000);
    let sig2 = Webhook::sign(PAYLOAD, "different", 1700000000);
    assert_ne!(sig1, sig2);
}

#[test]
fn verify_valid() {
    let ts = now();
    let sig = Webhook::sign(PAYLOAD, SECRET, ts);
    assert!(Webhook::verify(PAYLOAD, SECRET, &sig, ts, Some(300)));
}

#[test]
fn reject_wrong_secret() {
    let ts = now();
    let sig = Webhook::sign(PAYLOAD, SECRET, ts);
    assert!(!Webhook::verify(PAYLOAD, "wrong", &sig, ts, Some(300)));
}

#[test]
fn reject_expired() {
    let ts = now() - 600;
    let sig = Webhook::sign(PAYLOAD, SECRET, ts);
    assert!(!Webhook::verify(PAYLOAD, SECRET, &sig, ts, Some(300)));
}

#[test]
fn skip_timestamp_check() {
    let ts = now() - 600;
    let sig = Webhook::sign(PAYLOAD, SECRET, ts);
    assert!(Webhook::verify(PAYLOAD, SECRET, &sig, ts, None));
}

#[test]
fn rotation_primary() {
    let ts = now();
    let sig = Webhook::sign(PAYLOAD, "primary", ts);
    assert!(Webhook::verify_with_rotation(PAYLOAD, "primary", Some("secondary"), &sig, ts, Some(300)));
}

#[test]
fn rotation_secondary() {
    let ts = now();
    let sig = Webhook::sign(PAYLOAD, "secondary", ts);
    assert!(Webhook::verify_with_rotation(PAYLOAD, "primary", Some("secondary"), &sig, ts, Some(300)));
}

#[test]
fn rotation_reject() {
    let ts = now();
    assert!(!Webhook::verify_with_rotation(PAYLOAD, "primary", Some("secondary"), "v1=bad", ts, Some(300)));
}