use vectorpin::attestation::AttestationError;
use vectorpin::{Pin, Signer, SignerError, Verifier, VerifyError};
use zeroize::Zeroizing;
#[test]
fn private_key_bytes_is_zeroizing() {
let signer = Signer::generate("kid".into()).expect("non-empty kid");
let seed: Zeroizing<[u8; 32]> = signer.private_key_bytes();
let _bytes: &[u8; 32] = &seed;
assert_eq!(seed.len(), 32);
}
#[test]
fn signer_generate_rejects_empty_kid() {
let res = Signer::generate(String::new());
assert!(
matches!(res, Err(SignerError::EmptyKeyId)),
"expected EmptyKeyId, got {:?}",
res.err()
);
}
#[test]
fn signer_generate_accepts_non_empty_kid() {
let res = Signer::generate("k".into());
assert!(res.is_ok());
}
#[test]
fn verifier_add_key_rejects_invalid_public_key() {
let mut bad = [0u8; 32];
bad[0] = 0x02;
let mut verifier = Verifier::new();
let res = verifier.add_key("kid", bad);
assert!(
matches!(res, Err(VerifyError::KeyDecodeFailed(_))),
"expected KeyDecodeFailed, got {:?}",
res
);
assert_eq!(
verifier.key_count(),
0,
"rejected key must not be registered"
);
}
#[test]
fn verifier_add_key_accepts_valid_public_key() {
let signer = Signer::generate("kid".into()).unwrap();
let mut verifier = Verifier::new();
verifier
.add_key("kid", signer.public_key_bytes())
.expect("valid pubkey");
assert_eq!(verifier.key_count(), 1);
}
#[test]
fn pin_from_json_rejects_non_string_extra_value() {
let bad = serde_json::json!({
"v": 2,
"model": "m",
"source_hash": format!("sha256:{}", "0".repeat(64)),
"vec_hash": format!("sha256:{}", "1".repeat(64)),
"vec_dtype": "f32",
"vec_dim": 1,
"ts": "2026-05-05T12:00:00Z",
"extra": {"k": 1},
"kid": "k",
"sig": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
});
let res = Pin::from_value(bad);
assert!(
matches!(&res, Err(AttestationError::InvalidField { field, .. }) if field.starts_with("extra")),
"expected InvalidField with field starting with 'extra', got {:?}",
res
);
}
#[test]
fn pin_from_json_accepts_string_extra_value() {
use std::collections::BTreeMap;
use vectorpin::signer::PinOptions;
let signer = Signer::generate("k".into()).unwrap();
let v: Vec<f32> = vec![1.0, 2.0, 3.0];
let mut extra = BTreeMap::new();
extra.insert("k".to_owned(), "v".to_owned());
let opts = PinOptions {
extra,
..PinOptions::default()
};
let pin = signer
.pin_with_options("hello", "m", v.as_slice(), opts)
.unwrap();
let wire = pin.to_json();
let round = Pin::from_json(&wire).expect("string-valued extra round-trips");
assert_eq!(round.header.extra.get("k").map(String::as_str), Some("v"));
}
#[test]
fn pin_from_json_rejects_trailing_garbage() {
let signer = Signer::generate("k".into()).unwrap();
let v: Vec<f32> = vec![1.0, 2.0, 3.0];
let pin = signer.pin("hello", "m", v.as_slice()).unwrap();
let mut wire = pin.to_json();
wire.push('\u{0000}');
wire.push_str("trailing");
let res = Pin::from_json(&wire);
assert!(
res.is_err(),
"trailing garbage after valid JSON must be rejected, got Ok"
);
}
#[test]
fn pin_normal_dim_still_round_trips_after_checked_cast() {
let signer = Signer::generate("k".into()).unwrap();
let v: Vec<f32> = vec![0.5; 1024];
let pin = signer.pin("hello", "m", v.as_slice()).unwrap();
assert_eq!(pin.header.vec_dim, 1024);
let mut verifier = Verifier::new();
verifier
.add_key(signer.key_id(), signer.public_key_bytes())
.unwrap();
verifier
.verify_full(&pin, Some("hello"), Some(v.as_slice()), None)
.unwrap();
}