use agent_ask::{
build_answer, build_question, build_rating, cid_of, generate_keypair, verify_artifact,
BuildAnswerOpts, BuildQuestionOpts, BuildRatingOpts,
};
use serde_json::json;
#[test]
fn build_question_signed_no_cid_field() {
let kp = generate_keypair();
let q = build_question(&kp, BuildQuestionOpts {
title: "hello world".into(),
body: "does this protocol work?".into(),
tags: vec!["meta".into()],
..Default::default()
}).unwrap();
assert_eq!(q["v"], "agent-ask/0.1");
assert_eq!(q["kind"], "question");
assert_eq!(q["title"], "hello world");
assert_eq!(q["tags"], json!(["meta"]));
assert_eq!(q["author_did"], json!(kp.did));
assert_eq!(q["sig"]["alg"], "ed25519");
assert!(q["sig"]["sig"].is_string());
assert!(q.get("cid").is_none());
}
#[test]
fn cid_of_returns_bafk() {
let kp = generate_keypair();
let q = build_question(&kp, BuildQuestionOpts {
title: "t".into(), body: "b".into(), tags: vec![], ..Default::default()
}).unwrap();
let cid = cid_of(&q).unwrap();
assert!(cid.starts_with("bafk"));
}
#[test]
fn verify_accepts_freshly_built() {
let kp = generate_keypair();
let q = build_question(&kp, BuildQuestionOpts {
title: "t".into(), body: "b".into(), tags: vec![], ..Default::default()
}).unwrap();
let res = verify_artifact(&q);
assert!(res.ok, "errors: {:?}", res.errors);
}
#[test]
fn verify_rejects_mutated_body() {
let kp = generate_keypair();
let q = build_question(&kp, BuildQuestionOpts {
title: "t".into(), body: "b".into(), tags: vec![], ..Default::default()
}).unwrap();
let mut tampered = q.clone();
tampered["body"] = json!("b!");
let res = verify_artifact(&tampered);
assert!(!res.ok);
assert!(res.errors.iter().any(|e| e.contains("signature")));
}
#[test]
fn verify_rejects_author_mismatch() {
let kp = generate_keypair();
let other = generate_keypair();
let q = build_question(&kp, BuildQuestionOpts {
title: "t".into(), body: "b".into(), tags: vec![], ..Default::default()
}).unwrap();
let mut tampered = q.clone();
tampered["author_did"] = json!(other.did);
let res = verify_artifact(&tampered);
assert!(!res.ok);
}
#[test]
fn cid_deterministic() {
let kp = generate_keypair();
let opts = || BuildQuestionOpts {
title: "t".into(), body: "b".into(), tags: vec![],
created_at: Some("2026-04-24T00:00:00Z".into()),
id: Some("01920000-0000-7000-8000-000000000000".into()),
..Default::default()
};
let a = build_question(&kp, opts()).unwrap();
let b = build_question(&kp, opts()).unwrap();
assert_eq!(a["sig"]["sig"], b["sig"]["sig"]);
assert_eq!(cid_of(&a).unwrap(), cid_of(&b).unwrap());
}
#[test]
fn build_answer_refs_qcid() {
let q_kp = generate_keypair();
let a_kp = generate_keypair();
let q = build_question(&q_kp, BuildQuestionOpts {
title: "q".into(), body: "q body".into(), tags: vec![], ..Default::default()
}).unwrap();
let q_cid = cid_of(&q).unwrap();
let answer = build_answer(&a_kp, BuildAnswerOpts {
question_cid: q_cid.clone(),
body: "because X".into(),
refs: Some(vec![]),
..Default::default()
}).unwrap();
assert_eq!(answer["kind"], "answer");
assert_eq!(answer["question_cid"], json!(q_cid));
assert_eq!(answer["author_did"], json!(a_kp.did));
let v = verify_artifact(&answer);
assert!(v.ok, "errors: {:?}", v.errors);
}
#[test]
fn build_answer_omits_empty_refs() {
let kp = generate_keypair();
let a = build_answer(&kp, BuildAnswerOpts {
question_cid: "bafkdeadbeef".into(),
body: "hi".into(),
..Default::default()
}).unwrap();
assert!(a.get("refs").is_none());
}
#[test]
fn build_rating_score_1() {
let kp = generate_keypair();
let r = build_rating(&kp, BuildRatingOpts {
target_cid: "bafkfeedf00d".into(),
score: 1,
rationale: Some("correct".into()),
..Default::default()
}).unwrap();
assert_eq!(r["score"], 1);
assert_eq!(r["rationale"], "correct");
let v = verify_artifact(&r);
assert!(v.ok, "errors: {:?}", v.errors);
}
#[test]
fn build_rating_rejects_invalid_score() {
let kp = generate_keypair();
assert!(build_rating(&kp, BuildRatingOpts {
target_cid: "x".into(), score: 2, ..Default::default()
}).is_err());
}