[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
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:
@@ -6,9 +6,6 @@ authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
integration-tests = []
|
||||
|
||||
[lib]
|
||||
name = "attune_api"
|
||||
path = "src/lib.rs"
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user