KeiSeiKit-1.0/_primitives/_rust/kei-auth-webauthn/tests/webauthn_smoke.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

102 lines
3.8 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 <author org>
//
//! Smoke tests for `kei-auth-webauthn`. Verify call shape only — the
//! full WebAuthn cryptographic ceremony requires a real authenticator
//! and is exercised by webauthn-rs upstream.
use kei_auth_webauthn::{build_webauthn, Error, WebauthnProvider};
use kei_runtime_core::dna::HasDna;
use kei_runtime_core::traits::auth::{AuthChallenge, AuthProvider};
use uuid::Uuid;
fn provider() -> WebauthnProvider {
WebauthnProvider::new("localhost", "http://localhost:8080", "kei-test")
.expect("provider builds with valid origin")
}
#[test]
fn provider_dna_has_wn_cap() {
let p = provider();
let dna = p.dna();
assert_eq!(dna.role(), "primitive");
let caps = dna.caps();
assert!(caps.contains("WN"), "DNA caps must contain WN backend tag, got {caps}");
assert!(caps.contains("AP"), "DNA caps must contain AP trait tag, got {caps}");
assert!(caps.contains("PR"), "DNA caps must contain PR role tag, got {caps}");
assert!(p.parent_dna().is_none(), "default constructor leaves parent unset");
assert_eq!(p.provider_name(), "webauthn");
assert!(p.is_passwordless(), "WebAuthn is passwordless by definition");
}
#[test]
fn build_webauthn_validates_origin() {
// Valid origin → Ok.
assert!(build_webauthn("localhost", "http://localhost:3000", "ok").is_ok());
// Garbage URL → Error::Url.
let err = build_webauthn("example.com", "not-a-url", "bad")
.expect_err("invalid origin must error");
assert!(matches!(err, Error::Url(_)), "expected Url variant, got {err:?}");
}
#[test]
fn start_registration_returns_credential_creation_options() {
let p = provider();
let user_id = Uuid::new_v4();
let (challenge, state) = p
.start_registration(user_id, "alice", "Alice Example")
.expect("registration ceremony starts");
// Sanity: the challenge response carries the rp name we configured
// and the user info we passed in. Use serde_json round-trip to peek
// at the structure without depending on private fields.
let challenge_json =
serde_json::to_value(&challenge).expect("CreationChallengeResponse serialises");
assert!(
challenge_json.is_object(),
"challenge must serialise to a JSON object"
);
// PasskeyRegistration is opaque ceremony state held in memory by
// the caller between leg 1 and leg 2 of the registration ceremony.
// We assert only that the type is constructed and Send/Sync-safe.
let _: &dyn Send = &state;
}
#[tokio::test]
async fn trait_issue_challenge_returns_ok_and_documents_redirect() {
let p = provider();
// Per the trait-extension convention in lib.rs, calling the trait
// method directly is a misuse — the primitive returns a Provider
// error pointing at the explicit helpers.
let dummy = AuthChallenge::SshKeySig {
key_id: "register".into(),
signature: String::new(),
};
let issue_err = p
.issue_challenge(&dummy)
.await
.expect_err("issue_challenge over WebAuthn must error with redirect msg");
let msg = format!("{issue_err}");
assert!(
msg.contains("start_registration") || msg.contains("start_authentication"),
"issue_challenge error must redirect to ceremony helpers, got: {msg}"
);
let verify_err = p
.verify(&dummy)
.await
.expect_err("verify over WebAuthn must error with redirect msg");
let msg = format!("{verify_err}");
assert!(
msg.contains("finish_registration") || msg.contains("finish_authentication"),
"verify error must redirect to ceremony helpers, got: {msg}"
);
// revoke is a no-op (operator-managed).
let revoked = p.revoke(p.dna()).await;
assert!(revoked.is_ok(), "revoke must be Ok (no-op)");
}