use super::*;
use crate::acl::Role;
use crate::operations::persona::{PersonaStores, Relationship, persona_key_for};
use crate::operations::room_issuance::{SigningContext, VtaKeySigner};
use crate::test_support::build_signing_test_app_state;
use chrono::{TimeDelta, Utc};
use dtg_credentials::{DTGCredential, IssuerScope};
use trust_tasks_rs::TypeUri;
use vta_sdk::provision_client::EphemeralSetupKey;
use vta_sdk::trust_task_proof::TrustTaskVmResolver;
use vta_sdk::trust_task_sign::{HolderKey, sign_in_place_with, sign_object_for_assertion};
use vta_sdk::trust_tasks as uris;
use vti_witness::config::SessionLimits;
use vti_witness::dispatch::{IdentityContext, Outcome, answer};
use vti_witness::store::{self as wstore, WitnessStore};
use vti_witness::witness::{WitnessPolicy, Witnessing};
const CTX: &str = "personal";
const OTHER: &str = "other";
fn claims(role: Role, ctx: &str) -> AuthClaims {
AuthClaims {
did: "did:key:z6MkClientWalletExample".to_string(),
role,
allowed_contexts: vec![ctx.to_string()],
session_id: "test-session".into(),
access_expires_at: 0,
issued_at: 0,
amr: Vec::new(),
acr: String::new(),
}
}
fn doc(uri: &str, payload: Value) -> TrustTask<Value> {
let uri: TypeUri = uri.parse().expect("witness uri");
TrustTask::new(format!("urn:uuid:{}", uuid::Uuid::new_v4()), uri, payload)
}
fn body(out: &TrustTaskOutcome) -> Value {
serde_json::from_slice(&out.body).expect("response is JSON")
}
fn ok(out: &TrustTaskOutcome) -> Value {
let b = body(out);
assert!(out.status.is_success(), "expected success, got {b:#}");
b["payload"].clone()
}
fn code(out: &TrustTaskOutcome) -> String {
body(out)
.pointer("/payload/code")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string()
}
fn urn() -> String {
format!("urn:uuid:{}", uuid::Uuid::new_v4())
}
struct Key {
did: String,
multibase: String,
}
impl Key {
fn new() -> Self {
let k = EphemeralSetupKey::generate().expect("key");
Self {
did: k.did.clone(),
multibase: k.private_key_multibase().to_string(),
}
}
fn holder(&self) -> HolderKey {
HolderKey::from_did_key(&self.did, &self.multibase).expect("holder")
}
}
struct Witness {
key: Key,
identity: IdentityContext,
_dir: tempfile::TempDir,
}
impl Witness {
fn new(communities: Vec<String>) -> Self {
let key = Key::new();
let dir = tempfile::tempdir().expect("tempdir");
let store = vti_common::store::test_fixture::open_with_keyspaces(dir.path(), wstore::ALL)
.expect("witness store");
let witnessing = Witnessing::new(
WitnessPolicy::new(
communities,
None,
vec![key.did.clone()],
SessionLimits::default(),
),
WitnessStore::from_store(&store).expect("witness keyspaces"),
);
let identity =
IdentityContext::new("w", key.did.clone(), key.holder(), witnessing).expect("identity");
Self {
key,
identity,
_dir: dir,
}
}
fn did(&self) -> &str {
&self.key.did
}
async fn carry(&self, sender: &str, document: &Value) -> Value {
match answer(
&self.identity,
sender,
&serde_json::to_vec(document).unwrap(),
&TrustTaskVmResolver::did_key_only(),
Utc::now(),
)
.await
{
Outcome::Reply(reply) => reply,
Outcome::Drop(reason) => panic!("the witness dropped the document: {reason:?}"),
}
}
}
struct World {
state: AppState,
_dir: tempfile::TempDir,
auth: AuthClaims,
witness: Witness,
community: Key,
relationship: String,
counterparty: String,
member: String,
}
impl World {
async fn new(member_link: bool) -> Self {
let (state, dir) = build_signing_test_app_state().await;
for ctx in [CTX, OTHER] {
crate::contexts::create_context(&state.contexts_ks, ctx, ctx)
.await
.expect("create context");
}
let community = Key::new();
let witness = Witness::new(vec![community.did.clone()]);
let raw = crate::operations::persona::test_fixture::mint_key(
&state.keys_ks,
&state.internal_ks,
&state.contexts_ks,
&state.acl_ks,
&state.seed_store,
&state.audit_sink,
CTX,
"relationship-key",
)
.await;
let relationship = format!(
"did:peer:2.V{}",
vta_sdk::did_key::ed25519_multibase_pubkey(&raw)
);
let member = crate::operations::persona::test_fixture::did_key_persona(
&state.keys_ks,
&state.internal_ks,
&state.contexts_ks,
&state.acl_ks,
&state.seed_store,
&state.audit_sink,
CTX,
"member-key",
)
.await
.did;
let counterparty = Key::new().did;
let auth = claims(Role::Admin, CTX);
let w = Self {
state,
_dir: dir,
auth,
witness,
community,
relationship,
counterparty,
member,
};
let vrc = w.vrc(&w.counterparty.clone()).await;
w.store("vrc-1", vrc).await;
let member_of = if member_link {
w.member.clone()
} else {
w.relationship.clone()
};
let vmc = w.vmc(&member_of).await;
w.store("vmc-1", vmc).await;
w
}
fn signing(&self) -> SigningContext<'_> {
SigningContext {
keys_ks: &self.state.keys_ks,
imported_ks: &self.state.imported_ks,
internal_ks: &self.state.internal_ks,
contexts_ks: &self.state.contexts_ks,
acl_ks: &self.state.acl_ks,
seed_store: &self.state.seed_store,
audit: &self.state.audit_sink,
auth: &self.auth,
}
}
async fn vrc(&self, subject: &str) -> Value {
let key = persona_key_for(
PersonaStores::from_state(&self.state),
&self.auth,
&self.relationship,
Relationship::AssertionMethod,
)
.await
.unwrap()
.expect("the VTA holds the relationship key");
let vc = DTGCredential::new_vrc(
self.relationship.clone(),
IssuerScope::Pairwise,
subject.to_string(),
Utc::now() - TimeDelta::minutes(5),
None,
)
.with_id(urn());
let mut vc = serde_json::to_value(vc).unwrap();
let signer = VtaKeySigner::new(self.signing(), &key.key_id, &key.verification_method);
let proof = affinidi_data_integrity::DataIntegrityProof::sign(
&vc,
&signer,
affinidi_data_integrity::SignOptions::new().with_proof_purpose("assertionMethod"),
)
.await
.expect("sign the VRC");
vc["proof"] = serde_json::to_value(proof).unwrap();
vc
}
async fn vmc(&self, member: &str) -> Value {
let vc = DTGCredential::new_vmc(
self.community.did.clone(),
member.to_string(),
Utc::now() - TimeDelta::days(1),
None,
false,
)
.with_id(urn());
let mut v = serde_json::to_value(vc).unwrap();
sign_object_for_assertion(&mut v, &self.community.holder())
.await
.unwrap();
v
}
async fn store(&self, id: &str, vc: Value) {
use crate::vault::model::{CredentialFormat, CredentialStatus, StoredCredential};
crate::vault::storage::put(
&self.state.vault_ks,
&StoredCredential {
id: id.into(),
format: CredentialFormat::EddsaJcs2022,
types: Vec::new(),
schema_id: None,
community_did: None,
context_id: Some(CTX.into()),
subject_did: None,
issuer_did: None,
purpose: None,
status: CredentialStatus::Valid,
valid_from: None,
valid_until: None,
received_at: Utc::now().to_rfc3339(),
source: None,
tags: Default::default(),
body: serde_json::to_vec(&vc).unwrap(),
lifecycle: vti_common::vault::VaultStatus::Active,
archived_at: None,
deleted_at: None,
grace_until: None,
},
)
.await
.expect("store the credential");
}
fn open_payload(&self, member_link: bool) -> Value {
let mut p = json!({
"witness": self.witness.did(),
"relationshipDid": self.relationship,
"counterpartyDid": self.counterparty,
"relationshipThreadId": "urn:uuid:9e8d7c6b-0000-4000-8000-000000000a01",
"relationshipCredentialId": "vrc-1",
"membership": { "credentialId": "vmc-1" },
});
if member_link {
p["membership"]["memberDid"] = json!(self.member);
}
p
}
async fn open_as(&self, auth: &AuthClaims, payload: Value) -> TrustTaskOutcome {
handle_open(
&self.state,
auth,
doc(uris::TASK_VTA_WITNESS_OPEN_0_2, payload),
)
.await
}
async fn present_as(
&self,
auth: &AuthClaims,
session: &str,
response: &Value,
) -> TrustTaskOutcome {
handle_present(
&self.state,
auth,
doc(
uris::TASK_VTA_WITNESS_PRESENT_0_2,
json!({ "sessionId": session, "response": response }),
),
)
.await
}
async fn accept_as(
&self,
auth: &AuthClaims,
session: &str,
response: &Value,
) -> TrustTaskOutcome {
handle_accept(
&self.state,
auth,
doc(
uris::TASK_VTA_WITNESS_ACCEPT_0_2,
json!({ "sessionId": session, "response": response }),
),
)
.await
}
async fn run_to_outcome(&self, member_link: bool) -> (String, Value, Value, Value) {
let opened = ok(&self
.open_as(&self.auth, self.open_payload(member_link))
.await);
let session = opened["sessionId"].as_str().unwrap().to_string();
let session_doc = opened["document"].clone();
let session_answer = self.witness.carry(&self.relationship, &session_doc).await;
let presented = ok(&self.present_as(&self.auth, &session, &session_answer).await);
let outcome = self
.witness
.carry(&self.relationship, &presented["document"])
.await;
assert!(
outcome["type"]
.as_str()
.is_some_and(|t| t.ends_with("submit/0.2#response")),
"the witness accepted the submission: {outcome:#}"
);
(session, session_doc, session_answer, outcome)
}
}
#[tokio::test]
async fn witnessed_end_to_end_through_the_member_link() {
let w = World::new(true).await;
let opened = ok(&w
.open_as(&w.auth, {
let mut p = w.open_payload(true);
p["storeAs"] = json!("vwc-alice-to-bob");
p
})
.await);
let session = opened["sessionId"].as_str().unwrap().to_string();
let session_doc = opened["document"].clone();
assert_eq!(session_doc["id"], session);
assert_eq!(session_doc["threadId"], session);
assert_eq!(session_doc["issuer"], w.relationship);
assert_eq!(session_doc["recipient"], w.witness.did());
assert_eq!(
session_doc["payload"]["parties"],
json!([w.relationship, w.counterparty])
);
assert!(
session_doc["proof"]["verificationMethod"]
.as_str()
.unwrap()
.starts_with(&format!("{}#", w.relationship)),
"signed as the relationship DID, never as the VTA"
);
let session_answer = w.witness.carry(&w.relationship, &session_doc).await;
let presented = ok(&w.present_as(&w.auth, &session, &session_answer).await);
let submit = &presented["document"];
assert_eq!(submit["issuer"], w.relationship);
assert_eq!(submit["threadId"], session);
assert!(
submit["payload"].get("ext").is_none(),
"no vendor-extension carriage: {submit:#}"
);
let member_vp = &submit["payload"]["memberPresentation"];
assert_eq!(
member_vp["holder"], w.member,
"the member presentation is the member DID's"
);
assert_eq!(
submit["payload"]["vp"]["verifiableCredential"]
.as_array()
.unwrap()
.len(),
1,
"under the member link the VMC rides in the member presentation, not beside the VRC"
);
let outcome = w.witness.carry(&w.relationship, submit).await;
let accepted = ok(&w.accept_as(&w.auth, &session, &outcome).await);
assert_eq!(accepted["credentialId"], "vwc-alice-to-bob");
assert_eq!(accepted["witness"], w.witness.did());
assert_eq!(accepted["evidence"]["session"], session_doc);
assert_eq!(accepted["evidence"]["outcome"], outcome);
assert_eq!(
accepted["vwcDigestMultibase"],
outcome["payload"]["vwcDigestMultibase"]
);
let stored = crate::vault::storage::get(&w.state.vault_ks, "vwc-alice-to-bob")
.await
.unwrap()
.expect("the VWC is in the vault");
assert_eq!(stored.context_id.as_deref(), Some(CTX));
assert_eq!(stored.issuer_did.as_deref(), Some(w.witness.did()));
assert_eq!(
serde_json::from_slice::<Value>(&stored.body).unwrap(),
outcome["payload"]["vwc"]
);
let tag = |k: &str| serde_json::from_str::<Value>(stored.tags.get(k).unwrap()).unwrap();
assert_eq!(
tag(crate::operations::witness::EVIDENCE_SESSION_TAG),
session_doc
);
assert_eq!(
tag(crate::operations::witness::EVIDENCE_OUTCOME_TAG),
outcome
);
let again = ok(&w.accept_as(&w.auth, &session, &outcome).await);
assert_eq!(again["credentialId"], accepted["credentialId"]);
assert_eq!(again["evidence"], accepted["evidence"]);
let out = w.present_as(&w.auth, &session, &session_answer).await;
assert_eq!(code(&out), "vta/witness/present:sessionUnknown");
}
#[tokio::test]
async fn witnessed_with_membership_naming_the_relationship_did() {
let w = World::new(false).await;
let (session, _, _, outcome) = w.run_to_outcome(false).await;
let accepted = ok(&w.accept_as(&w.auth, &session, &outcome).await);
assert_eq!(
accepted["credentialId"], outcome["payload"]["vwc"]["id"],
"without storeAs the handle is the VWC's id"
);
}
#[tokio::test]
async fn a_repeated_present_is_safe() {
let w = World::new(true).await;
let opened = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let session = opened["sessionId"].as_str().unwrap().to_string();
let answer = w.witness.carry(&w.relationship, &opened["document"]).await;
let first = ok(&w.present_as(&w.auth, &session, &answer).await);
let second = ok(&w.present_as(&w.auth, &session, &answer).await);
assert_ne!(first["document"]["id"], second["document"]["id"]);
let outcome = w.witness.carry(&w.relationship, &second["document"]).await;
ok(&w.accept_as(&w.auth, &session, &outcome).await);
let refused = w.witness.carry(&w.relationship, &first["document"]).await;
assert!(
refused["payload"]["code"]
.as_str()
.is_some_and(|c| c.ends_with("challengeMismatch")),
"{refused:#}"
);
}
#[tokio::test]
async fn an_answer_from_anywhere_else_is_refused() {
let w = World::new(true).await;
let a = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let b = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let session_a = a["sessionId"].as_str().unwrap();
let answer_b = w.witness.carry(&w.relationship, &b["document"]).await;
let out = w.present_as(&w.auth, session_a, &answer_b).await;
assert_eq!(code(&out), "vta/witness/present:responseInvalid");
let answer_a = w.witness.carry(&w.relationship, &a["document"]).await;
let mut tampered = answer_a.clone();
tampered["payload"]["challenge"] = json!("00".repeat(32));
let out = w.present_as(&w.auth, session_a, &tampered).await;
assert_eq!(code(&out), "vta/witness/present:responseInvalid");
let impostor = Key::new();
let mut forged: TrustTask<Value> = serde_json::from_value(answer_a.clone()).unwrap();
forged.issuer = Some(impostor.did.clone());
forged.proof = None;
sign_in_place_with(&mut forged, &impostor.holder())
.await
.unwrap();
let out = w
.present_as(&w.auth, session_a, &serde_json::to_value(forged).unwrap())
.await;
assert_eq!(code(&out), "vta/witness/present:responseInvalid");
ok(&w.present_as(&w.auth, session_a, &answer_a).await);
let refusal = json!({
"id": urn(), "type": "https://trusttasks.org/spec/trust-task-error/0.5",
"issuer": w.witness.did(), "recipient": w.relationship, "threadId": session_a,
"payload": { "code": "witness/session/submit:presentationInvalid" },
});
let out = w.accept_as(&w.auth, session_a, &refusal).await;
assert_eq!(code(&out), "vta/witness/accept:responseInvalid");
}
#[tokio::test]
async fn a_vwc_from_another_session_is_refused() {
let w = World::new(true).await;
let (_, _, _, other_outcome) = w.run_to_outcome(true).await;
let opened = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let session = opened["sessionId"].as_str().unwrap().to_string();
let answer = w.witness.carry(&w.relationship, &opened["document"]).await;
ok(&w.present_as(&w.auth, &session, &answer).await);
let mut forged: TrustTask<Value> = serde_json::from_value(json!({
"id": urn(),
"type": <trust_tasks_rs::specs::witness::session::submit::v0_2::Response
as trust_tasks_rs::Payload>::TYPE_URI,
"issuer": w.witness.did(),
"recipient": w.relationship,
"issuedAt": Utc::now().to_rfc3339(),
"threadId": session,
"payload": other_outcome["payload"].clone(),
}))
.unwrap();
sign_in_place_with(&mut forged, &w.witness.key.holder())
.await
.unwrap();
let out = w
.accept_as(&w.auth, &session, &serde_json::to_value(forged).unwrap())
.await;
assert_eq!(code(&out), "vta/witness/accept:vwcInvalid");
assert!(
body(&out).to_string().contains("taskContext"),
"{:#}",
body(&out)
);
let other_id = other_outcome["payload"]["vwc"]["id"].as_str().unwrap();
assert!(
crate::vault::storage::get(&w.state.vault_ks, other_id)
.await
.unwrap()
.is_none(),
"the earlier session was never accepted, and this one stored nothing"
);
}
#[tokio::test]
async fn session_unknown_says_nothing_about_why() {
let w = World::new(true).await;
let opened = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let session = opened["sessionId"].as_str().unwrap();
let answer = w.witness.carry(&w.relationship, &opened["document"]).await;
let unknown = w.present_as(&w.auth, "urn:uuid:nobody", &answer).await;
let elsewhere = w
.present_as(&claims(Role::Admin, OTHER), session, &answer)
.await;
let wrong_state = w.accept_as(&w.auth, session, &answer).await;
assert_eq!(code(&unknown), "vta/witness/present:sessionUnknown");
assert_eq!(code(&elsewhere), "vta/witness/present:sessionUnknown");
assert_eq!(code(&wrong_state), "vta/witness/accept:sessionUnknown");
assert_eq!(
body(&unknown)["payload"]["message"],
body(&elsewhere)["payload"]["message"]
);
}
#[tokio::test]
async fn a_session_opened_at_another_version_is_unknown() {
let w = World::new(true).await;
let opened = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let session = opened["sessionId"].as_str().unwrap();
let answer = w.witness.carry(&w.relationship, &opened["document"]).await;
let key = format!("session:{session}");
let raw = w
.state
.witness_sessions_ks
.get_raw(key.clone())
.await
.unwrap()
.expect("the open session's record");
let mut record: Value = serde_json::from_slice(&raw).unwrap();
record["sessionDocument"]["type"] = json!("https://trusttasks.org/spec/witness/session/0.1");
w.state
.witness_sessions_ks
.insert(key, &record)
.await
.unwrap();
let out = w.present_as(&w.auth, session, &answer).await;
assert_eq!(code(&out), "vta/witness/present:sessionUnknown");
}
#[tokio::test]
async fn key_custody_refuses_another_contexts_relationship_did() {
let w = World::new(true).await;
let other = claims(Role::Admin, OTHER);
let out = w.open_as(&other, w.open_payload(true)).await;
assert_eq!(code(&out), "permissionDenied", "{:#}", body(&out));
let mut p = w.open_payload(false);
p["relationshipDid"] = json!(Key::new().did);
let out = w.open_as(&w.auth, p).await;
assert_eq!(code(&out), "permissionDenied");
let mut p = w.open_payload(true);
p["membership"]["memberDid"] = json!(Key::new().did);
let out = w.open_as(&w.auth, p).await;
assert_eq!(code(&out), "permissionDenied");
}
#[tokio::test]
async fn sign_alone_does_not_satisfy_witness_request() {
let w = World::new(true).await;
assert!(vti_common::acl::role_has_capability(
&Role::Application,
Capability::Sign
));
let out = w
.open_as(&claims(Role::Application, CTX), w.open_payload(true))
.await;
assert_eq!(code(&out), "permissionDenied");
assert!(body(&out).to_string().contains("WitnessRequest"));
}
impl World {
async fn list_as(&self, auth: &AuthClaims, payload: Value) -> TrustTaskOutcome {
handle_list(
&self.state,
auth,
doc(uris::TASK_VTA_WITNESS_LIST_0_1, payload),
)
.await
}
async fn witnessed(&self) -> Value {
let (session, _, _, outcome) = self.run_to_outcome(true).await;
ok(&self.accept_as(&self.auth, &session, &outcome).await)
}
}
#[tokio::test]
async fn list_reads_back_what_accept_stored_byte_for_byte() {
let w = World::new(true).await;
let accepted = w.witnessed().await;
let listed = ok(&w.list_as(&w.auth, json!({})).await);
let entries = listed["witnessings"].as_array().unwrap();
assert_eq!(entries.len(), 1, "{listed:#}");
let e = &entries[0];
assert_eq!(e["credentialId"], accepted["credentialId"]);
assert_eq!(e["witness"], accepted["witness"]);
assert_eq!(e["relationshipDid"], w.relationship);
assert_eq!(e["vwcDigestMultibase"], accepted["vwcDigestMultibase"]);
assert!(e["acceptedAt"].as_str().is_some());
for part in ["session", "outcome"] {
assert_eq!(
serde_json::to_string(&e["evidence"][part]).unwrap(),
serde_json::to_string(&accepted["evidence"][part]).unwrap(),
"evidence.{part} is returned exactly as stored"
);
}
assert_eq!(listed["truncated"], false);
assert!(listed.get("cursor").is_none(), "{listed:#}");
}
#[tokio::test]
async fn list_applies_its_filters() {
let w = World::new(true).await;
w.witnessed().await;
let count = |v: Value| v["witnessings"].as_array().unwrap().len();
let rel = ok(&w
.list_as(&w.auth, json!({ "relationshipDid": w.relationship }))
.await);
assert_eq!(count(rel), 1);
let by_witness = ok(&w
.list_as(&w.auth, json!({ "witness": w.witness.did() }))
.await);
assert_eq!(count(by_witness), 1);
let other_rel = ok(&w
.list_as(&w.auth, json!({ "relationshipDid": w.counterparty }))
.await);
assert_eq!(count(other_rel), 0);
let other_witness = ok(&w
.list_as(&w.auth, json!({ "witness": Key::new().did }))
.await);
assert_eq!(count(other_witness), 0);
}
#[tokio::test]
async fn list_answers_unreachable_and_unknown_alike() {
let w = World::new(true).await;
w.witnessed().await;
let elsewhere = claims(Role::Admin, OTHER);
let unreachable = w
.list_as(&elsewhere, json!({ "relationshipDid": w.relationship }))
.await;
let unknown = w
.list_as(&w.auth, json!({ "relationshipDid": Key::new().did }))
.await;
let everything_elsewhere = w.list_as(&elsewhere, json!({})).await;
for out in [&unreachable, &unknown, &everything_elsewhere] {
assert_eq!(
ok(out),
json!({ "witnessings": [], "truncated": false }),
"{:#}",
body(out)
);
}
}
#[tokio::test]
async fn list_pages_oldest_first_with_an_opaque_cursor() {
let w = World::new(true).await;
let mut ids = Vec::new();
for _ in 0..3 {
ids.push(w.witnessed().await["credentialId"].clone());
}
let first = ok(&w.list_as(&w.auth, json!({ "pageSize": 2 })).await);
assert_eq!(first["truncated"], true, "{first:#}");
let cursor = first["cursor"]
.as_str()
.expect("a cursor to continue")
.to_string();
let second = ok(&w
.list_as(&w.auth, json!({ "pageSize": 2, "cursor": cursor }))
.await);
assert_eq!(second["truncated"], false, "{second:#}");
assert!(second.get("cursor").is_none());
let seen: Vec<Value> = first["witnessings"]
.as_array()
.unwrap()
.iter()
.chain(second["witnessings"].as_array().unwrap())
.map(|e| e["credentialId"].clone())
.collect();
assert_eq!(seen.len(), 3, "every stored VWC once: {seen:?}");
let mut sorted = seen.clone();
sorted.sort_by_key(|v| v.to_string());
let mut expected = ids.clone();
expected.sort_by_key(|v| v.to_string());
assert_eq!(sorted, expected);
let at: Vec<String> = first["witnessings"]
.as_array()
.unwrap()
.iter()
.chain(second["witnessings"].as_array().unwrap())
.map(|e| e["acceptedAt"].as_str().unwrap().to_string())
.collect();
let mut ordered = at.clone();
ordered.sort();
assert_eq!(at, ordered, "oldest first");
for bad in ["not-a-cursor", "w1.!!!", "w1.Zm9v"] {
let out = w.list_as(&w.auth, json!({ "cursor": bad })).await;
assert_eq!(code(&out), "vta/witness/list:cursorInvalid", "{bad}");
}
}
#[tokio::test]
async fn list_omits_an_archived_witness_credential() {
let w = World::new(true).await;
let accepted = w.witnessed().await;
let id = accepted["credentialId"].as_str().unwrap();
let mut stored = crate::vault::storage::get(&w.state.vault_ks, id)
.await
.unwrap()
.unwrap();
stored.lifecycle = vti_common::vault::VaultStatus::Archived;
crate::vault::storage::put(&w.state.vault_ks, &stored)
.await
.unwrap();
let listed = ok(&w.list_as(&w.auth, json!({})).await);
assert_eq!(listed["witnessings"], json!([]));
}
#[tokio::test]
async fn list_needs_witness_request_not_sign() {
let w = World::new(true).await;
let out = w.list_as(&claims(Role::Application, CTX), json!({})).await;
assert_eq!(code(&out), "permissionDenied");
}
#[tokio::test]
async fn open_refuses_what_cannot_succeed_before_anything_is_disclosed() {
let w = World::new(true).await;
let mut p = w.open_payload(true);
p["relationshipCredentialId"] = json!("no-such-credential");
assert_eq!(
code(&w.open_as(&w.auth, p).await),
"vta/witness/open:notFound"
);
let wrong = w.vrc(&Key::new().did).await;
w.store("vrc-wrong", wrong).await;
let mut p = w.open_payload(true);
p["relationshipCredentialId"] = json!("vrc-wrong");
assert_eq!(
code(&w.open_as(&w.auth, p).await),
"vta/witness/open:credentialMismatch"
);
assert_eq!(
code(&w.open_as(&w.auth, w.open_payload(false)).await),
"vta/witness/open:credentialMismatch"
);
let mut p = w.open_payload(true);
p["witness"] = json!(w.counterparty);
assert_eq!(code(&w.open_as(&w.auth, p).await), "malformedRequest");
assert!(
w.state
.witness_sessions_ks
.prefix_iter_raw("session:")
.await
.unwrap()
.is_empty()
);
}
#[tokio::test]
async fn present_refuses_a_credential_changed_since_open() {
let w = World::new(true).await;
let opened = ok(&w.open_as(&w.auth, w.open_payload(true)).await);
let session = opened["sessionId"].as_str().unwrap();
let answer = w.witness.carry(&w.relationship, &opened["document"]).await;
let reissued = w.vrc(&w.counterparty.clone()).await;
w.store("vrc-1", reissued).await;
let out = w.present_as(&w.auth, session, &answer).await;
assert_eq!(code(&out), "vta/witness/present:credentialMismatch");
}