[wip] cli capability parity
Some checks failed
CI / Rustfmt (push) Successful in 23s
CI / Cargo Audit & Deny (push) Successful in 30s
CI / Web Blocking Checks (push) Successful in 48s
CI / Security Blocking Checks (push) Successful in 8s
CI / Clippy (push) Failing after 1m55s
CI / Web Advisory Checks (push) Successful in 35s
CI / Security Advisory Checks (push) Successful in 37s
CI / Tests (push) Successful in 8m5s

This commit is contained in:
2026-03-06 16:58:50 -06:00
parent 48b6ca6bd7
commit 87d830f952
94 changed files with 3694 additions and 734 deletions

View File

@@ -6,9 +6,6 @@ authors.workspace = true
license.workspace = true
repository.workspace = true
[features]
integration-tests = []
[lib]
name = "attune_api"
path = "src/lib.rs"

View File

@@ -2,6 +2,7 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use utoipa::{IntoParams, ToSchema};
use validator::Validate;
@@ -61,9 +62,9 @@ pub struct KeyResponse {
#[schema(example = true)]
pub encrypted: bool,
/// The secret value (decrypted if encrypted)
#[schema(example = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")]
pub value: String,
/// The secret value (decrypted if encrypted). Can be a string, object, array, number, or boolean.
#[schema(value_type = Value, example = json!("ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"))]
pub value: JsonValue,
/// Creation timestamp
#[schema(example = "2024-01-13T10:30:00Z")]
@@ -194,21 +195,16 @@ pub struct CreateKeyRequest {
#[schema(example = "GitHub API Token")]
pub name: String,
/// The secret value to store
#[validate(length(min = 1, max = 10000))]
#[schema(example = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")]
pub value: String,
/// The secret value to store. Can be a string, object, array, number, or boolean.
#[schema(value_type = Value, example = json!("ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"))]
pub value: JsonValue,
/// Whether to encrypt the value (recommended: true)
#[serde(default = "default_encrypted")]
#[schema(example = true)]
/// Whether to encrypt the value at rest (default: false; use --encrypt / -e from CLI)
#[serde(default)]
#[schema(example = false)]
pub encrypted: bool,
}
fn default_encrypted() -> bool {
true
}
/// Request to update an existing key/secret
#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
pub struct UpdateKeyRequest {
@@ -217,10 +213,9 @@ pub struct UpdateKeyRequest {
#[schema(example = "GitHub API Token (Updated)")]
pub name: Option<String>,
/// Update the secret value
#[validate(length(min = 1, max = 10000))]
#[schema(example = "ghp_new_token_xxxxxxxxxxxxxxxxxxxxxxxx")]
pub value: Option<String>,
/// Update the secret value. Can be a string, object, array, number, or boolean.
#[schema(value_type = Option<Value>, example = json!("ghp_new_token_xxxxxxxxxxxxxxxxxxxxxxxx"))]
pub value: Option<JsonValue>,
/// Update encryption status (re-encrypts if changing from false to true)
#[schema(example = true)]

View File

@@ -115,6 +115,9 @@ async fn mq_reconnect_loop(state: Arc<AppState>, mq_url: String) {
#[tokio::main]
async fn main() -> Result<()> {
// Install HMAC-only JWT crypto provider (must be before any token operations)
attune_common::auth::install_crypto_provider();
// Initialize tracing subscriber
tracing_subscriber::fmt()
.with_target(false)

View File

@@ -102,8 +102,8 @@ pub async fn get_key(
ApiError::InternalServerError("Encryption key not configured on server".to_string())
})?;
let decrypted_value =
attune_common::crypto::decrypt(&key.value, encryption_key).map_err(|e| {
let decrypted_value = attune_common::crypto::decrypt_json(&key.value, encryption_key)
.map_err(|e| {
tracing::error!("Failed to decrypt key '{}': {}", key_ref, e);
ApiError::InternalServerError(format!("Failed to decrypt key: {}", e))
})?;
@@ -233,11 +233,11 @@ pub async fn create_key(
)
})?;
let encrypted_value = attune_common::crypto::encrypt(&request.value, encryption_key)
let encrypted_value = attune_common::crypto::encrypt_json(&request.value, encryption_key)
.map_err(|e| {
tracing::error!("Failed to encrypt key value: {}", e);
ApiError::InternalServerError(format!("Failed to encrypt value: {}", e))
})?;
tracing::error!("Failed to encrypt key value: {}", e);
ApiError::InternalServerError(format!("Failed to encrypt value: {}", e))
})?;
let key_hash = attune_common::crypto::hash_encryption_key(encryption_key);
@@ -270,10 +270,11 @@ pub async fn create_key(
// Return decrypted value in response
if key.encrypted {
let encryption_key = state.config.security.encryption_key.as_ref().unwrap();
key.value = attune_common::crypto::decrypt(&key.value, encryption_key).map_err(|e| {
tracing::error!("Failed to decrypt newly created key: {}", e);
ApiError::InternalServerError(format!("Failed to decrypt value: {}", e))
})?;
key.value =
attune_common::crypto::decrypt_json(&key.value, encryption_key).map_err(|e| {
tracing::error!("Failed to decrypt newly created key: {}", e);
ApiError::InternalServerError(format!("Failed to decrypt value: {}", e))
})?;
}
let response = ApiResponse::with_message(KeyResponse::from(key), "Key created successfully");
@@ -328,11 +329,11 @@ pub async fn update_key(
)
})?;
let encrypted_value = attune_common::crypto::encrypt(&new_value, encryption_key)
let encrypted_value = attune_common::crypto::encrypt_json(&new_value, encryption_key)
.map_err(|e| {
tracing::error!("Failed to encrypt key value: {}", e);
ApiError::InternalServerError(format!("Failed to encrypt value: {}", e))
})?;
tracing::error!("Failed to encrypt key value: {}", e);
ApiError::InternalServerError(format!("Failed to encrypt value: {}", e))
})?;
let key_hash = attune_common::crypto::hash_encryption_key(encryption_key);
@@ -366,7 +367,7 @@ pub async fn update_key(
ApiError::InternalServerError("Encryption key not configured on server".to_string())
})?;
updated_key.value = attune_common::crypto::decrypt(&updated_key.value, encryption_key)
updated_key.value = attune_common::crypto::decrypt_json(&updated_key.value, encryption_key)
.map_err(|e| {
tracing::error!("Failed to decrypt updated key '{}': {}", key_ref, e);
ApiError::InternalServerError(format!("Failed to decrypt value: {}", e))

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Integration tests for health check and authentication endpoints
use axum::http::StatusCode;
@@ -8,6 +7,7 @@ use serde_json::json;
mod helpers;
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_register_debug() {
let ctx = TestContext::new()
.await
@@ -37,6 +37,7 @@ async fn test_register_debug() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_health_check() {
let ctx = TestContext::new()
.await
@@ -55,6 +56,7 @@ async fn test_health_check() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_health_detailed() {
let ctx = TestContext::new()
.await
@@ -75,6 +77,7 @@ async fn test_health_detailed() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_health_ready() {
let ctx = TestContext::new()
.await
@@ -91,6 +94,7 @@ async fn test_health_ready() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_health_live() {
let ctx = TestContext::new()
.await
@@ -107,6 +111,7 @@ async fn test_health_live() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_register_user() {
let ctx = TestContext::new()
.await
@@ -138,6 +143,7 @@ async fn test_register_user() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_register_duplicate_user() {
let ctx = TestContext::new()
.await
@@ -175,6 +181,7 @@ async fn test_register_duplicate_user() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_register_invalid_password() {
let ctx = TestContext::new()
.await
@@ -197,6 +204,7 @@ async fn test_register_invalid_password() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_login_success() {
let ctx = TestContext::new()
.await
@@ -239,6 +247,7 @@ async fn test_login_success() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_login_wrong_password() {
let ctx = TestContext::new()
.await
@@ -275,6 +284,7 @@ async fn test_login_wrong_password() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_login_nonexistent_user() {
let ctx = TestContext::new()
.await
@@ -296,6 +306,7 @@ async fn test_login_nonexistent_user() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_current_user() {
let ctx = TestContext::new()
.await
@@ -319,6 +330,7 @@ async fn test_get_current_user() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_current_user_unauthorized() {
let ctx = TestContext::new()
.await
@@ -333,6 +345,7 @@ async fn test_get_current_user_unauthorized() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_current_user_invalid_token() {
let ctx = TestContext::new()
.await
@@ -347,6 +360,7 @@ async fn test_get_current_user_invalid_token() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_refresh_token() {
let ctx = TestContext::new()
.await
@@ -397,6 +411,7 @@ async fn test_refresh_token() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_refresh_with_invalid_token() {
let ctx = TestContext::new()
.await

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Integration tests for pack registry system
//!
//! This module tests:
@@ -128,6 +127,7 @@ actions:
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_from_local_directory() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -167,6 +167,7 @@ async fn test_install_pack_from_local_directory() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_with_dependency_validation_success() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -217,6 +218,7 @@ async fn test_install_pack_with_dependency_validation_success() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_with_missing_dependency_fails() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -256,6 +258,7 @@ async fn test_install_pack_with_missing_dependency_fails() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_skip_deps_bypasses_validation() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -291,6 +294,7 @@ async fn test_install_pack_skip_deps_bypasses_validation() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_with_runtime_validation() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -324,6 +328,7 @@ async fn test_install_pack_with_runtime_validation() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_metadata_tracking() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -373,6 +378,7 @@ async fn test_install_pack_metadata_tracking() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_force_reinstall() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -425,6 +431,7 @@ async fn test_install_pack_force_reinstall() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_storage_path_created() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -475,6 +482,7 @@ async fn test_install_pack_storage_path_created() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_invalid_source() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -505,6 +513,7 @@ async fn test_install_pack_invalid_source() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_missing_pack_yaml() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -539,6 +548,7 @@ async fn test_install_pack_missing_pack_yaml() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_invalid_pack_yaml() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -567,6 +577,7 @@ async fn test_install_pack_invalid_pack_yaml() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_without_auth_fails() -> Result<()> {
let ctx = TestContext::new().await?; // No auth
@@ -592,6 +603,7 @@ async fn test_install_pack_without_auth_fails() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_multiple_pack_installations() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();
@@ -639,6 +651,7 @@ async fn test_multiple_pack_installations() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_install_pack_version_upgrade() -> Result<()> {
let ctx = TestContext::new().await?.with_auth().await?;
let token = ctx.token().unwrap();

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Integration tests for pack workflow sync and validation
mod helpers;
@@ -59,6 +58,7 @@ tasks:
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sync_pack_workflows_endpoint() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -95,6 +95,7 @@ async fn test_sync_pack_workflows_endpoint() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_validate_pack_workflows_endpoint() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -121,6 +122,7 @@ async fn test_validate_pack_workflows_endpoint() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sync_nonexistent_pack_returns_404() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -137,6 +139,7 @@ async fn test_sync_nonexistent_pack_returns_404() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_validate_nonexistent_pack_returns_404() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -153,6 +156,7 @@ async fn test_validate_nonexistent_pack_returns_404() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sync_workflows_requires_authentication() {
let ctx = TestContext::new().await.unwrap();
@@ -180,6 +184,7 @@ async fn test_sync_workflows_requires_authentication() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_validate_workflows_requires_authentication() {
let ctx = TestContext::new().await.unwrap();
@@ -207,6 +212,7 @@ async fn test_validate_workflows_requires_authentication() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_pack_creation_with_auto_sync() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -237,6 +243,7 @@ async fn test_pack_creation_with_auto_sync() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_pack_update_with_auto_resync() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Integration tests for SSE execution stream endpoint
//!
//! These tests verify that:
@@ -87,6 +86,7 @@ async fn create_test_execution(pool: &PgPool, action_id: i64) -> Result<Executio
/// Run with: cargo test test_sse_stream_receives_execution_updates -- --ignored --nocapture
/// After starting: cargo run -p attune-api -- -c config.test.yaml
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sse_stream_receives_execution_updates() -> Result<()> {
// Set up test context with auth
let ctx = TestContext::new().await?.with_auth().await?;
@@ -225,6 +225,7 @@ async fn test_sse_stream_receives_execution_updates() -> Result<()> {
/// Test that SSE stream correctly filters by execution_id
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sse_stream_filters_by_execution_id() -> Result<()> {
// Set up test context with auth
let ctx = TestContext::new().await?.with_auth().await?;
@@ -326,6 +327,7 @@ async fn test_sse_stream_filters_by_execution_id() -> Result<()> {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sse_stream_requires_authentication() -> Result<()> {
// Try to connect without token
let sse_url = "http://localhost:8080/api/v1/executions/stream";
@@ -371,6 +373,7 @@ async fn test_sse_stream_requires_authentication() -> Result<()> {
/// Test streaming all executions (no filter)
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_sse_stream_all_executions() -> Result<()> {
// Set up test context with auth
let ctx = TestContext::new().await?.with_auth().await?;
@@ -463,6 +466,7 @@ async fn test_sse_stream_all_executions() -> Result<()> {
/// Test that PostgreSQL NOTIFY triggers actually fire
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_postgresql_notify_trigger_fires() -> Result<()> {
let ctx = TestContext::new().await?;

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Integration tests for webhook API endpoints
use attune_api::{AppState, Server};
@@ -109,6 +108,7 @@ async fn get_auth_token(app: &axum::Router, username: &str, password: &str) -> S
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_enable_webhook() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -151,6 +151,7 @@ async fn test_enable_webhook() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_disable_webhook() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -201,6 +202,7 @@ async fn test_disable_webhook() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_regenerate_webhook_key() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -252,6 +254,7 @@ async fn test_regenerate_webhook_key() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_regenerate_webhook_key_not_enabled() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -288,6 +291,7 @@ async fn test_regenerate_webhook_key_not_enabled() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_receive_webhook() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -358,6 +362,7 @@ async fn test_receive_webhook() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_receive_webhook_invalid_key() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state));
@@ -387,6 +392,7 @@ async fn test_receive_webhook_invalid_key() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_receive_webhook_disabled() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -436,6 +442,7 @@ async fn test_receive_webhook_disabled() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_requires_auth_for_management() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -468,6 +475,7 @@ async fn test_webhook_requires_auth_for_management() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_receive_webhook_minimal_payload() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Comprehensive integration tests for webhook security features (Phase 3)
//!
//! Tests cover:
@@ -123,6 +122,7 @@ fn generate_hmac_signature(payload: &[u8], secret: &str, algorithm: &str) -> Str
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_hmac_sha256_valid() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -189,6 +189,7 @@ async fn test_webhook_hmac_sha256_valid() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_hmac_sha512_valid() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -245,6 +246,7 @@ async fn test_webhook_hmac_sha512_valid() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_hmac_invalid_signature() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -300,6 +302,7 @@ async fn test_webhook_hmac_invalid_signature() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_hmac_missing_signature() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -352,6 +355,7 @@ async fn test_webhook_hmac_missing_signature() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_hmac_wrong_secret() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -414,6 +418,7 @@ async fn test_webhook_hmac_wrong_secret() {
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_rate_limit_enforced() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -489,6 +494,7 @@ async fn test_webhook_rate_limit_enforced() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_rate_limit_disabled() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -535,6 +541,7 @@ async fn test_webhook_rate_limit_disabled() {
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_ip_whitelist_allowed() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -605,6 +612,7 @@ async fn test_webhook_ip_whitelist_allowed() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_ip_whitelist_blocked() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -661,6 +669,7 @@ async fn test_webhook_ip_whitelist_blocked() {
// ============================================================================
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_payload_size_limit_enforced() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));
@@ -711,6 +720,7 @@ async fn test_webhook_payload_size_limit_enforced() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_webhook_payload_size_within_limit() {
let state = setup_test_state().await;
let server = Server::new(std::sync::Arc::new(state.clone()));

View File

@@ -1,4 +1,3 @@
#![cfg(feature = "integration-tests")]
//! Integration tests for workflow API endpoints
use attune_common::repositories::{
@@ -20,6 +19,7 @@ fn unique_pack_name() -> String {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_workflow_success() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -65,6 +65,7 @@ async fn test_create_workflow_success() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_workflow_duplicate_ref() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -110,6 +111,7 @@ async fn test_create_workflow_duplicate_ref() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_workflow_pack_not_found() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -132,6 +134,7 @@ async fn test_create_workflow_pack_not_found() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_workflow_by_ref() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -170,6 +173,7 @@ async fn test_get_workflow_by_ref() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_get_workflow_not_found() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -182,6 +186,7 @@ async fn test_get_workflow_not_found() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_workflows() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -228,6 +233,7 @@ async fn test_list_workflows() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_workflows_by_pack() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -295,6 +301,7 @@ async fn test_list_workflows_by_pack() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_workflows_with_filters() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -362,6 +369,7 @@ async fn test_list_workflows_with_filters() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_workflow() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -410,6 +418,7 @@ async fn test_update_workflow() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_workflow_not_found() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -428,6 +437,7 @@ async fn test_update_workflow_not_found() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_workflow() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -469,6 +479,7 @@ async fn test_delete_workflow() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_workflow_not_found() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();
@@ -481,6 +492,7 @@ async fn test_delete_workflow_not_found() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_workflow_requires_auth() {
let ctx = TestContext::new().await.unwrap();
@@ -505,6 +517,7 @@ async fn test_create_workflow_requires_auth() {
}
#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_workflow_validation() {
let ctx = TestContext::new().await.unwrap().with_auth().await.unwrap();