use std::sync::Arc;
use std::time::Duration;
use futures::future::BoxFuture;
use wasm_capability_contract::{
CapabilityEngine, CapabilityError, ComponentHandle, ComponentInvokeRequest,
ComponentInvokeResponse, ComponentLoadRequest, ComponentLoadResponse, ComponentManifest,
ResourceLimits,
};
struct EchoEngineDouble {
reject_load: bool,
}
impl CapabilityEngine for EchoEngineDouble {
fn load(
&self,
req: ComponentLoadRequest,
) -> BoxFuture<'_, Result<ComponentLoadResponse, CapabilityError>> {
Box::pin(async move {
if self.reject_load {
return Err(CapabilityError::InvalidManifest(
"double rejects this load".to_string(),
));
}
Ok(ComponentLoadResponse {
handle: ComponentHandle(req.manifest.component_id),
})
})
}
fn invoke(
&self,
req: ComponentInvokeRequest,
) -> BoxFuture<'_, Result<ComponentInvokeResponse, CapabilityError>> {
Box::pin(async move {
if req.payload.is_empty() {
return Err(CapabilityError::InvalidOutput(
"echo double refuses empty payloads".to_string(),
));
}
Ok(ComponentInvokeResponse {
payload: req.payload,
})
})
}
}
fn sample_manifest() -> ComponentManifest {
ComponentManifest {
component_id: "echo-handler".to_string(),
contract_version: "swe:edge-handler@0.2.0".to_string(),
handler_export: "echo".to_string(),
resource_limits: ResourceLimits {
max_memory_bytes: 16 * 1024 * 1024,
invoke_timeout_ms: 5_000,
max_concurrency: 8,
max_payload_bytes: 256 * 1024,
},
capabilities: vec![],
}
}
fn _accept(_engine: &dyn CapabilityEngine) {}
#[tokio::test]
async fn test_load_a_fresh_component_happy() {
let engine: Arc<dyn CapabilityEngine> = Arc::new(EchoEngineDouble { reject_load: false });
let loaded = engine
.load(ComponentLoadRequest {
manifest: sample_manifest(),
component_bytes: vec![0, 1, 2, 3],
})
.await
.unwrap_or_else(|e| panic!("load must succeed: {e}"));
assert_eq!(loaded.handle, ComponentHandle("echo-handler".to_string()));
}
#[tokio::test]
async fn test_load_rejected_bytes_error() {
let engine: Arc<dyn CapabilityEngine> = Arc::new(EchoEngineDouble { reject_load: true });
let result = engine
.load(ComponentLoadRequest {
manifest: sample_manifest(),
component_bytes: vec![0xff, 0xff],
})
.await;
assert!(matches!(result, Err(CapabilityError::InvalidManifest(_))));
}
#[tokio::test]
async fn test_load_empty_component_bytes_edge() {
let engine: Arc<dyn CapabilityEngine> = Arc::new(EchoEngineDouble { reject_load: false });
let loaded = engine
.load(ComponentLoadRequest {
manifest: sample_manifest(),
component_bytes: vec![],
})
.await
.unwrap_or_else(|e| panic!("load with empty bytes must still succeed on this double: {e}"));
assert_eq!(loaded.handle, ComponentHandle("echo-handler".to_string()));
}
#[tokio::test]
async fn test_invoke_a_loaded_component_happy() {
let engine: Arc<dyn CapabilityEngine> = Arc::new(EchoEngineDouble { reject_load: false });
let invoked = engine
.invoke(ComponentInvokeRequest {
handle: ComponentHandle("echo-handler".to_string()),
payload: b"hello".to_vec(),
deadline: Duration::from_secs(1),
})
.await
.unwrap_or_else(|e| panic!("invoke must succeed: {e}"));
assert_eq!(
invoked.payload,
b"hello".to_vec(),
"the double must echo back exactly what it received"
);
}
#[tokio::test]
async fn test_invoke_empty_payload_error() {
let engine: Arc<dyn CapabilityEngine> = Arc::new(EchoEngineDouble { reject_load: false });
let result = engine
.invoke(ComponentInvokeRequest {
handle: ComponentHandle("echo-handler".to_string()),
payload: vec![],
deadline: Duration::from_secs(1),
})
.await;
assert!(matches!(result, Err(CapabilityError::InvalidOutput(_))));
}
#[tokio::test]
async fn test_invoke_single_byte_payload_edge() {
let engine: Arc<dyn CapabilityEngine> = Arc::new(EchoEngineDouble { reject_load: false });
let invoked = engine
.invoke(ComponentInvokeRequest {
handle: ComponentHandle("echo-handler".to_string()),
payload: vec![0],
deadline: Duration::from_secs(1),
})
.await
.unwrap_or_else(|e| panic!("invoke with a single-byte payload must succeed: {e}"));
assert_eq!(invoked.payload, vec![0]);
}