Files
attune/crates/cli/tests/common/mod.rs
2026-02-04 17:46:30 -06:00

446 lines
14 KiB
Rust

use serde_json::json;
use std::path::PathBuf;
use tempfile::TempDir;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
/// Test fixture for CLI integration tests
pub struct TestFixture {
pub mock_server: MockServer,
pub config_dir: TempDir,
pub config_path: PathBuf,
}
impl TestFixture {
/// Create a new test fixture with a mock API server
pub async fn new() -> Self {
let mock_server = MockServer::start().await;
let config_dir = TempDir::new().expect("Failed to create temp dir");
// Create attune subdirectory to match actual config path structure
let attune_dir = config_dir.path().join("attune");
std::fs::create_dir_all(&attune_dir).expect("Failed to create attune config dir");
let config_path = attune_dir.join("config.yaml");
Self {
mock_server,
config_dir,
config_path,
}
}
/// Get the mock server URI
pub fn server_url(&self) -> String {
self.mock_server.uri()
}
/// Get the config directory path
pub fn config_dir_path(&self) -> &std::path::Path {
self.config_dir.path()
}
/// Write a test config file with the mock server URL
pub fn write_config(&self, content: &str) {
std::fs::write(&self.config_path, content).expect("Failed to write config");
}
/// Write a default config with the mock server
pub fn write_default_config(&self) {
let config = format!(
r#"
current_profile: default
default_output_format: table
profiles:
default:
api_url: {}
description: Test server
"#,
self.server_url()
);
self.write_config(&config);
}
/// Write a config with authentication tokens
pub fn write_authenticated_config(&self, access_token: &str, refresh_token: &str) {
let config = format!(
r#"
current_profile: default
default_output_format: table
profiles:
default:
api_url: {}
auth_token: {}
refresh_token: {}
description: Test server
"#,
self.server_url(),
access_token,
refresh_token
);
self.write_config(&config);
}
/// Write a config with multiple profiles
#[allow(dead_code)]
pub fn write_multi_profile_config(&self) {
let config = format!(
r#"
current_profile: default
default_output_format: table
profiles:
default:
api_url: {}
description: Default test server
staging:
api_url: https://staging.example.com
description: Staging environment
production:
api_url: https://api.example.com
description: Production environment
output_format: json
"#,
self.server_url()
);
self.write_config(&config);
}
}
/// Mock a successful login response
#[allow(dead_code)]
pub async fn mock_login_success(server: &MockServer, access_token: &str, refresh_token: &str) {
Mock::given(method("POST"))
.and(path("/auth/login"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": {
"access_token": access_token,
"refresh_token": refresh_token,
"expires_in": 3600
}
})))
.mount(server)
.await;
}
/// Mock a failed login response
#[allow(dead_code)]
pub async fn mock_login_failure(server: &MockServer) {
Mock::given(method("POST"))
.and(path("/auth/login"))
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"error": "Invalid credentials"
})))
.mount(server)
.await;
}
/// Mock a whoami response
#[allow(dead_code)]
pub async fn mock_whoami_success(server: &MockServer, username: &str, email: &str) {
Mock::given(method("GET"))
.and(path("/auth/whoami"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": {
"id": 1,
"name": "Test User",
"username": username,
"email": email,
"identity_type": "user",
"enabled": true,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
})))
.mount(server)
.await;
}
/// Mock an unauthorized response
#[allow(dead_code)]
pub async fn mock_unauthorized(server: &MockServer, path_pattern: &str) {
Mock::given(method("GET"))
.and(path(path_pattern))
.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"error": "Unauthorized"
})))
.mount(server)
.await;
}
/// Mock a pack list response
#[allow(dead_code)]
pub async fn mock_pack_list(server: &MockServer) {
Mock::given(method("GET"))
.and(path("/api/v1/packs"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{
"id": 1,
"ref": "core",
"label": "Core Pack",
"description": "Core pack",
"version": "1.0.0",
"author": "Attune",
"enabled": true,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
},
{
"id": 2,
"ref": "linux",
"label": "Linux Pack",
"description": "Linux automation pack",
"version": "1.0.0",
"author": "Attune",
"enabled": true,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
]
})))
.mount(server)
.await;
}
/// Mock a pack get response
#[allow(dead_code)]
pub async fn mock_pack_get(server: &MockServer, pack_ref: &str) {
let path_pattern = format!("/api/v1/packs/{}", pack_ref);
// Capitalize first letter for label
let label = pack_ref
.chars()
.enumerate()
.map(|(i, c)| {
if i == 0 {
c.to_uppercase().next().unwrap()
} else {
c
}
})
.collect::<String>();
Mock::given(method("GET"))
.and(path(path_pattern.as_str()))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": {
"id": 1,
"ref": pack_ref,
"label": format!("{} Pack", label),
"description": format!("{} pack", pack_ref),
"version": "1.0.0",
"author": "Attune",
"enabled": true,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
})))
.mount(server)
.await;
}
/// Mock an action list response
#[allow(dead_code)]
pub async fn mock_action_list(server: &MockServer) {
Mock::given(method("GET"))
.and(path("/api/v1/actions"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{
"id": 1,
"ref": "core.echo",
"pack_ref": "core",
"label": "Echo Action",
"description": "Echo a message",
"entrypoint": "echo.py",
"runtime": null,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
],
"meta": {
"page": 1,
"limit": 50,
"total": 1,
"total_pages": 1
}
})))
.mount(server)
.await;
}
/// Mock an action execution response
#[allow(dead_code)]
pub async fn mock_action_execute(server: &MockServer, execution_id: i64) {
Mock::given(method("POST"))
.and(path("/api/v1/executions/execute"))
.respond_with(ResponseTemplate::new(201).set_body_json(json!({
"data": {
"id": execution_id,
"action": 1,
"action_ref": "core.echo",
"config": {},
"parent": null,
"enforcement": null,
"executor": null,
"status": "scheduled",
"result": null,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
})))
.mount(server)
.await;
}
/// Mock an execution get response
#[allow(dead_code)]
pub async fn mock_execution_get(server: &MockServer, execution_id: i64, status: &str) {
let path_pattern = format!("/api/v1/executions/{}", execution_id);
Mock::given(method("GET"))
.and(path(path_pattern.as_str()))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": {
"id": execution_id,
"action": 1,
"action_ref": "core.echo",
"config": {"message": "Hello"},
"parent": null,
"enforcement": null,
"executor": null,
"status": status,
"result": {"output": "Hello"},
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
})))
.mount(server)
.await;
}
/// Mock an execution list response with filters
#[allow(dead_code)]
pub async fn mock_execution_list(server: &MockServer) {
Mock::given(method("GET"))
.and(path("/api/v1/executions"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{
"id": 1,
"action_ref": "core.echo",
"status": "succeeded",
"parent": null,
"enforcement": null,
"result": {"output": "Hello"},
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
},
{
"id": 2,
"action_ref": "core.echo",
"status": "failed",
"parent": null,
"enforcement": null,
"result": {"error": "Command failed"},
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
]
})))
.mount(server)
.await;
}
/// Mock a rule list response
#[allow(dead_code)]
pub async fn mock_rule_list(server: &MockServer) {
Mock::given(method("GET"))
.and(path("/api/v1/rules"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{
"id": 1,
"ref": "core.on_webhook",
"pack": 1,
"pack_ref": "core",
"label": "On Webhook",
"description": "Handle webhook events",
"trigger": 1,
"trigger_ref": "core.webhook",
"action": 1,
"action_ref": "core.echo",
"enabled": true,
"conditions": {},
"action_params": {},
"trigger_params": {},
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
]
})))
.mount(server)
.await;
}
/// Mock a trigger list response
#[allow(dead_code)]
pub async fn mock_trigger_list(server: &MockServer) {
Mock::given(method("GET"))
.and(path("/api/v1/triggers"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{
"id": 1,
"ref": "core.webhook",
"pack": 1,
"pack_ref": "core",
"label": "Webhook Trigger",
"description": "Webhook trigger",
"enabled": true,
"param_schema": {},
"out_schema": {},
"webhook_enabled": false,
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
]
})))
.mount(server)
.await;
}
/// Mock a sensor list response
#[allow(dead_code)]
pub async fn mock_sensor_list(server: &MockServer) {
Mock::given(method("GET"))
.and(path("/api/v1/sensors"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"data": [
{
"id": 1,
"ref": "core.webhook_sensor",
"pack": 1,
"pack_ref": "core",
"label": "Webhook Sensor",
"description": "Webhook sensor",
"enabled": true,
"trigger_types": ["core.webhook"],
"entry_point": "webhook_sensor.py",
"created": "2024-01-01T00:00:00Z",
"updated": "2024-01-01T00:00:00Z"
}
]
})))
.mount(server)
.await;
}
/// Mock a 404 not found response
#[allow(dead_code)]
pub async fn mock_not_found(server: &MockServer, path_pattern: &str) {
Mock::given(method("GET"))
.and(path(path_pattern))
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
"error": "Not found"
})))
.mount(server)
.await;
}