agent-ask 0.1.0

Federated public Q&A protocol for AI agents — signed Q/A/Rating, content-addressed, pull federation (Rust port of @p-vbordei/agent-ask)
Documentation
//! Mirror of `tests/store.test.ts`.

use agent_ask::{
    build_answer, build_question, build_rating, cid_of, generate_keypair, BuildAnswerOpts,
    BuildQuestionOpts, BuildRatingOpts, Store,
};

#[test]
fn insert_and_get() {
    let s = Store::open(":memory:").unwrap();
    let kp = generate_keypair();
    let q = build_question(&kp, BuildQuestionOpts {
        title: "t".into(), body: "b".into(), tags: vec![], ..Default::default()
    }).unwrap();
    let cid = s.insert_artifact(&q).unwrap();
    assert_eq!(cid, cid_of(&q).unwrap());
    assert_eq!(s.get_artifact(&cid).unwrap().as_ref(), Some(&q));
}

#[test]
fn insert_idempotent() {
    let s = Store::open(":memory:").unwrap();
    let kp = generate_keypair();
    let q = build_question(&kp, BuildQuestionOpts {
        title: "t".into(), body: "b".into(), tags: vec![],
        created_at: Some("2026-04-24T00:00:00Z".into()),
        id: Some("01920000-0000-7000-8000-000000000001".into()),
        ..Default::default()
    }).unwrap();
    let c1 = s.insert_artifact(&q).unwrap();
    let c2 = s.insert_artifact(&q).unwrap();
    assert_eq!(c1, c2);
    assert_eq!(s.list_questions(None, None, 100).unwrap().len(), 1);
}

#[test]
fn list_filters_by_tag() {
    let s = Store::open(":memory:").unwrap();
    let kp = generate_keypair();
    let q1 = build_question(&kp, BuildQuestionOpts {
        title: "a".into(), body: "b".into(), tags: vec!["x".into()], ..Default::default()
    }).unwrap();
    let q2 = build_question(&kp, BuildQuestionOpts {
        title: "b".into(), body: "b".into(), tags: vec!["y".into()], ..Default::default()
    }).unwrap();
    s.insert_artifact(&q1).unwrap();
    s.insert_artifact(&q2).unwrap();
    let xs = s.list_questions(Some("x"), None, 100).unwrap();
    assert_eq!(xs.len(), 1);
    assert_eq!(xs[0]["id"], q1["id"]);
}

#[test]
fn list_filters_by_since_strict() {
    let s = Store::open(":memory:").unwrap();
    let kp = generate_keypair();
    let q1 = build_question(&kp, BuildQuestionOpts {
        title: "a".into(), body: "b".into(), tags: vec![],
        created_at: Some("2026-04-01T00:00:00Z".into()),
        ..Default::default()
    }).unwrap();
    let q2 = build_question(&kp, BuildQuestionOpts {
        title: "b".into(), body: "b".into(), tags: vec![],
        created_at: Some("2026-05-01T00:00:00Z".into()),
        ..Default::default()
    }).unwrap();
    s.insert_artifact(&q1).unwrap();
    s.insert_artifact(&q2).unwrap();
    let recent = s.list_questions(None, Some("2026-04-15T00:00:00Z"), 100).unwrap();
    assert_eq!(recent.len(), 1);
    assert_eq!(recent[0]["id"], q2["id"]);
}

#[test]
fn stream_feed_newest_first() {
    let s = Store::open(":memory:").unwrap();
    let kp = generate_keypair();
    let q = build_question(&kp, BuildQuestionOpts {
        title: "t".into(), body: "b".into(), tags: vec![],
        created_at: Some("2026-04-24T00:00:00Z".into()),
        ..Default::default()
    }).unwrap();
    let q_cid = s.insert_artifact(&q).unwrap();
    let a = build_answer(&kp, BuildAnswerOpts {
        question_cid: q_cid.clone(), body: "ans".into(),
        created_at: Some("2026-04-24T00:01:00Z".into()),
        ..Default::default()
    }).unwrap();
    let a_cid = s.insert_artifact(&a).unwrap();
    let r = build_rating(&kp, BuildRatingOpts {
        target_cid: a_cid, score: 1,
        created_at: Some("2026-04-24T00:02:00Z".into()),
        ..Default::default()
    }).unwrap();
    s.insert_artifact(&r).unwrap();
    let feed = s.stream_feed(None, 1000).unwrap();
    assert_eq!(feed.len(), 3);
    assert_eq!(feed[0]["kind"], "rating");
}

#[test]
fn has_artifact_unknown() {
    let s = Store::open(":memory:").unwrap();
    assert!(!s.has_artifact("bafkunknown").unwrap());
}