mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-13 16:02:59 +00:00
* feat: stop offering deno for new scripts and ship it only on full images Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep deno in the integration test worker and narrow the missing-deno error Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep deno selectable on existing deno scripts when the saved order omits it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: drop the deno workloads from the benchmark suite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: run bun instead of deno in tests that are not about the deno runtime Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: drop the deno cases from the integration suite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: update the app policy hash for the bun inline script Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
309 lines
10 KiB
Rust
309 lines
10 KiB
Rust
use sqlx::{Pool, Postgres};
|
|
|
|
use windmill_test_utils::*;
|
|
|
|
/// Test that the workspace success handler cache works correctly with 60s TTL
|
|
#[cfg(feature = "deno_core")]
|
|
#[sqlx::test(fixtures("base"))]
|
|
async fn test_success_handler_cache(db: Pool<Postgres>) -> anyhow::Result<()> {
|
|
initialize_tracing().await;
|
|
|
|
// First, create a success handler script
|
|
let _server = ApiServer::start(db.clone()).await?;
|
|
|
|
// Set up a success handler in workspace_settings using new JSONB column
|
|
sqlx::query!(
|
|
r#"
|
|
UPDATE workspace_settings
|
|
SET success_handler = '{"path": "script/f/test/success_handler", "extra_args": {"key": "value"}}'::jsonb
|
|
WHERE workspace_id = 'test-workspace'
|
|
"#
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
// Verify the success handler was set
|
|
let result = sqlx::query_scalar!(
|
|
r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
|
)
|
|
.fetch_one(&db)
|
|
.await?;
|
|
|
|
assert_eq!(result, Some("script/f/test/success_handler".to_string()));
|
|
|
|
// Verify extra args were set
|
|
let extra_args = sqlx::query_scalar!(
|
|
r#"SELECT success_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
|
)
|
|
.fetch_one(&db)
|
|
.await?;
|
|
|
|
assert!(extra_args.is_some());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test that success handler can be set and removed via database operations
|
|
#[cfg(feature = "deno_core")]
|
|
#[sqlx::test(fixtures("base"))]
|
|
async fn test_success_handler_settings(db: Pool<Postgres>) -> anyhow::Result<()> {
|
|
initialize_tracing().await;
|
|
|
|
let _server = ApiServer::start(db.clone()).await?;
|
|
|
|
// Initially success_handler should be NULL
|
|
let initial = sqlx::query_scalar!(
|
|
r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
|
)
|
|
.fetch_one(&db)
|
|
.await?;
|
|
assert!(initial.is_none());
|
|
|
|
// Set success handler
|
|
sqlx::query!(
|
|
r#"
|
|
UPDATE workspace_settings
|
|
SET success_handler = '{"path": "flow/f/test/success_flow"}'::jsonb
|
|
WHERE workspace_id = 'test-workspace'
|
|
"#
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
let after_set = sqlx::query_scalar!(
|
|
r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
|
)
|
|
.fetch_one(&db)
|
|
.await?;
|
|
assert_eq!(after_set, Some("flow/f/test/success_flow".to_string()));
|
|
|
|
// Remove success handler
|
|
sqlx::query!(
|
|
r#"
|
|
UPDATE workspace_settings
|
|
SET success_handler = NULL
|
|
WHERE workspace_id = 'test-workspace'
|
|
"#
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
let after_remove = sqlx::query_scalar!(
|
|
r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"#
|
|
)
|
|
.fetch_one(&db)
|
|
.await?;
|
|
assert!(after_remove.is_none());
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Test that a successful job completion triggers the success handler
|
|
#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))]
|
|
#[sqlx::test(fixtures("base"))]
|
|
async fn test_success_handler_triggered_on_success(db: Pool<Postgres>) -> anyhow::Result<()> {
|
|
use serde_json::json;
|
|
use windmill_common::jobs::JobPayload;
|
|
use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings};
|
|
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
|
|
|
initialize_tracing().await;
|
|
|
|
let server = ApiServer::start(db.clone()).await?;
|
|
|
|
// Create a simple success handler script
|
|
// Note: lock must be non-null for script to be considered "deployed"
|
|
let success_handler_code = r#"
|
|
export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, result: any) {
|
|
console.log("Success handler called for job:", job_id);
|
|
return { handled: true, original_path: path };
|
|
}
|
|
"#;
|
|
|
|
sqlx::query!(
|
|
r#"
|
|
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
|
VALUES ('test-workspace', 1234567890, 'f/test/success_handler', $1, 'bun', 'script', 'test-user', '{}', 'Success handler script', 'Handles successful job completions', E'{}\n//bun.lock\n<empty>')
|
|
"#,
|
|
success_handler_code
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
// Create a simple test script that we'll run (needs to be JobKind::Script, not Preview)
|
|
// Note: lock must be non-null for script to be considered "deployed"
|
|
let test_script_code = "export function main() { return 'success'; }";
|
|
let test_script_hash: i64 = 9876543210;
|
|
|
|
sqlx::query!(
|
|
r#"
|
|
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
|
VALUES ('test-workspace', $1, 'f/test/simple_script', $2, 'bun', 'script', 'test-user', '{}', 'Simple test script', 'A simple test script', E'{}\n//bun.lock\n<empty>')
|
|
"#,
|
|
test_script_hash,
|
|
test_script_code
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
// Set up the success handler in workspace_settings
|
|
sqlx::query!(
|
|
r#"
|
|
UPDATE workspace_settings
|
|
SET success_handler = '{"path": "script/f/test/success_handler"}'::jsonb
|
|
WHERE workspace_id = 'test-workspace'
|
|
"#
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
// Create the success_handler group
|
|
sqlx::query!(
|
|
r#"
|
|
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
|
VALUES ('test-workspace', 'success_handler', 'The group the success handler acts on behalf of', '{"u/test-user": true}')
|
|
ON CONFLICT DO NOTHING
|
|
"#
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
// Run a script using ScriptHash (produces JobKind::Script, not Preview)
|
|
let completed_job = RunJob::from(JobPayload::ScriptHash {
|
|
hash: ScriptHash(test_script_hash),
|
|
path: "f/test/simple_script".to_string(),
|
|
cache_ttl: None,
|
|
cache_ignore_s3_path: None,
|
|
dedicated_worker: None,
|
|
language: ScriptLang::Bun,
|
|
priority: None,
|
|
apply_preprocessor: false,
|
|
concurrency_settings: ConcurrencySettings::default(),
|
|
debouncing_settings: DebouncingSettings::default(),
|
|
labels: None,
|
|
job_token_scopes: None,
|
|
})
|
|
.run_until_complete(&db, false, server.addr.port())
|
|
.await;
|
|
|
|
let result = completed_job.json_result().unwrap();
|
|
assert_eq!(result, json!("success"));
|
|
|
|
let main_job_id = completed_job.id;
|
|
|
|
// Wait a short time for the success handler job to be created
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
|
|
|
// Verify the success handler job was created (query by email since trigger is not set)
|
|
let success_handler_job = sqlx::query!(
|
|
r#"
|
|
SELECT
|
|
id,
|
|
runnable_path,
|
|
permissioned_as_email,
|
|
parent_job
|
|
FROM v2_job
|
|
WHERE workspace_id = 'test-workspace'
|
|
AND permissioned_as_email = 'success_handler@windmill.dev'
|
|
ORDER BY created_at DESC
|
|
LIMIT 1
|
|
"#
|
|
)
|
|
.fetch_optional(&db)
|
|
.await?;
|
|
|
|
assert!(
|
|
success_handler_job.is_some(),
|
|
"Success handler job should have been created"
|
|
);
|
|
|
|
let handler_job = success_handler_job.unwrap();
|
|
|
|
// Verify the success handler job has correct parameters
|
|
assert_eq!(
|
|
handler_job.runnable_path.as_deref(),
|
|
Some("f/test/success_handler"),
|
|
"Success handler should run the configured script"
|
|
);
|
|
assert_eq!(
|
|
handler_job.permissioned_as_email.as_str(),
|
|
"success_handler@windmill.dev",
|
|
"Success handler should run as success_handler user"
|
|
);
|
|
assert_eq!(
|
|
handler_job.parent_job,
|
|
Some(main_job_id),
|
|
"Success handler should have main job as parent"
|
|
);
|
|
// Note: root_job may be None when it equals parent_job (optimization in push function)
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// A hub script run at the top level (a schedule, a trigger, the API) is a `script_hub` job,
|
|
/// and its success must reach the workspace success handler like a workspace script's.
|
|
#[cfg(all(feature = "enterprise", feature = "private"))]
|
|
#[sqlx::test(fixtures("base"))]
|
|
async fn test_success_handler_triggered_on_hub_script_success(
|
|
db: Pool<Postgres>,
|
|
) -> anyhow::Result<()> {
|
|
use windmill_common::jobs::JobPayload;
|
|
|
|
initialize_tracing().await;
|
|
|
|
let server = ApiServer::start(db.clone()).await?;
|
|
|
|
let version = "990000003";
|
|
let hub_dir = &*windmill_common::worker::HUB_CACHE_DIR;
|
|
tokio::fs::create_dir_all(hub_dir).await?;
|
|
tokio::fs::write(
|
|
format!("{hub_dir}/{version}"),
|
|
r#"{"content":"echo ok","lockfile":null,"language":"bash","schema":{},"summary":null}"#,
|
|
)
|
|
.await?;
|
|
|
|
// The workspace handler settings are cached per workspace id across the tests of this
|
|
// binary, so this reuses the handler path every other test here configures.
|
|
sqlx::query(
|
|
"INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)
|
|
VALUES ('test-workspace', 1234567891, 'f/test/success_handler', 'echo handled', 'bash', 'script', 'test-user', '{}', '', '', '')",
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
sqlx::query(
|
|
r#"UPDATE workspace_settings SET success_handler = '{"path": "script/f/test/success_handler"}'::jsonb
|
|
WHERE workspace_id = 'test-workspace'"#,
|
|
)
|
|
.execute(&db)
|
|
.await?;
|
|
|
|
let completed_job = RunJob::from(JobPayload::ScriptHub {
|
|
path: format!("hub/{version}/test/ok"),
|
|
apply_preprocessor: false,
|
|
})
|
|
.run_until_complete(&db, false, server.addr.port())
|
|
.await;
|
|
assert!(completed_job.success, "Hub script should have succeeded");
|
|
|
|
let mut handler_parent = None;
|
|
for _ in 0..50 {
|
|
handler_parent = sqlx::query_scalar::<_, Option<uuid::Uuid>>(
|
|
"SELECT parent_job FROM v2_job
|
|
WHERE workspace_id = 'test-workspace' AND permissioned_as_email = 'success_handler@windmill.dev'",
|
|
)
|
|
.fetch_optional(&db)
|
|
.await?;
|
|
if handler_parent.is_some() {
|
|
break;
|
|
}
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
}
|
|
assert_eq!(
|
|
handler_parent,
|
|
Some(Some(completed_job.id)),
|
|
"Workspace success handler should handle the successful hub script"
|
|
);
|
|
|
|
Ok(())
|
|
}
|