// test_support.mbt — Shared helpers for the test suite.
//
// These helpers are public so that the blackbox test package and the
// examples can reuse them; they are not part of the intended production API
// surface.
///|
/// Builds a request context for tests with the given headers.
pub fn make_test_request(
method : String,
path : String,
query : String?,
headers : OrderedHeaders,
) -> Result[RequestContext, HsError] {
RequestContext::new_default(
method,
"https",
"example.com",
path,
query,
headers,
None,
)
}
///|
/// Builds an HMAC-SHA256 key record for tests.
pub fn make_test_key(keyid : String, secret : String) -> KeyRecord {
{
keyid,
algorithm: "hmac-sha256",
material: SharedSecret(@utf8.encode(secret)),
}
}
///|
/// Builds a minimal request context for signing tests.
pub fn make_minimal_request() -> RequestContext {
let headers = OrderedHeaders::new()
let _ = headers.append("content-type", "application/json")
let _ = headers.append("date", "Tue, 20 Apr 2021 02:07:55 GMT")
let req = RequestContext::new_default(
"POST",
"https",
"example.com",
"/foo",
Some("param=Value&Pet=dog"),
headers,
None,
)
match req {
Ok(r) => r
Err(_) => abort("test request construction failed")
}
}
///|
/// Signs a minimal request with the given covered components and a fixed
/// created time, returning the signed fields.
pub fn sign_test_request(
components : Array[CoveredComponent],
keyid : String,
created : Int64,
) -> Result[SignedFields, HsError] {
let req = make_minimal_request()
let key = make_test_key(keyid, "test-secret")
let params : SignatureParameters = {
created: Some(created),
expires: None,
keyid: Some(keyid),
alg: None,
nonce: None,
tag: None,
extensions: Array::new(),
}
let options : SignOptions = {
label: "sig1",
components,
parameters: params,
algorithm: "hmac-sha256",
key,
}
sign_request(req, options, Limits::default())
}
///|
/// Builds a default verification context: resolver, policy, clock, nonce
/// store, and limits ready for `verify_request`.
pub fn make_verify_ctx(
key : KeyRecord,
created : Int64,
) -> (
InMemoryKeyResolver,
VerificationPolicy,
FixedClock,
InMemoryNonceStore,
Limits,
) {
let resolver = InMemoryKeyResolver::new()
let _ = resolver.add(key, Limits::default())
let policy = VerificationPolicy::hmac_only()
let clock = FixedClock::new(created)
let nonces = InMemoryNonceStore::new()
(resolver, policy, clock, nonces, Limits::default())
}
///|
/// Verifies a signed request with a fixed clock, returning the report.
pub fn verify_test_request(
request : RequestContext,
signed : SignedFields,
key : KeyRecord,
now : Int64,
) -> Result[VerificationReport, HsError] {
let (resolver, policy, clock, nonces, limits) = make_verify_ctx(key, now)
verify_request(
request,
signed.signature_input,
signed.signature,
resolver,
policy,
clock,
nonces,
limits,
AnyValid,
)
}
///|
/// Asserts that two byte buffers are equal (test helper).
pub fn assert_bytes_eq(a : Bytes, b : Bytes, what : String) -> Unit {
if !constant_time_equal(a, b) {
abort("bytes differ for: " + what)
}
}
///|
/// Verifies a request using a `make_verify_ctx` context. Keeps test bodies
/// short so that canonical formatting does not split long calls.
pub fn verify_with_ctx(
ctx : (
InMemoryKeyResolver,
VerificationPolicy,
FixedClock,
InMemoryNonceStore,
Limits,
),
req : RequestContext,
input : String,
sig : String,
multi : MultiSignaturePolicy,
) -> Result[VerificationReport, HsError] {
verify_request(req, input, sig, ctx.0, ctx.1, ctx.2, ctx.3, ctx.4, multi)
}
///|
/// Verifies a single label using a `make_verify_ctx` context.
pub fn verify_label_with_ctx(
ctx : (
InMemoryKeyResolver,
VerificationPolicy,
FixedClock,
InMemoryNonceStore,
Limits,
),
target : SignTarget,
entry : SignatureInputEntry,
sig : Bytes,
) -> Result[VerifiedSignature, HsError] {
verify_label(target, entry, sig, ctx.0, ctx.1, ctx.2, ctx.3, ctx.4)
}
///|
/// Returns `"ok"` when the request verifies, otherwise the rejection kind.
/// Compact helper for negative tests.
pub fn verify_outcome(
ctx : (
InMemoryKeyResolver,
VerificationPolicy,
FixedClock,
InMemoryNonceStore,
Limits,
),
req : RequestContext,
input : String,
sig : String,
) -> String {
match verify_with_ctx(ctx, req, input, sig, AnyValid) {
Ok(report) => if report.verified.is_empty() { "rejected" } else { "ok" }
Err(e) =>
if e.kind() is SignatureMismatch {
"rejected"
} else {
e.kind_name()
}
}
}
///|
/// Returns the error kind of a single-label verification, or `"ok"`.
pub fn verify_label_outcome(
ctx : (
InMemoryKeyResolver,
VerificationPolicy,
FixedClock,
InMemoryNonceStore,
Limits,
),
target : SignTarget,
entry : SignatureInputEntry,
sig : Bytes,
) -> String {
match verify_label_with_ctx(ctx, target, entry, sig) {
Ok(_) => "ok"
Err(e) => e.kind_name()
}
}