use ash_core::{
ash_build_proof, ash_verify_proof, ash_derive_client_secret,
ash_canonicalize_json, ash_canonicalize_query, ash_canonicalize_urlencoded,
ash_hash_body, ash_normalize_binding,
ash_build_proof_scoped, ash_verify_proof_scoped,
ash_build_proof_unified, ash_verify_proof_unified,
ash_generate_nonce, ash_generate_context_id,
ash_validate_timestamp, ash_timing_safe_equal,
};
mod quick_start {
use super::*;
#[test]
fn test_quick_start_example() {
let nonce = ash_generate_nonce(32).unwrap();
assert_eq!(nonce.len(), 64);
let context_id = ash_generate_context_id().unwrap();
assert!(context_id.starts_with("ash_"));
let binding = ash_normalize_binding("POST", "/api/users", "").unwrap();
assert_eq!(binding, "POST|/api/users|");
let secret = ash_derive_client_secret(&nonce, &context_id, &binding).unwrap();
assert_eq!(secret.len(), 64);
let body = r#"{"name":"John"}"#;
let body_hash = ash_hash_body(body);
assert_eq!(body_hash.len(), 64);
let timestamp = chrono::Utc::now().timestamp().to_string();
let proof = ash_build_proof(&secret, ×tamp, &binding, &body_hash).unwrap();
assert_eq!(proof.len(), 64);
let valid = ash_verify_proof(&nonce, &context_id, &binding, ×tamp, &body_hash, &proof).unwrap();
assert!(valid);
}
}
mod basic_usage {
use super::*;
#[test]
fn test_json_canonicalization_example() {
let json = r#"{"z":1,"a":2,"nested":{"b":3,"a":4}}"#;
let canonical = ash_canonicalize_json(json).unwrap();
assert_eq!(canonical, r#"{"a":2,"nested":{"a":4,"b":3},"z":1}"#);
}
#[test]
fn test_query_string_canonicalization_example() {
let query = "z=3&a=1&b=2";
let canonical = ash_canonicalize_query(query).unwrap();
assert_eq!(canonical, "a=1&b=2&z=3");
}
#[test]
fn test_binding_normalization_example() {
let binding = ash_normalize_binding("post", "/api/users/", "page=1&limit=10").unwrap();
assert!(binding.starts_with("POST|"));
assert!(binding.contains("/api/users|"));
}
#[test]
fn test_body_hashing_example() {
let body = r#"{"user":"john","action":"login"}"#;
let hash = ash_hash_body(body);
assert_eq!(hash.len(), 64);
assert!(hash.chars().all(|c| c.is_ascii_hexdigit()));
}
}
mod proof_lifecycle {
use super::*;
#[test]
fn test_full_proof_lifecycle_example() {
let nonce = ash_generate_nonce(32).unwrap();
let context_id = ash_generate_context_id().unwrap();
let method = "POST";
let path = "/api/transfer";
let query = "confirm=true";
let body = r#"{"from":"acc1","to":"acc2","amount":100}"#;
let binding = ash_normalize_binding(method, path, query).unwrap();
let secret = ash_derive_client_secret(&nonce, &context_id, &binding).unwrap();
let body_hash = ash_hash_body(body);
let timestamp = chrono::Utc::now().timestamp().to_string();
let proof = ash_build_proof(&secret, ×tamp, &binding, &body_hash).unwrap();
let ts_valid = ash_validate_timestamp(×tamp, 300, 60);
assert!(ts_valid.is_ok());
let server_binding = ash_normalize_binding(method, path, query).unwrap();
let server_body_hash = ash_hash_body(body);
let valid = ash_verify_proof(&nonce, &context_id, &server_binding, ×tamp, &server_body_hash, &proof).unwrap();
assert!(valid);
}
}
mod scoped_proofs {
use super::*;
#[test]
fn test_scoped_proof_example() {
let nonce = ash_generate_nonce(32).unwrap();
let context_id = ash_generate_context_id().unwrap();
let binding = ash_normalize_binding("POST", "/api/payment", "").unwrap();
let secret = ash_derive_client_secret(&nonce, &context_id, &binding).unwrap();
let payload = r#"{"amount":100,"currency":"USD","memo":"Payment for order #123"}"#;
let scope = vec!["amount", "currency"];
let timestamp = chrono::Utc::now().timestamp().to_string();
let (proof, scope_hash) = ash_build_proof_scoped(&secret, ×tamp, &binding, payload, &scope).unwrap();
let valid = ash_verify_proof_scoped(&nonce, &context_id, &binding, ×tamp, payload, &scope, &scope_hash, &proof).unwrap();
assert!(valid);
let modified_payload = r#"{"amount":100,"currency":"USD","memo":"Updated memo"}"#;
let valid_modified = ash_verify_proof_scoped(&nonce, &context_id, &binding, ×tamp, modified_payload, &scope, &scope_hash, &proof).unwrap();
assert!(valid_modified, "Modified non-scoped field should verify");
let tampered_payload = r#"{"amount":10000,"currency":"USD","memo":"Payment for order #123"}"#;
let invalid = ash_verify_proof_scoped(&nonce, &context_id, &binding, ×tamp, tampered_payload, &scope, &scope_hash, &proof).unwrap();
assert!(!invalid, "Modified scoped field should fail");
}
}
mod chained_proofs {
use super::*;
#[test]
fn test_chained_proof_example() {
let nonce = ash_generate_nonce(32).unwrap();
let context_id = ash_generate_context_id().unwrap();
let timestamp = chrono::Utc::now().timestamp().to_string();
let binding1 = ash_normalize_binding("POST", "/api/transfer/init", "").unwrap();
let secret1 = ash_derive_client_secret(&nonce, &context_id, &binding1).unwrap();
let payload1 = r#"{"from":"acc1","to":"acc2","amount":100}"#;
let result1 = ash_build_proof_unified(&secret1, ×tamp, &binding1, payload1, &["amount"], None).unwrap();
let binding2 = ash_normalize_binding("POST", "/api/transfer/confirm", "").unwrap();
let secret2 = ash_derive_client_secret(&nonce, &context_id, &binding2).unwrap();
let payload2 = r#"{"confirmed":true}"#;
let result2 = ash_build_proof_unified(&secret2, ×tamp, &binding2, payload2, &[], Some(&result1.proof)).unwrap();
assert!(!result2.chain_hash.is_empty());
let valid = ash_verify_proof_unified(
&nonce, &context_id, &binding2, ×tamp, payload2,
&result2.proof, &[], &result2.scope_hash,
Some(&result1.proof), &result2.chain_hash
).unwrap();
assert!(valid);
}
}
mod error_handling {
use super::*;
#[test]
fn test_error_handling_example() {
let result = ash_derive_client_secret("short", "ctx", "GET|/|");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(!error.to_string().is_empty());
let result = ash_canonicalize_json("not json");
assert!(result.is_err());
let old_ts = (chrono::Utc::now().timestamp() - 3600).to_string();
let result = ash_validate_timestamp(&old_ts, 300, 60);
assert!(result.is_err());
}
}
mod urlencoded_forms {
use super::*;
#[test]
fn test_form_data_example() {
let form_data = "username=john&password=secret123&remember=true";
let canonical = ash_canonicalize_urlencoded(form_data).unwrap();
assert!(canonical.starts_with("password="));
assert!(canonical.contains("&remember="));
assert!(canonical.contains("&username="));
}
}
mod timing_safe {
use super::*;
#[test]
fn test_timing_safe_comparison_example() {
let secret1 = "a".repeat(64);
let secret2 = "a".repeat(64);
let secret3 = "b".repeat(64);
assert!(ash_timing_safe_equal(secret1.as_bytes(), secret2.as_bytes()));
assert!(!ash_timing_safe_equal(secret1.as_bytes(), secret3.as_bytes()));
}
}
mod complete_flow {
use super::*;
#[test]
fn test_complete_api_flow_example() {
let nonce = "a".repeat(64); let context_id = "ctx_user123_req456";
let method = "POST";
let path = "/api/orders";
let query = "";
let body = r#"{"product_id":"prod_abc","quantity":2,"price":29.99}"#;
let binding = ash_normalize_binding(method, path, query).unwrap();
let secret = ash_derive_client_secret(&nonce, &context_id, &binding).unwrap();
let body_hash = ash_hash_body(body);
let timestamp = chrono::Utc::now().timestamp().to_string();
let proof = ash_build_proof(&secret, ×tamp, &binding, &body_hash).unwrap();
let _ = ash_validate_timestamp(×tamp, 300, 60).unwrap();
let server_binding = ash_normalize_binding(method, path, query).unwrap();
let server_body_hash = ash_hash_body(body);
let valid = ash_verify_proof(&nonce, &context_id, &server_binding, ×tamp, &server_body_hash, &proof).unwrap();
assert!(valid, "Proof should be valid");
}
}
mod nested_scoping {
use super::*;
#[test]
fn test_nested_field_scoping_example() {
let nonce = ash_generate_nonce(32).unwrap();
let context_id = ash_generate_context_id().unwrap();
let binding = ash_normalize_binding("POST", "/api/user", "").unwrap();
let timestamp = chrono::Utc::now().timestamp().to_string();
let secret = ash_derive_client_secret(&nonce, &context_id, &binding).unwrap();
let payload = r#"{
"user": {
"name": "John",
"email": "john@example.com",
"preferences": {
"theme": "dark",
"notifications": true
}
},
"action": "update"
}"#;
let scope = vec!["user.email", "action"];
let (proof, scope_hash) = ash_build_proof_scoped(&secret, ×tamp, &binding, payload, &scope).unwrap();
let valid = ash_verify_proof_scoped(&nonce, &context_id, &binding, ×tamp, payload, &scope, &scope_hash, &proof).unwrap();
assert!(valid);
}
}