agent_sdk_core/testing/extension.rs
1//! Deterministic test-kit helpers for SDK consumers. Use these fakes and harnesses to
2//! exercise public contracts without live providers, real stores, product UI, network
3//! telemetry, or wall-clock-dependent infrastructure. They mutate only their
4//! in-memory state unless noted. This file contains the extension portion of that
5//! contract.
6//!
7use std::sync::{Arc, Mutex};
8
9use crate::{
10 domain::AgentError,
11 extension_ports::{
12 ExtensionActionExecutionOutput, ExtensionActionExecutionRequest, ExtensionActionExecutor,
13 },
14 package_extension::ExtensionBridgeRef,
15};
16
17#[derive(Clone)]
18/// In-memory scripted extension action executor fixture for SDK conformance tests.
19/// Use it to script deterministic behavior in memory; any transcript or endpoint mutation is documented on the method that performs it.
20pub struct ScriptedExtensionActionExecutor {
21 bridge_ref: ExtensionBridgeRef,
22 output: ExtensionActionExecutionOutput,
23 calls: Arc<Mutex<Vec<ExtensionActionExecutionRequest>>>,
24}
25
26impl ScriptedExtensionActionExecutor {
27 /// Creates a new testing::extension value with explicit
28 /// caller-provided inputs. This constructor is data-only and
29 /// performs no I/O or external side effects.
30 ///
31 /// # Panics
32 ///
33 /// Panics if constructor invariants fail, such as invalid identifier
34 /// text or constructor-specific bounds. Use a fallible constructor such as
35 /// `try_new` when one is available for untrusted input.
36 pub fn new(bridge_ref: ExtensionBridgeRef, output: ExtensionActionExecutionOutput) -> Self {
37 Self {
38 bridge_ref,
39 output,
40 calls: Arc::new(Mutex::new(Vec::new())),
41 }
42 }
43
44 /// Operates on in-memory or journal-derived testing::extension state for
45 /// diagnostics and repair evidence. It does not create a second run loop
46 /// or product workflow owner.
47 pub fn calls(&self) -> Vec<ExtensionActionExecutionRequest> {
48 self.calls
49 .lock()
50 .expect("extension action executor calls lock")
51 .clone()
52 }
53
54 /// Returns the call count currently held by this value.
55 /// This reads deterministic in-memory test state and performs no external I/O.
56 pub fn call_count(&self) -> usize {
57 self.calls
58 .lock()
59 .expect("extension action executor calls lock")
60 .len()
61 }
62}
63
64impl ExtensionActionExecutor for ScriptedExtensionActionExecutor {
65 fn bridge_ref(&self) -> &ExtensionBridgeRef {
66 &self.bridge_ref
67 }
68
69 fn execute(
70 &self,
71 request: &ExtensionActionExecutionRequest,
72 ) -> Result<ExtensionActionExecutionOutput, AgentError> {
73 self.calls
74 .lock()
75 .expect("extension action executor calls lock")
76 .push(request.clone());
77 Ok(self.output.clone())
78 }
79}