agent_sdk_core/testing/
hooks.rs1use std::{
8 collections::VecDeque,
9 sync::{Arc, Mutex},
10};
11
12use crate::{
13 domain::AgentError,
14 hook_ports::{HookExecutionOutcome, HookExecutor},
15 package_hooks::{HookExecutorRef, HookInput, HookResponse},
16};
17
18#[derive(Clone, Debug)]
19pub struct ScriptedHookExecutor {
22 executor_ref: HookExecutorRef,
23 outcomes: Arc<Mutex<VecDeque<Result<HookExecutionOutcome, AgentError>>>>,
24 invocations: Arc<Mutex<Vec<HookInput>>>,
25}
26
27impl ScriptedHookExecutor {
28 pub fn new(
32 executor_ref: impl Into<String>,
33 outcomes: impl IntoIterator<Item = Result<HookExecutionOutcome, AgentError>>,
34 ) -> Self {
35 Self {
36 executor_ref: HookExecutorRef::new(executor_ref),
37 outcomes: Arc::new(Mutex::new(outcomes.into_iter().collect())),
38 invocations: Arc::new(Mutex::new(Vec::new())),
39 }
40 }
41
42 pub fn once(executor_ref: impl Into<String>, response: HookResponse, elapsed_ms: u64) -> Self {
46 Self::new(
47 executor_ref,
48 [Ok(HookExecutionOutcome::new(response, elapsed_ms))],
49 )
50 }
51
52 pub fn invocations(&self) -> Vec<HookInput> {
55 self.invocations
56 .lock()
57 .expect("hook invocations lock")
58 .clone()
59 }
60}
61
62impl HookExecutor for ScriptedHookExecutor {
63 fn executor_ref(&self) -> &HookExecutorRef {
64 &self.executor_ref
65 }
66
67 fn invoke(&self, input: HookInput) -> Result<HookExecutionOutcome, AgentError> {
68 self.invocations
69 .lock()
70 .expect("hook invocations lock")
71 .push(input);
72 self.outcomes
73 .lock()
74 .expect("hook outcomes lock")
75 .pop_front()
76 .unwrap_or_else(|| Err(AgentError::contract_violation("scripted hook exhausted")))
77 }
78}