#![allow(clippy::doc_markdown)]
#![allow(clippy::unused_self)]
#![allow(clippy::cloned_instead_of_copied)]
#![allow(clippy::redundant_closure)]
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, SystemTime};
use test_log::test;
use tokio::sync::Mutex;
use caxton::deployment_manager::{HealthCheckResult, InstanceDeploymentResult};
use caxton::domain::{
AgentLifecycleState, AgentVersion, DeploymentConfig, DeploymentError, HotReloadConfig,
HotReloadError, HotReloadStrategy, ResourceRequirements, TrafficSplitPercentage,
ValidationResult, VersionNumber, WasmModule, WasmSecurityPolicy, WasmValidationError,
};
use caxton::domain_types::{AgentId, AgentName};
use caxton::{
AgentLifecycleManager, CaxtonDeploymentManager, CaxtonHotReloadManager, HealthStatus,
InstanceManager, LifecycleError, ResourceAllocator, RuntimeManager, TrafficRouter,
WasmModuleValidatorTrait, time_provider::test_time_provider,
};
#[derive(Clone)]
struct IntegratedMockSystem {
deployment_should_succeed: Arc<AtomicBool>,
hot_reload_should_succeed: Arc<AtomicBool>,
validation_should_succeed: Arc<AtomicBool>,
deployment_delay: Arc<Mutex<Duration>>,
hot_reload_delay: Arc<Mutex<Duration>>,
validation_delay: Arc<Mutex<Duration>>,
deployed_agents: Arc<Mutex<HashMap<AgentId, DeployedAgentState>>>,
active_versions: Arc<Mutex<HashMap<AgentId, AgentVersion>>>,
traffic_splits: Arc<Mutex<HashMap<AgentId, TrafficSplitPercentage>>>,
deployment_count: Arc<AtomicU64>,
hot_reload_count: Arc<AtomicU64>,
validation_count: Arc<AtomicU64>,
}
#[derive(Clone, Debug)]
struct DeployedAgentState {
#[allow(dead_code)]
agent_id: AgentId,
#[allow(dead_code)]
agent_name: Option<AgentName>,
current_version: AgentVersion,
wasm_module: Vec<u8>,
resources: ResourceRequirements,
#[allow(dead_code)]
deployed_at: SystemTime,
is_healthy: bool,
}
impl IntegratedMockSystem {
fn new() -> Self {
Self {
deployment_should_succeed: Arc::new(AtomicBool::new(true)),
hot_reload_should_succeed: Arc::new(AtomicBool::new(true)),
validation_should_succeed: Arc::new(AtomicBool::new(true)),
deployment_delay: Arc::new(Mutex::new(Duration::from_millis(50))),
hot_reload_delay: Arc::new(Mutex::new(Duration::from_millis(100))),
validation_delay: Arc::new(Mutex::new(Duration::from_millis(20))),
deployed_agents: Arc::new(Mutex::new(HashMap::new())),
active_versions: Arc::new(Mutex::new(HashMap::new())),
traffic_splits: Arc::new(Mutex::new(HashMap::new())),
deployment_count: Arc::new(AtomicU64::new(0)),
hot_reload_count: Arc::new(AtomicU64::new(0)),
validation_count: Arc::new(AtomicU64::new(0)),
}
}
fn set_deployment_success(&self, succeed: bool) {
self.deployment_should_succeed
.store(succeed, Ordering::SeqCst);
}
fn set_hot_reload_success(&self, succeed: bool) {
self.hot_reload_should_succeed
.store(succeed, Ordering::SeqCst);
}
fn set_validation_success(&self, succeed: bool) {
self.validation_should_succeed
.store(succeed, Ordering::SeqCst);
}
async fn set_deployment_delay(&self, delay: Duration) {
*self.deployment_delay.lock().await = delay;
}
async fn set_hot_reload_delay(&self, delay: Duration) {
*self.hot_reload_delay.lock().await = delay;
}
async fn set_validation_delay(&self, delay: Duration) {
*self.validation_delay.lock().await = delay;
}
fn get_deployment_count(&self) -> u64 {
self.deployment_count.load(Ordering::SeqCst)
}
fn get_hot_reload_count(&self) -> u64 {
self.hot_reload_count.load(Ordering::SeqCst)
}
fn get_validation_count(&self) -> u64 {
self.validation_count.load(Ordering::SeqCst)
}
async fn is_agent_deployed(&self, agent_id: AgentId) -> bool {
self.deployed_agents.lock().await.contains_key(&agent_id)
}
#[allow(dead_code)]
async fn get_agent_version(&self, agent_id: AgentId) -> Option<AgentVersion> {
self.active_versions.lock().await.get(&agent_id).cloned()
}
async fn get_deployed_agent_count(&self) -> usize {
self.deployed_agents.lock().await.len()
}
}
struct IntegratedMockResourceAllocator {
system: IntegratedMockSystem,
}
#[async_trait::async_trait]
impl ResourceAllocator for IntegratedMockResourceAllocator {
async fn allocate_resources(
&self,
_agent_id: AgentId,
_requirements: &ResourceRequirements,
) -> Result<(), DeploymentError> {
if !self.system.deployment_should_succeed.load(Ordering::SeqCst) {
return Err(DeploymentError::InsufficientResources {
resource: "Mock resource allocation failure".to_string(),
});
}
let delay = *self.system.deployment_delay.lock().await;
tokio::time::sleep(delay / 4).await;
Ok(())
}
async fn deallocate_resources(&self, _agent_id: AgentId) -> Result<(), DeploymentError> {
Ok(())
}
async fn check_resource_availability(
&self,
_requirements: &ResourceRequirements,
) -> Result<bool, DeploymentError> {
Ok(self.system.deployment_should_succeed.load(Ordering::SeqCst))
}
}
struct IntegratedMockInstanceManager {
system: IntegratedMockSystem,
}
#[async_trait::async_trait]
impl InstanceManager for IntegratedMockInstanceManager {
async fn deploy_instance(
&self,
agent_id: AgentId,
wasm_bytes: &[u8],
resources: &ResourceRequirements,
) -> Result<InstanceDeploymentResult, DeploymentError> {
self.system.deployment_count.fetch_add(1, Ordering::SeqCst);
let delay = *self.system.deployment_delay.lock().await;
tokio::time::sleep(delay * 3 / 4).await;
if wasm_bytes.is_empty() || !self.system.deployment_should_succeed.load(Ordering::SeqCst) {
return Ok(InstanceDeploymentResult {
success: false,
instance_id: format!("failed-instance-{agent_id}"),
duration: delay,
error: Some("Mock deployment failure".to_string()),
memory_used: 0,
fuel_consumed: 0,
});
}
let deployed_state = DeployedAgentState {
agent_id,
agent_name: None, current_version: AgentVersion::generate(),
wasm_module: wasm_bytes.to_vec(),
resources: resources.clone(),
deployed_at: SystemTime::now(),
is_healthy: true,
};
{
let mut deployed = self.system.deployed_agents.lock().await;
deployed.insert(agent_id, deployed_state);
}
Ok(InstanceDeploymentResult {
success: true,
instance_id: format!("instance-{agent_id}"),
duration: delay,
error: None,
memory_used: resources.memory_limit.into_inner(),
fuel_consumed: resources.fuel_limit.into_inner(),
})
}
async fn health_check(&self, agent_id: AgentId) -> Result<HealthCheckResult, DeploymentError> {
let deployed = self.system.deployed_agents.lock().await;
if let Some(agent_state) = deployed.get(&agent_id) {
Ok(HealthCheckResult {
healthy: agent_state.is_healthy
&& self.system.deployment_should_succeed.load(Ordering::SeqCst),
response_time: Duration::from_millis(10),
error: None,
})
} else {
Ok(HealthCheckResult {
healthy: false,
response_time: Duration::from_millis(100),
error: Some("Agent not deployed".to_string()),
})
}
}
async fn stop_instance(&self, agent_id: AgentId) -> Result<(), DeploymentError> {
let mut deployed = self.system.deployed_agents.lock().await;
deployed.remove(&agent_id);
Ok(())
}
async fn get_instance_metrics(
&self,
agent_id: AgentId,
) -> Result<(usize, u64), DeploymentError> {
let deployed = self.system.deployed_agents.lock().await;
if let Some(agent_state) = deployed.get(&agent_id) {
Ok((
agent_state.resources.memory_limit.into_inner(),
agent_state.resources.fuel_limit.into_inner(),
))
} else {
Err(DeploymentError::InsufficientResources {
resource: format!("Instance not found: instance-{agent_id}"),
})
}
}
}
struct IntegratedMockRuntimeManager {
system: IntegratedMockSystem,
}
#[async_trait::async_trait]
impl RuntimeManager for IntegratedMockRuntimeManager {
async fn create_instance(
&self,
agent_id: AgentId,
version: AgentVersion,
wasm_bytes: &[u8],
) -> Result<(), HotReloadError> {
self.system.hot_reload_count.fetch_add(1, Ordering::SeqCst);
if wasm_bytes.is_empty() {
return Err(HotReloadError::StatePreservationFailed {
reason: "Empty WASM module".to_string(),
});
}
let delay = *self.system.hot_reload_delay.lock().await;
tokio::time::sleep(delay / 2).await;
if !self.system.hot_reload_should_succeed.load(Ordering::SeqCst) {
return Err(HotReloadError::StatePreservationFailed {
reason: "Mock hot reload failure".to_string(),
});
}
{
let mut deployed = self.system.deployed_agents.lock().await;
if let Some(agent_state) = deployed.get_mut(&agent_id) {
agent_state.current_version = version;
agent_state.wasm_module = wasm_bytes.to_vec();
}
}
{
let mut active = self.system.active_versions.lock().await;
active.insert(agent_id, version);
}
Ok(())
}
async fn stop_instance(
&self,
agent_id: AgentId,
version: AgentVersion,
) -> Result<(), HotReloadError> {
let mut active = self.system.active_versions.lock().await;
if active.get(&agent_id) == Some(&version) {
active.remove(&agent_id);
}
Ok(())
}
async fn get_instance_metrics(
&self,
agent_id: AgentId,
_version: AgentVersion,
) -> Result<(usize, u64, u64), HotReloadError> {
let deployed = self.system.deployed_agents.lock().await;
if let Some(agent_state) = deployed.get(&agent_id) {
Ok((
agent_state.resources.memory_limit.into_inner(),
agent_state.resources.fuel_limit.into_inner(),
100, ))
} else {
Err(HotReloadError::StatePreservationFailed {
reason: "Agent not found".to_string(),
})
}
}
async fn preserve_state(
&self,
agent_id: AgentId,
version: AgentVersion,
) -> Result<Vec<u8>, HotReloadError> {
let deployed = self.system.deployed_agents.lock().await;
if deployed.contains_key(&agent_id) {
Ok(format!("state-{agent_id}-{version}").into_bytes())
} else {
Err(HotReloadError::StatePreservationFailed {
reason: "Agent not found for state preservation".to_string(),
})
}
}
async fn restore_state(
&self,
_agent_id: AgentId,
_version: AgentVersion,
_state_data: &[u8],
) -> Result<(), HotReloadError> {
Ok(())
}
async fn health_check(
&self,
agent_id: AgentId,
_version: AgentVersion,
) -> Result<bool, HotReloadError> {
let deployed = self.system.deployed_agents.lock().await;
let exists = deployed.contains_key(&agent_id);
let healthy = self.system.hot_reload_should_succeed.load(Ordering::SeqCst);
Ok(exists && healthy)
}
}
struct IntegratedMockTrafficRouter {
system: IntegratedMockSystem,
}
#[async_trait::async_trait]
impl TrafficRouter for IntegratedMockTrafficRouter {
async fn set_traffic_split(
&self,
agent_id: AgentId,
_old_version: AgentVersion,
new_version: AgentVersion,
split_percentage: TrafficSplitPercentage,
) -> Result<(), HotReloadError> {
if !self.system.hot_reload_should_succeed.load(Ordering::SeqCst) {
return Err(HotReloadError::TrafficSplittingFailed {
reason: "Mock traffic split failure".to_string(),
});
}
let mut splits = self.system.traffic_splits.lock().await;
splits.insert(agent_id, split_percentage);
if split_percentage.as_percentage() == 100 {
let mut active = self.system.active_versions.lock().await;
active.insert(agent_id, new_version);
}
Ok(())
}
async fn get_traffic_split(
&self,
agent_id: AgentId,
) -> Result<TrafficSplitPercentage, HotReloadError> {
let splits = self.system.traffic_splits.lock().await;
Ok(splits
.get(&agent_id)
.cloned()
.unwrap_or_else(|| TrafficSplitPercentage::full()))
}
async fn switch_traffic(
&self,
agent_id: AgentId,
target_version: AgentVersion,
) -> Result<(), HotReloadError> {
if !self.system.hot_reload_should_succeed.load(Ordering::SeqCst) {
return Err(HotReloadError::TrafficSplittingFailed {
reason: "Mock traffic switch failure".to_string(),
});
}
let mut active = self.system.active_versions.lock().await;
active.insert(agent_id, target_version);
let mut splits = self.system.traffic_splits.lock().await;
splits.insert(agent_id, TrafficSplitPercentage::full());
Ok(())
}
}
struct IntegratedMockWasmModuleValidator {
system: IntegratedMockSystem,
}
#[async_trait::async_trait]
impl WasmModuleValidatorTrait for IntegratedMockWasmModuleValidator {
async fn validate_module(
&self,
wasm_bytes: &[u8],
agent_name: Option<AgentName>,
) -> Result<WasmModule, WasmValidationError> {
self.system.validation_count.fetch_add(1, Ordering::SeqCst);
let delay = *self.system.validation_delay.lock().await;
tokio::time::sleep(delay).await;
if wasm_bytes.is_empty() {
return Err(WasmValidationError::EmptyModule);
}
if !self.system.validation_should_succeed.load(Ordering::SeqCst) {
return Err(WasmValidationError::InvalidFormat {
reason: "Mock validation failure".to_string(),
});
}
WasmModule::from_bytes(
AgentVersion::generate(),
VersionNumber::first(),
None,
agent_name,
wasm_bytes,
&WasmSecurityPolicy::testing(),
)
}
async fn validate_security(
&self,
_module: &WasmModule,
) -> Result<ValidationResult, WasmValidationError> {
Ok(ValidationResult::Valid)
}
async fn extract_metadata(
&self,
_wasm_bytes: &[u8],
) -> Result<HashMap<String, String>, WasmValidationError> {
Ok(HashMap::new())
}
}
struct IntegrationTestFixture {
lifecycle_manager: AgentLifecycleManager,
system: IntegratedMockSystem,
}
impl IntegrationTestFixture {
fn new() -> Self {
let system = IntegratedMockSystem::new();
let resource_allocator = Arc::new(IntegratedMockResourceAllocator {
system: system.clone(),
});
let instance_manager = Arc::new(IntegratedMockInstanceManager {
system: system.clone(),
});
let deployment_manager = Arc::new(CaxtonDeploymentManager::new(
resource_allocator,
instance_manager,
));
let runtime_manager = Arc::new(IntegratedMockRuntimeManager {
system: system.clone(),
});
let traffic_router = Arc::new(IntegratedMockTrafficRouter {
system: system.clone(),
});
let hot_reload_manager = Arc::new(CaxtonHotReloadManager::with_time_provider(
runtime_manager,
traffic_router,
test_time_provider(),
));
let module_validator = Arc::new(IntegratedMockWasmModuleValidator {
system: system.clone(),
});
let lifecycle_manager =
AgentLifecycleManager::new(deployment_manager, hot_reload_manager, module_validator);
Self {
lifecycle_manager,
system,
}
}
fn create_test_agent_data(
&self,
) -> (
AgentId,
AgentName,
AgentVersion,
VersionNumber,
DeploymentConfig,
Vec<u8>,
) {
(
AgentId::generate(),
AgentName::try_new("integration-test-agent".to_string()).unwrap(),
AgentVersion::generate(),
VersionNumber::first(),
DeploymentConfig::immediate(),
self.create_valid_wasm_bytes(),
)
}
fn create_valid_wasm_bytes(&self) -> Vec<u8> {
vec![
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
]
}
}
#[test(tokio::test)]
async fn test_story_003_agents_can_be_deployed_from_wasm_modules() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
let result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await;
assert!(result.is_ok());
let deployment_result = result.unwrap();
assert!(deployment_result.status.is_success());
assert!(fixture.system.is_agent_deployed(agent_id).await);
assert_eq!(fixture.system.get_deployment_count(), 1);
assert_eq!(fixture.system.get_validation_count(), 1);
}
#[test(tokio::test)]
async fn test_story_003_agent_state_transitions_follow_defined_lifecycle() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Ready);
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
fixture
.lifecycle_manager
.stop_agent(agent_id, Some(Duration::from_millis(100)))
.await
.unwrap();
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Stopped);
}
#[test(tokio::test)]
async fn test_story_003_hot_reload_deploys_new_versions_without_downtime() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, from_version, from_version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name.clone()),
from_version,
from_version_number,
config,
wasm_bytes,
)
.await
.unwrap();
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
let to_version = AgentVersion::generate();
let to_version_number = from_version_number.next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Graceful);
let new_wasm_bytes = fixture.create_valid_wasm_bytes();
let start_time = std::time::Instant::now();
let result = fixture
.lifecycle_manager
.hot_reload_agent(
agent_id,
from_version,
to_version,
to_version_number,
reload_config,
new_wasm_bytes,
)
.await;
let hot_reload_time = start_time.elapsed();
assert!(result.is_ok());
let reload_result = result.unwrap();
assert!(reload_result.status.is_success());
assert!(hot_reload_time < Duration::from_secs(75));
let lifecycle = fixture
.lifecycle_manager
.get_agent_lifecycle(agent_id)
.await
.unwrap();
assert_eq!(lifecycle.version, to_version);
assert_eq!(lifecycle.version_number, to_version_number);
assert_eq!(fixture.system.get_hot_reload_count(), 1);
}
#[test(tokio::test)]
async fn test_story_003_resource_limits_set_during_deployment() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, mut config, wasm_bytes) =
fixture.create_test_agent_data();
config.resource_requirements.memory_limit =
caxton::domain::DeploymentMemoryLimit::try_new(2 * 1024 * 1024).unwrap(); config.resource_requirements.fuel_limit =
caxton::domain::DeploymentFuelLimit::try_new(5_000_000).unwrap();
let result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config.clone(),
wasm_bytes,
)
.await
.unwrap();
assert!(result.status.is_success());
let metrics = result.metrics.unwrap();
assert_eq!(metrics.memory_usage_peak, 2 * 1024 * 1024);
assert_eq!(metrics.fuel_consumed, 5_000_000);
}
#[test(tokio::test)]
async fn test_story_003_failed_agents_dont_affect_other_agents() {
let fixture = IntegrationTestFixture::new();
let (agent1_id, agent1_name, version1, version_number1, config1, wasm_bytes1) =
fixture.create_test_agent_data();
fixture
.lifecycle_manager
.deploy_agent(
agent1_id,
Some(agent1_name),
version1,
version_number1,
config1,
wasm_bytes1,
)
.await
.unwrap();
fixture
.lifecycle_manager
.start_agent(agent1_id)
.await
.unwrap();
fixture.system.set_deployment_success(false);
let (agent2_id, agent2_name, version2, version_number2, config2, wasm_bytes2) =
fixture.create_test_agent_data();
let result2 = fixture
.lifecycle_manager
.deploy_agent(
agent2_id,
Some(agent2_name),
version2,
version_number2,
config2,
wasm_bytes2,
)
.await;
assert!(result2.is_err());
let status1 = fixture
.lifecycle_manager
.get_agent_status(agent1_id)
.await
.unwrap();
assert_eq!(
status1.lifecycle.current_state,
AgentLifecycleState::Running
);
assert!(matches!(status1.health_status, HealthStatus::Healthy));
assert!(
fixture
.lifecycle_manager
.get_agent_status(agent2_id)
.await
.is_err()
);
}
#[test(tokio::test)]
async fn test_story_003_deployment_validates_wasm_modules_before_activation() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, _) =
fixture.create_test_agent_data();
let empty_wasm = vec![];
let result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
empty_wasm,
)
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LifecycleError::ValidationFailed { .. }
));
assert_eq!(fixture.system.get_deployment_count(), 0); assert_eq!(fixture.system.get_validation_count(), 1); }
#[test(tokio::test)]
async fn test_story_003_all_state_transitions_are_type_safe_and_tested() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
fixture
.lifecycle_manager
.stop_agent(agent_id, Some(Duration::from_millis(50)))
.await
.unwrap();
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Stopped);
}
#[test(tokio::test)]
async fn test_story_003_deployment_completes_in_less_than_1_second() {
let fixture = IntegrationTestFixture::new();
fixture
.system
.set_deployment_delay(Duration::from_millis(200))
.await;
fixture
.system
.set_validation_delay(Duration::from_millis(50))
.await;
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
let start_time = std::time::Instant::now();
let result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await;
let elapsed = start_time.elapsed();
assert!(result.is_ok());
assert!(elapsed < Duration::from_secs(1)); }
#[test(tokio::test)]
async fn test_story_003_hot_reload_maintains_message_processing() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, from_version, from_version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name.clone()),
from_version,
from_version_number,
config,
wasm_bytes,
)
.await
.unwrap();
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
let to_version = AgentVersion::generate();
let to_version_number = from_version_number.next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Graceful);
let new_wasm_bytes = fixture.create_valid_wasm_bytes();
let result = fixture
.lifecycle_manager
.hot_reload_agent(
agent_id,
from_version,
to_version,
to_version_number,
reload_config,
new_wasm_bytes,
)
.await;
assert!(result.is_ok());
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
assert_eq!(status.lifecycle.version, to_version);
}
#[test(tokio::test)]
async fn test_story_003_resource_cleanup_is_verified() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
assert!(fixture.system.is_agent_deployed(agent_id).await);
assert_eq!(fixture.system.get_deployed_agent_count().await, 1);
let result = fixture
.lifecycle_manager
.remove_agent(agent_id)
.await
.unwrap();
assert!(result.success);
assert_eq!(fixture.system.get_deployed_agent_count().await, 0);
assert!(
fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.is_err()
);
}
#[test(tokio::test)]
async fn test_story_003_api_provides_lifecycle_operations() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
let deploy_result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await;
assert!(deploy_result.is_ok());
let status = fixture.lifecycle_manager.get_agent_status(agent_id).await;
assert!(status.is_ok());
let lifecycle = fixture
.lifecycle_manager
.get_agent_lifecycle(agent_id)
.await;
assert!(lifecycle.is_ok());
let agent_list = fixture.lifecycle_manager.list_agents().await;
assert_eq!(agent_list.len(), 1);
assert!(agent_list.contains_key(&agent_id));
let start_result = fixture.lifecycle_manager.start_agent(agent_id).await;
assert!(start_result.is_ok());
let to_version = AgentVersion::generate();
let to_version_number = VersionNumber::first().next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Immediate);
let new_wasm_bytes = fixture.create_valid_wasm_bytes();
let hot_reload_result = fixture
.lifecycle_manager
.hot_reload_agent(
agent_id,
version,
to_version,
to_version_number,
reload_config,
new_wasm_bytes,
)
.await;
assert!(hot_reload_result.is_ok());
let stop_result = fixture
.lifecycle_manager
.stop_agent(agent_id, Some(Duration::from_millis(100)))
.await;
assert!(stop_result.is_ok());
let remove_result = fixture.lifecycle_manager.remove_agent(agent_id).await;
assert!(remove_result.is_ok());
}
#[test(tokio::test)]
async fn test_complete_agent_lifecycle_integration() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
let start_time = std::time::Instant::now();
let deployment_result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name.clone()),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
assert!(deployment_result.status.is_success());
assert!(fixture.system.is_agent_deployed(agent_id).await);
let start_result = fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
assert!(start_result.success);
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
assert!(matches!(status.health_status, HealthStatus::Healthy));
let new_version = AgentVersion::generate();
let new_version_number = version_number.next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Graceful);
let new_wasm_bytes = fixture.create_valid_wasm_bytes();
let reload_result = fixture
.lifecycle_manager
.hot_reload_agent(
agent_id,
version,
new_version,
new_version_number,
reload_config,
new_wasm_bytes,
)
.await
.unwrap();
assert!(reload_result.status.is_success());
let updated_lifecycle = fixture
.lifecycle_manager
.get_agent_lifecycle(agent_id)
.await
.unwrap();
assert_eq!(updated_lifecycle.version, new_version);
assert_eq!(updated_lifecycle.version_number, new_version_number);
assert_eq!(
updated_lifecycle.current_state,
AgentLifecycleState::Running
);
let stop_result = fixture
.lifecycle_manager
.stop_agent(agent_id, Some(Duration::from_millis(200)))
.await
.unwrap();
assert!(stop_result.success);
let final_status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(
final_status.lifecycle.current_state,
AgentLifecycleState::Stopped
);
let remove_result = fixture
.lifecycle_manager
.remove_agent(agent_id)
.await
.unwrap();
assert!(remove_result.success);
assert!(
fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.is_err()
);
let total_time = start_time.elapsed();
assert!(total_time < Duration::from_secs(10)); assert_eq!(fixture.system.get_deployment_count(), 1);
assert_eq!(fixture.system.get_hot_reload_count(), 1);
assert_eq!(fixture.system.get_validation_count(), 2); }
#[test(tokio::test)]
async fn test_multi_agent_isolation_and_independence() {
let fixture = IntegrationTestFixture::new();
let mut agents = Vec::new();
for i in 0..3 {
let (agent_id, _, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
let agent_name = AgentName::try_new(format!("test-agent-{i}")).unwrap();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
agents.push((agent_id, version));
}
for (agent_id, _) in &agents {
let status = fixture
.lifecycle_manager
.get_agent_status(*agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
}
fixture.system.set_hot_reload_success(false);
let (failing_agent, failing_version) = agents[1];
let new_version = AgentVersion::generate();
let new_version_number = VersionNumber::first().next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Immediate);
let result = fixture
.lifecycle_manager
.hot_reload_agent(
failing_agent,
failing_version,
new_version,
new_version_number,
reload_config,
fixture.create_valid_wasm_bytes(),
)
.await;
assert!(result.is_err());
let failing_status = fixture
.lifecycle_manager
.get_agent_status(failing_agent)
.await
.unwrap();
assert_eq!(
failing_status.lifecycle.current_state,
AgentLifecycleState::Running
);
for (agent_id, _) in &agents {
if *agent_id != failing_agent {
let status = fixture
.lifecycle_manager
.get_agent_status(*agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
assert!(matches!(status.health_status, HealthStatus::Healthy));
}
}
assert_eq!(fixture.system.get_deployed_agent_count().await, 3);
assert_eq!(fixture.system.get_deployment_count(), 3);
assert_eq!(fixture.system.get_hot_reload_count(), 1);
}
#[test(tokio::test)]
async fn test_error_propagation_and_handling_across_components() {
let fixture = IntegrationTestFixture::new();
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
fixture.system.set_validation_success(false);
let result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name.clone()),
version,
version_number,
config.clone(),
wasm_bytes.clone(),
)
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LifecycleError::ValidationFailed { .. }
));
fixture.system.set_validation_success(true);
fixture.system.set_deployment_success(false);
let result = fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name.clone()),
version,
version_number,
config.clone(),
wasm_bytes.clone(),
)
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LifecycleError::DeploymentError(_)
));
fixture.system.set_deployment_success(true);
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
fixture.system.set_hot_reload_success(false);
let to_version = AgentVersion::generate();
let to_version_number = version_number.next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Graceful);
let result = fixture
.lifecycle_manager
.hot_reload_agent(
agent_id,
version,
to_version,
to_version_number,
reload_config,
fixture.create_valid_wasm_bytes(),
)
.await;
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
LifecycleError::HotReloadError(_)
));
let status = fixture
.lifecycle_manager
.get_agent_status(agent_id)
.await
.unwrap();
assert_eq!(status.lifecycle.current_state, AgentLifecycleState::Running);
}
#[test(tokio::test)]
async fn test_performance_requirements_across_all_operations() {
let fixture = IntegrationTestFixture::new();
fixture
.system
.set_deployment_delay(Duration::from_millis(100))
.await;
fixture
.system
.set_hot_reload_delay(Duration::from_millis(150))
.await;
fixture
.system
.set_validation_delay(Duration::from_millis(20))
.await;
let (agent_id, agent_name, version, version_number, config, wasm_bytes) =
fixture.create_test_agent_data();
let start_time = std::time::Instant::now();
fixture
.lifecycle_manager
.deploy_agent(
agent_id,
Some(agent_name),
version,
version_number,
config,
wasm_bytes,
)
.await
.unwrap();
let deployment_time = start_time.elapsed();
assert!(deployment_time < Duration::from_secs(1));
let start_time = std::time::Instant::now();
fixture
.lifecycle_manager
.start_agent(agent_id)
.await
.unwrap();
let start_time_elapsed = start_time.elapsed();
assert!(start_time_elapsed < Duration::from_millis(100));
let start_time = std::time::Instant::now();
let to_version = AgentVersion::generate();
let to_version_number = version_number.next().unwrap();
let reload_config = HotReloadConfig::new(HotReloadStrategy::Immediate);
fixture
.lifecycle_manager
.hot_reload_agent(
agent_id,
version,
to_version,
to_version_number,
reload_config,
fixture.create_valid_wasm_bytes(),
)
.await
.unwrap();
let hot_reload_time = start_time.elapsed();
assert!(hot_reload_time < Duration::from_secs(1));
let start_time = std::time::Instant::now();
fixture
.lifecycle_manager
.stop_agent(agent_id, Some(Duration::from_millis(50)))
.await
.unwrap();
let stop_time = start_time.elapsed();
assert!(stop_time < Duration::from_millis(500));
}