//! Integration tests for CLI rules, triggers, and sensors commands #![allow(deprecated)] use assert_cmd::Command; use predicates::prelude::*; use serde_json::json; use wiremock::{ matchers::{method, path}, Mock, ResponseTemplate, }; mod common; use common::*; // ============================================================================ // Rule Tests // ============================================================================ #[tokio::test] async fn test_rule_list_authenticated() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock rule list endpoint mock_rule_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("rule") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains("On Webhook")); } #[tokio::test] async fn test_rule_list_unauthenticated() { let fixture = TestFixture::new().await; fixture.write_default_config(); // Mock unauthorized response mock_unauthorized(&fixture.mock_server, "/api/v1/rules").await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("rule") .arg("list"); cmd.assert().failure(); } #[tokio::test] async fn test_rule_list_json_output() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock rule list endpoint mock_rule_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("--json") .arg("rule") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains(r#""ref": "core.on_webhook""#)); } #[tokio::test] async fn test_rule_list_yaml_output() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock rule list endpoint mock_rule_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("--yaml") .arg("rule") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains("ref: core.on_webhook")); } #[tokio::test] async fn test_rule_get_by_ref() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock rule get endpoint Mock::given(method("GET")) .and(path("/api/v1/rules/core.on_webhook")) .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(&fixture.mock_server) .await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("rule") .arg("show") .arg("core.on_webhook"); cmd.assert() .success() .stdout(predicate::str::contains("On Webhook")) .stdout(predicate::str::contains("Handle webhook events")); } #[tokio::test] async fn test_rule_get_not_found() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock 404 response mock_not_found(&fixture.mock_server, "/api/v1/rules/nonexistent.rule").await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("rule") .arg("show") .arg("nonexistent.rule"); cmd.assert() .failure() .stderr(predicate::str::contains("Error")); } #[tokio::test] async fn test_rule_list_by_pack() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock rule list endpoint with pack filter via query parameter mock_rule_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("rule") .arg("list") .arg("--pack") .arg("core"); cmd.assert().success(); } // ============================================================================ // Trigger Tests // ============================================================================ #[tokio::test] async fn test_trigger_list_authenticated() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock trigger list endpoint mock_trigger_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("trigger") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains("Webhook Trigger")); } #[tokio::test] async fn test_trigger_list_unauthenticated() { let fixture = TestFixture::new().await; fixture.write_default_config(); // Mock unauthorized response mock_unauthorized(&fixture.mock_server, "/api/v1/triggers").await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("trigger") .arg("list"); cmd.assert().failure(); } #[tokio::test] async fn test_trigger_list_json_output() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock trigger list endpoint mock_trigger_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("--json") .arg("trigger") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains(r#""ref": "core.webhook""#)); } #[tokio::test] async fn test_trigger_list_yaml_output() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock trigger list endpoint mock_trigger_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("--yaml") .arg("trigger") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains("ref: core.webhook")); } #[tokio::test] async fn test_trigger_get_by_ref() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock trigger get endpoint Mock::given(method("GET")) .and(path("/api/v1/triggers/core.webhook")) .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(&fixture.mock_server) .await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("trigger") .arg("show") .arg("core.webhook"); cmd.assert() .success() .stdout(predicate::str::contains("Webhook Trigger")) .stdout(predicate::str::contains("Webhook trigger")); } #[tokio::test] async fn test_trigger_get_not_found() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock 404 response mock_not_found(&fixture.mock_server, "/api/v1/triggers/nonexistent.trigger").await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("trigger") .arg("show") .arg("nonexistent.trigger"); cmd.assert() .failure() .stderr(predicate::str::contains("Error")); } // ============================================================================ // Sensor Tests // ============================================================================ #[tokio::test] async fn test_sensor_list_authenticated() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock sensor list endpoint mock_sensor_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("sensor") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains("Webhook Sensor")); } #[tokio::test] async fn test_sensor_list_unauthenticated() { let fixture = TestFixture::new().await; fixture.write_default_config(); // Mock unauthorized response mock_unauthorized(&fixture.mock_server, "/api/v1/sensors").await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("sensor") .arg("list"); cmd.assert().failure(); } #[tokio::test] async fn test_sensor_list_json_output() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock sensor list endpoint mock_sensor_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("--json") .arg("sensor") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains(r#""ref": "core.webhook_sensor""#)); } #[tokio::test] async fn test_sensor_list_yaml_output() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock sensor list endpoint mock_sensor_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("--yaml") .arg("sensor") .arg("list"); cmd.assert() .success() .stdout(predicate::str::contains("ref: core.webhook_sensor")); } #[tokio::test] async fn test_sensor_get_by_ref() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock sensor get endpoint Mock::given(method("GET")) .and(path("/api/v1/sensors/core.webhook_sensor")) .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(&fixture.mock_server) .await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("sensor") .arg("show") .arg("core.webhook_sensor"); cmd.assert() .success() .stdout(predicate::str::contains("Webhook Sensor")) .stdout(predicate::str::contains("Webhook sensor")); } #[tokio::test] async fn test_sensor_get_not_found() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock 404 response mock_not_found(&fixture.mock_server, "/api/v1/sensors/nonexistent.sensor").await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("sensor") .arg("show") .arg("nonexistent.sensor"); cmd.assert() .failure() .stderr(predicate::str::contains("Error")); } #[tokio::test] async fn test_sensor_list_by_pack() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock sensor list endpoint with pack filter via query parameter mock_sensor_list(&fixture.mock_server).await; let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("sensor") .arg("list") .arg("--pack") .arg("core"); cmd.assert().success(); } // ============================================================================ // Cross-feature Tests // ============================================================================ #[tokio::test] async fn test_all_list_commands_with_profile() { let fixture = TestFixture::new().await; // Create multi-profile config let config = format!( r#" current_profile: default default_output_format: table profiles: default: api_url: {} auth_token: default_token refresh_token: default_refresh staging: api_url: {} auth_token: staging_token refresh_token: staging_refresh "#, fixture.server_url(), fixture.server_url() ); fixture.write_config(&config); // Mock all list endpoints mock_rule_list(&fixture.mock_server).await; mock_trigger_list(&fixture.mock_server).await; mock_sensor_list(&fixture.mock_server).await; // Test rule list with profile let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--profile") .arg("staging") .arg("rule") .arg("list"); cmd.assert().success(); // Test trigger list with profile let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--profile") .arg("staging") .arg("trigger") .arg("list"); cmd.assert().success(); // Test sensor list with profile let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--profile") .arg("staging") .arg("sensor") .arg("list"); cmd.assert().success(); } #[tokio::test] async fn test_empty_list_results() { let fixture = TestFixture::new().await; fixture.write_authenticated_config("valid_token", "refresh_token"); // Mock empty lists Mock::given(method("GET")) .and(path("/api/v1/rules")) .respond_with(ResponseTemplate::new(200).set_body_json(json!({"data": []}))) .mount(&fixture.mock_server) .await; Mock::given(method("GET")) .and(path("/api/v1/triggers")) .respond_with(ResponseTemplate::new(200).set_body_json(json!({"data": []}))) .mount(&fixture.mock_server) .await; Mock::given(method("GET")) .and(path("/api/v1/sensors")) .respond_with(ResponseTemplate::new(200).set_body_json(json!({"data": []}))) .mount(&fixture.mock_server) .await; // All should succeed with empty results let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("rule") .arg("list"); cmd.assert().success(); let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("trigger") .arg("list"); cmd.assert().success(); let mut cmd = Command::cargo_bin("attune").unwrap(); cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path()) .env("HOME", fixture.config_dir_path()) .arg("--api-url") .arg(fixture.server_url()) .arg("sensor") .arg("list"); cmd.assert().success(); }