#[cfg(test)]
mod tests {
use pywatt_sdk::communication::ipc_types::{OrchestratorToModule, RotatedNotification, SecretValueResponse};
use pywatt_sdk::security::secret_client::{RequestMode, SecretClient};
use std::sync::Arc;
use tokio::sync::mpsc;
struct MockStdin {
receiver: mpsc::Receiver<String>,
}
impl MockStdin {
fn new(receiver: mpsc::Receiver<String>) -> Self {
Self { receiver }
}
async fn read_line(&mut self, buf: &mut String) -> std::io::Result<usize> {
if let Some(line) = self.receiver.recv().await {
buf.push_str(&line);
Ok(line.len())
} else {
Ok(0) }
}
}
async fn run_ipc_loop(mut mock_stdin: MockStdin, client: Arc<SecretClient>) {
let mut line = String::new();
loop {
line.clear();
match mock_stdin.read_line(&mut line).await {
Ok(0) => {
break;
}
Ok(_) => {
let trimmed = line.trim_end();
match serde_json::from_str::<OrchestratorToModule>(trimmed) {
Ok(msg) => match msg {
OrchestratorToModule::Secret(_) | OrchestratorToModule::Rotated(_) => {
if let Err(e) = client.process_server_message(trimmed).await {
eprintln!("Error processing secret RPC: {}", e);
}
}
OrchestratorToModule::Shutdown => {
break;
}
OrchestratorToModule::Init(_) => {
}
OrchestratorToModule::ServiceResponse(_)
| OrchestratorToModule::ServiceOperationResult(_)
| OrchestratorToModule::HttpRequest(_) => {
}
_ => {}
},
Err(e) => {
eprintln!("Failed to parse IPC message: {} raw: {}", e, trimmed);
}
}
}
Err(e) => {
eprintln!("Error reading IPC stdin: {}", e);
break;
}
}
}
}
#[tokio::test]
async fn test_process_secret_message() {
let (sender, receiver) = mpsc::channel(10);
let mock_stdin = MockStdin::new(receiver);
let client = SecretClient::new_dummy();
let client_clone = Arc::new(client);
let ipc_task = tokio::spawn(run_ipc_loop(mock_stdin, client_clone.clone()));
let secret_msg = OrchestratorToModule::Secret(SecretValueResponse {
name: "NEW_SECRET".to_string(),
value: "secret_value".to_string(),
rotation_id: None,
});
let json = serde_json::to_string(&secret_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
let shutdown_msg = OrchestratorToModule::Shutdown;
let json = serde_json::to_string(&shutdown_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
ipc_task.await.unwrap();
let result = client_clone
.get_secret("NEW_SECRET", RequestMode::CacheThenRemote)
.await;
assert!(result.is_ok());
let secret_string = result.unwrap();
assert_eq!(
secrecy::ExposeSecret::expose_secret(&secret_string),
"secret_value"
);
}
#[tokio::test]
async fn test_process_rotation_message() {
let (sender, receiver) = mpsc::channel(10);
let mock_stdin = MockStdin::new(receiver);
let (verify_tx, verify_rx) = mpsc::channel::<Vec<String>>(1);
let client = SecretClient::new_dummy();
let client_clone = Arc::new(client);
let ipc_task = tokio::spawn(run_ipc_loop(mock_stdin, client_clone.clone()));
let rotated_msg = OrchestratorToModule::Rotated(RotatedNotification {
keys: vec!["SECRET1".to_string(), "SECRET2".to_string()],
rotation_id: "rotation-123".to_string(),
});
let json = serde_json::to_string(&rotated_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
let secret1_msg = OrchestratorToModule::Secret(SecretValueResponse {
name: "SECRET1".to_string(),
value: "new_value1".to_string(),
rotation_id: Some("rotation-123".to_string()),
});
let json = serde_json::to_string(&secret1_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
let secret2_msg = OrchestratorToModule::Secret(SecretValueResponse {
name: "SECRET2".to_string(),
value: "new_value2".to_string(),
rotation_id: Some("rotation-123".to_string()),
});
let json = serde_json::to_string(&secret2_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
let shutdown_msg = OrchestratorToModule::Shutdown;
let json = serde_json::to_string(&shutdown_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
ipc_task.await.unwrap();
let secret1 = client_clone
.get_secret("SECRET1", RequestMode::CacheThenRemote)
.await
.unwrap();
let secret2 = client_clone
.get_secret("SECRET2", RequestMode::CacheThenRemote)
.await
.unwrap();
assert_eq!(secrecy::ExposeSecret::expose_secret(&secret1), "new_value1");
assert_eq!(secrecy::ExposeSecret::expose_secret(&secret2), "new_value2");
}
#[tokio::test]
async fn test_process_invalid_message() {
let (sender, receiver) = mpsc::channel(10);
let mock_stdin = MockStdin::new(receiver);
let client = SecretClient::new_dummy();
let client_clone = Arc::new(client);
let ipc_task = tokio::spawn(run_ipc_loop(mock_stdin, client_clone.clone()));
sender.send("{ invalid json }\n".to_string()).await.unwrap();
let shutdown_msg = OrchestratorToModule::Shutdown;
let json = serde_json::to_string(&shutdown_msg).unwrap();
sender.send(format!("{}\n", json)).await.unwrap();
ipc_task.await.unwrap();
}
}