use super::*;
#[test]
fn inbox_inject_take_is_exactly_once_fifo_idempotent_and_fenced() {
let root = tempfile::tempdir().unwrap();
let ws = tempfile::tempdir().unwrap();
let store = ConversationStore::new(root.path(), ws.path(), 100).unwrap();
let conv = store.create("session", None).unwrap();
assert_eq!(store.take_injected_prompt(&conv).unwrap(), None);
assert_eq!(
store.inject_prompt(&conv, "first", None).unwrap(),
InjectOutcome::Enqueued
);
assert_eq!(
store.inject_prompt(&conv, "second", None).unwrap(),
InjectOutcome::Enqueued
);
assert_eq!(
store.take_injected_prompt(&conv).unwrap().unwrap().body,
"first"
);
assert_eq!(
store.take_injected_prompt(&conv).unwrap().unwrap().body,
"second"
);
assert_eq!(
store.take_injected_prompt(&conv).unwrap(),
None,
"drained exactly once"
);
assert_eq!(
store.inject_prompt(&conv, "again", Some("k1")).unwrap(),
InjectOutcome::Enqueued
);
assert_eq!(
store.inject_prompt(&conv, "again", Some("k1")).unwrap(),
InjectOutcome::Duplicate
);
assert_eq!(
store.take_injected_prompt(&conv).unwrap().unwrap().body,
"again"
);
assert_eq!(
store.take_injected_prompt(&conv).unwrap(),
None,
"the idem duplicate did not enqueue twice"
);
store.inject_prompt(&conv, "linked", None).unwrap();
let taken = store.take_injected_prompt(&conv).unwrap().unwrap();
store.link_inbox_delivery(&taken.id, "receipt-123").unwrap();
let ws_b = tempfile::tempdir().unwrap();
let store_b = ConversationStore::new(root.path(), ws_b.path(), 100).unwrap();
assert!(
store_b.inject_prompt(&conv, "cross-ws", None).is_err(),
"cross-workspace inject is rejected"
);
store.inject_prompt(&conv, "mine", None).unwrap();
assert_eq!(
store_b.take_injected_prompt(&conv).unwrap(),
None,
"cross-workspace take sees nothing"
);
assert!(
store.take_injected_prompt(&conv).unwrap().is_some(),
"the owning workspace still dequeues it"
);
}
#[test]
fn an_expired_offer_is_not_pending() {
let root = tempfile::tempdir().unwrap();
let ws = tempfile::tempdir().unwrap();
let mut store = ConversationStore::new(root.path(), ws.path(), 100).unwrap();
let conv = store.create("s", None).unwrap();
store.claim_clock = || 1_000;
let (definition, _instance) = test_offer(&store, &conv);
let id = store
.publish_interaction_offer(
&conv,
&definition,
crate::interaction_offer::OfferDanger::High,
&[newt_interaction::Audience::Web],
)
.unwrap();
assert!(store.pending_interaction_offer(&conv).unwrap().is_some());
store.claim_clock = || 999 + ConversationStore::PERMISSION_REQUEST_TTL_NANOS;
assert!(store.pending_interaction_offer(&conv).unwrap().is_some());
store.claim_clock = || 1_000 + ConversationStore::PERMISSION_REQUEST_TTL_NANOS;
assert!(store.pending_interaction_offer(&conv).unwrap().is_none());
assert_eq!(store.take_interaction_decision(&conv, &id).unwrap(), None);
}
fn test_offer(
store: &ConversationStore,
conv: &str,
) -> (
newt_interaction::InteractionDefinition,
newt_interaction::InteractionInstance,
) {
let question = crate::Question::<crate::PermissionAction> {
markdown: "\u{2298} run_command wants to run `bash`".to_string(),
actions: vec![
crate::Action::new(crate::PermissionAction::AllowOnce, "a", "allow once"),
crate::Action::new(crate::PermissionAction::Deny, "d", "deny (default)"),
],
note: None,
};
let definition = crate::interaction_adapter::question_to_definition(&question).unwrap();
let (instance, _) = crate::interaction_gate::mint_offer(
&definition,
store.workspace_fence(),
conv,
&[newt_interaction::Audience::Web],
store.claim_tick(),
)
.unwrap();
(definition, instance)
}
#[test]
fn interaction_offer_publish_answer_take_race_and_fence() {
let root = tempfile::tempdir().unwrap();
let ws = tempfile::tempdir().unwrap();
let other_ws = tempfile::tempdir().unwrap();
let store = ConversationStore::new(root.path(), ws.path(), 100).unwrap();
let conv = store.create("s", None).unwrap();
let (definition, _instance) = test_offer(&store, &conv);
let id = store
.publish_interaction_offer(
&conv,
&definition,
crate::interaction_offer::OfferDanger::Low,
&[newt_interaction::Audience::Web],
)
.unwrap();
let pending = store.pending_interaction_offer(&conv).unwrap().unwrap();
assert_eq!(pending.instance_id, id);
assert_eq!(pending.danger, crate::interaction_offer::OfferDanger::Low);
let foreign = ConversationStore::new(root.path(), other_ws.path(), 100).unwrap();
assert!(foreign.pending_interaction_offer(&conv).unwrap().is_none());
assert_eq!(
store
.answer_interaction_offer(
&conv,
&id,
crate::PermissionAction::AllowOnce,
newt_interaction::Audience::Web
)
.unwrap(),
AnswerOutcome::Answered
);
assert_eq!(
store
.answer_interaction_offer(
&conv,
&id,
crate::PermissionAction::Deny,
newt_interaction::Audience::Web
)
.unwrap(),
AnswerOutcome::AlreadyResolved
);
assert_eq!(
store.take_interaction_decision(&conv, &id).unwrap(),
Some(crate::PermissionAction::AllowOnce)
);
assert!(store.pending_interaction_offer(&conv).unwrap().is_none());
assert!(!store.cancel_interaction_offer(&conv, &id).unwrap());
}
#[test]
fn an_unknown_persisted_option_reads_as_none_not_error() {
let root = tempfile::tempdir().unwrap();
let ws = tempfile::tempdir().unwrap();
let store = ConversationStore::new(root.path(), ws.path(), 100).unwrap();
let conv = store.create("s", None).unwrap();
let (definition, _instance) = test_offer(&store, &conv);
let id = store
.publish_interaction_offer(
&conv,
&definition,
crate::interaction_offer::OfferDanger::Low,
&[newt_interaction::Audience::Web],
)
.unwrap();
store
.answer_interaction_offer(
&conv,
&id,
crate::PermissionAction::AllowOnce,
newt_interaction::Audience::Web,
)
.unwrap();
store
.lock_conn()
.execute(
"UPDATE interaction_offers SET response_json = replace(response_json, 'allow_once', 'allow_twice') WHERE instance_id = ?1",
[&id],
)
.unwrap();
assert_eq!(store.take_interaction_decision(&conv, &id).unwrap(), None);
assert_eq!(
store.interaction_answered_by(&conv, &id).unwrap(),
Some(newt_interaction::Audience::Web)
);
}