use anyhow::{Context, Result};
use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
use tokio::time::timeout;
mod common;
use common::find_available_port;
use wash_runtime::{
engine::Engine,
host::{HostApi, HostBuilder},
plugin::{wasi_blobstore::WasiBlobstore, wasi_http::HttpServer},
types::{Component, LocalResources, Workload, WorkloadStartRequest},
wit::WitInterface,
};
const HTTP_BLOBSTORE_WASM: &[u8] = include_bytes!("fixtures/http_blobstore.wasm");
#[tokio::test]
async fn test_http_blobstore_integration() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
println!("Starting HTTP + Blobstore integration test with blobstore-filesystem component");
let engine = Engine::builder().build()?;
let port = find_available_port().await?;
let addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap();
let http_plugin = HttpServer::new(addr);
let blobstore_plugin = WasiBlobstore::new(None);
let host = HostBuilder::new()
.with_engine(engine.clone())
.with_plugin(Arc::new(http_plugin))?
.with_plugin(Arc::new(blobstore_plugin))?
.build()?;
println!("Created host with HTTP and blobstore plugins");
let host = host.start().await.context("Failed to start host")?;
println!("Host started, HTTP server listening on {addr}");
let req = WorkloadStartRequest {
workload: Workload {
namespace: "test".to_string(),
name: "test-workload".to_string(),
annotations: HashMap::new(),
service: None,
components: vec![Component {
bytes: bytes::Bytes::from_static(HTTP_BLOBSTORE_WASM),
local_resources: LocalResources {
memory_limit_mb: 256,
cpu_limit: 1,
config: HashMap::new(),
environment: HashMap::new(),
volume_mounts: vec![],
allowed_hosts: vec![],
},
pool_size: 1,
max_invocations: 100,
}],
host_interfaces: vec![
WitInterface {
namespace: "wasi".to_string(),
package: "http".to_string(),
interfaces: ["incoming-handler".to_string()].into_iter().collect(),
version: Some(semver::Version::parse("0.2.2").unwrap()),
config: {
let mut config = HashMap::new();
config.insert("host".to_string(), "foo".to_string());
config
},
},
WitInterface {
namespace: "wasi".to_string(),
package: "blobstore".to_string(),
interfaces: [
"blobstore".to_string(),
"container".to_string(),
"types".to_string(),
]
.into_iter()
.collect(),
version: Some(semver::Version::parse("0.2.0-draft").unwrap()),
config: HashMap::new(),
},
],
volumes: vec![],
},
};
let workload_response = host
.workload_start(req)
.await
.context("Failed to start workload")?;
println!(
"Started workload: {:?}",
workload_response.workload_status.workload_id
);
println!("Testing blobstore-filesystem component endpoint with POST data");
let test_data = "Hello, blobstore world!";
let client = reqwest::Client::new();
let response = timeout(
Duration::from_secs(5),
client
.post(format!("http://{addr}/"))
.header("HOST", "foo")
.body(test_data)
.send(),
)
.await
.context("HTTP request timed out")?
.context("Failed to make HTTP request")?;
let status = response.status();
println!("HTTP Response Status: {}", status);
let response_text = response
.text()
.await
.context("Failed to read response body")?;
println!("HTTP Response Body: {}", response_text.trim());
println!("Blobstore-filesystem component responded successfully");
assert!(status.is_success(), "Expected success, got {}", status);
assert!(
!response_text.trim().is_empty(),
"Expected response body content"
);
assert_eq!(
response_text.trim(),
test_data,
"Expected response to match the data we sent (round-trip verification)"
);
println!("Component successfully performed round-trip: POST data → blobstore → response");
println!("HTTP and blobstore plugins are both active and working together");
println!("All integration tests passed");
Ok(())
}
#[tokio::test]
async fn test_plugin_isolation() -> Result<()> {
println!("Testing plugin isolation between workloads");
let engine = Engine::builder().build()?;
let blobstore1 = WasiBlobstore::new(None);
let blobstore2 = WasiBlobstore::new(None);
let _host1 = HostBuilder::new()
.with_engine(engine.clone())
.with_plugin(Arc::new(blobstore1))?
.build()?;
let _host2 = HostBuilder::new()
.with_engine(engine.clone())
.with_plugin(Arc::new(blobstore2))?
.build()?;
println!("Created two independent hosts with blobstore plugins");
println!("Plugin isolation test passed");
Ok(())
}
#[tokio::test]
async fn test_plugin_lifecycle() -> Result<()> {
println!("Testing plugin lifecycle");
let engine = Engine::builder().build()?;
let port = find_available_port().await?;
let addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap();
let http_plugin = HttpServer::new(addr);
let host = HostBuilder::new()
.with_engine(engine)
.with_plugin(Arc::new(http_plugin))?
.build()?;
let _host = host.start().await.context("Failed to start host")?;
println!("Host started successfully");
println!("Host lifecycle test passed");
Ok(())
}