refactor: move integration tests to subcrates to reduce recompilation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
44
backend/Cargo.lock
generated
44
backend/Cargo.lock
generated
@@ -15778,6 +15778,7 @@ dependencies = [
|
||||
"windmill-indexer",
|
||||
"windmill-queue",
|
||||
"windmill-runtime-nativets",
|
||||
"windmill-test-utils",
|
||||
"windmill-worker",
|
||||
"windows-service",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -16138,6 +16139,26 @@ dependencies = [
|
||||
"windmill-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.631.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
"rand 0.9.0",
|
||||
"rdkafka",
|
||||
"reqwest 0.13.1",
|
||||
"rumqttc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"uuid",
|
||||
"windmill-api-client",
|
||||
"windmill-common",
|
||||
"windmill-test-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.631.1"
|
||||
@@ -16969,6 +16990,29 @@ dependencies = [
|
||||
"windmill-oauth",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.631.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
"futures",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"windmill-api",
|
||||
"windmill-api-agent-workers",
|
||||
"windmill-api-client",
|
||||
"windmill-common",
|
||||
"windmill-queue",
|
||||
"windmill-runtime-nativets",
|
||||
"windmill-worker",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.631.1"
|
||||
|
||||
@@ -67,7 +67,9 @@ members = [
|
||||
"./parsers/windmill-parser-py",
|
||||
"./parsers/windmill-parser-py-imports",
|
||||
"./parsers/windmill-sql-datatype-parser-wasm",
|
||||
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu"
|
||||
"./parsers/windmill-parser-yaml", "windmill-macros", "parsers/windmill-parser-nu",
|
||||
"./windmill-test-utils",
|
||||
"./windmill-api-integration-tests",
|
||||
]
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
@@ -240,6 +242,7 @@ serde_json.workspace = true
|
||||
reqwest.workspace = true
|
||||
windmill-queue = { workspace = true, features = ["failpoints"] }
|
||||
windmill-dep-map.workspace = true
|
||||
windmill-test-utils.workspace = true
|
||||
axum.workspace = true
|
||||
serde.workspace = true
|
||||
windmill-api-client.workspace = true
|
||||
@@ -317,6 +320,7 @@ windmill-parser-php = { path = "./parsers/windmill-parser-php" }
|
||||
windmill-jseval = { path = "./windmill-jseval" }
|
||||
windmill-runtime-nativets = { path = "./windmill-runtime-nativets" }
|
||||
windmill-api-client = { path = "./windmill-api-client" }
|
||||
windmill-test-utils = { path = "./windmill-test-utils" }
|
||||
|
||||
reqwest-retry = "^0"
|
||||
reqwest-middleware = { version = "^0", features = ["json"] }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#![cfg(all(feature = "private", feature = "agent_worker_server"))]
|
||||
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod common;
|
||||
use crate::common::*;
|
||||
use windmill_test_utils::*;
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx::Pool;
|
||||
use windmill_common::jobs::{JobPayload, RawCode};
|
||||
@@ -860,7 +859,7 @@ export function main() {
|
||||
// ============================================================================
|
||||
|
||||
mod dedicated_worker_protocol {
|
||||
use crate::common::{parse_dedicated_worker_line, DedicatedWorkerResult};
|
||||
use windmill_test_utils::{parse_dedicated_worker_line, DedicatedWorkerResult};
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::process::{Command, Stdio};
|
||||
use windmill_worker::{
|
||||
|
||||
@@ -2,8 +2,7 @@ use sqlx::{Pool, Postgres};
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
use windmill_api_client::types::NewScript;
|
||||
mod common;
|
||||
use common::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer};
|
||||
use windmill_test_utils::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer};
|
||||
|
||||
mod dependency_map {
|
||||
use super::*;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
/// Test that workspace error handler can be set and removed via database operations
|
||||
#[cfg(feature = "deno_core")]
|
||||
|
||||
@@ -25,8 +25,7 @@ use windmill_common::{
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
/// Helper to create a FlowModule with default fields
|
||||
fn flow_module(id: &str, value: FlowModuleValue) -> FlowModule {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
mod common;
|
||||
|
||||
mod job_payload {
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
@@ -8,7 +6,7 @@ mod job_payload {
|
||||
use windmill_common::scripts::{ScriptHash, ScriptLang};
|
||||
use windmill_common::flow_status::RestartedFrom;
|
||||
|
||||
use crate::common::*;
|
||||
use windmill_test_utils::*;
|
||||
use windmill_common::min_version::{
|
||||
MIN_VERSION, MIN_VERSION_IS_AT_LEAST_1_427, MIN_VERSION_IS_AT_LEAST_1_432,
|
||||
MIN_VERSION_IS_AT_LEAST_1_440,
|
||||
|
||||
@@ -7,8 +7,7 @@ use windmill_common::{
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ListJobsResponse {
|
||||
|
||||
@@ -14,10 +14,7 @@
|
||||
*/
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
mod common;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use futures::StreamExt;
|
||||
|
||||
@@ -11,10 +11,7 @@
|
||||
*/
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
mod common;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use std::time::Instant;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod common;
|
||||
use crate::common::*;
|
||||
use windmill_test_utils::*;
|
||||
use sqlx::postgres::Postgres;
|
||||
use sqlx::Pool;
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
mod common;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
mod retry {
|
||||
use crate::common::*;
|
||||
use windmill_test_utils::*;
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
/// Test that the workspace success handler cache works correctly with 60s TTL
|
||||
#[cfg(feature = "deno_core")]
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
mod common;
|
||||
|
||||
mod suspend_resume {
|
||||
#[cfg(feature = "deno_core")]
|
||||
use serde_json::json;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use crate::common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
#[cfg(feature = "deno_core")]
|
||||
use futures::{Stream, StreamExt};
|
||||
|
||||
@@ -25,8 +25,7 @@ use windmill_common::{
|
||||
jobs::{JobPayload, RawCode},
|
||||
scripts::ScriptLang,
|
||||
};
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
use futures::StreamExt;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
mod common;
|
||||
mod workspace_dependencies {
|
||||
|
||||
use crate::common::in_test_worker;
|
||||
use crate::common::init_client;
|
||||
use crate::common::listen_for_completed_jobs;
|
||||
use windmill_test_utils::in_test_worker;
|
||||
use windmill_test_utils::init_client;
|
||||
use windmill_test_utils::listen_for_completed_jobs;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tokio_stream::StreamExt;
|
||||
use windmill_common::scripts::ScriptLang;
|
||||
|
||||
30
backend/windmill-api-integration-tests/Cargo.toml
Normal file
30
backend/windmill-api-integration-tests/Cargo.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "windmill-api-integration-tests"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_api_integration_tests"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = ["windmill-test-utils/private"]
|
||||
enterprise = ["windmill-test-utils/enterprise"]
|
||||
|
||||
[dependencies]
|
||||
windmill-test-utils.workspace = true
|
||||
windmill-api-client.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
sqlx.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde.workspace = true
|
||||
reqwest.workspace = true
|
||||
tokio.workspace = true
|
||||
anyhow.workspace = true
|
||||
uuid.workspace = true
|
||||
rand.workspace = true
|
||||
rumqttc.workspace = true
|
||||
rdkafka.workspace = true
|
||||
async-nats.workspace = true
|
||||
2
backend/windmill-api-integration-tests/src/lib.rs
Normal file
2
backend/windmill-api-integration-tests/src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
// This crate exists only to host integration tests.
|
||||
// See tests/ directory for the actual test files.
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn app_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/apps/{endpoint}/{path}")
|
||||
@@ -40,7 +39,7 @@ fn new_app(path: &str, summary: &str) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_app_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
@@ -12,7 +11,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_draft_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
146
backend/windmill-api-integration-tests/tests/fixtures/base.sql
vendored
Normal file
146
backend/windmill-api-integration-tests/tests/fixtures/base.sql
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
-- used for backend automated testing
|
||||
-- https://docs.rs/sqlx/latest/sqlx/attr.test.html
|
||||
|
||||
INSERT INTO workspace
|
||||
(id, name, owner)
|
||||
VALUES ('test-workspace', 'test-workspace', 'test-user');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin');
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key) VALUES
|
||||
('test-workspace', 'cloud', 'test-key');
|
||||
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id) VALUES
|
||||
('test-workspace');
|
||||
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES
|
||||
('test-workspace', 'all', 'All users', '{}');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username)
|
||||
VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test3@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 3');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test3@windmill.dev', 'test-user-3', false, 'User');
|
||||
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true);
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false);
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_3', 'test3@windmill.dev', 'test token 3', false);
|
||||
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin;
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user;
|
||||
|
||||
CREATE FUNCTION "notify_insert_on_completed_job" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('completed', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TRIGGER "notify_insert_on_completed_job"
|
||||
AFTER INSERT ON "v2_job_completed"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_insert_on_completed_job" ();
|
||||
|
||||
|
||||
CREATE FUNCTION "notify_queue" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('queued', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TRIGGER "notify_queue_after_insert"
|
||||
AFTER INSERT ON "v2_job_queue"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
CREATE TRIGGER "notify_queue_after_flow_status_update"
|
||||
AFTER UPDATE ON "v2_job_status"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status)
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
-- Apply phase 4:
|
||||
DROP FUNCTION IF EXISTS v2_job_after_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE;
|
||||
|
||||
DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE;
|
||||
|
||||
ALTER TABLE v2_job_queue
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __last_ping CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_status CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __same_worker CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __pre_run_error CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __mem_peak CASCADE,
|
||||
DROP COLUMN IF EXISTS __root_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __leaf_jobs CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrent_limit CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE,
|
||||
DROP COLUMN IF EXISTS __timeout CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_step_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __cache_ttl CASCADE;
|
||||
|
||||
LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE;
|
||||
ALTER TABLE v2_job_completed
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_at CASCADE,
|
||||
DROP COLUMN IF EXISTS __success CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_skipped CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __tag CASCADE,
|
||||
DROP COLUMN IF EXISTS __priority CASCADE;
|
||||
238
backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql
vendored
Normal file
238
backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
-- Permissions test fixture
|
||||
-- This sets up a comprehensive permission testing scenario
|
||||
|
||||
-- ============================================
|
||||
-- USERS
|
||||
-- ============================================
|
||||
|
||||
-- Admin user (may already exist from base.sql, use ON CONFLICT)
|
||||
INSERT INTO usr (workspace_id, email, username, is_admin, role)
|
||||
VALUES ('test-workspace', 'admin@windmill.dev', 'admin', true, 'Admin')
|
||||
ON CONFLICT (workspace_id, username) DO NOTHING;
|
||||
|
||||
-- Regular users (non-admin)
|
||||
INSERT INTO usr (workspace_id, email, username, is_admin, role)
|
||||
VALUES
|
||||
('test-workspace', 'alice@windmill.dev', 'alice', false, 'Developer'),
|
||||
('test-workspace', 'bob@windmill.dev', 'bob', false, 'Developer'),
|
||||
('test-workspace', 'charlie@windmill.dev', 'charlie', false, 'Developer');
|
||||
|
||||
-- Operator user (can execute but cannot create/update scripts, flows, apps)
|
||||
INSERT INTO usr (workspace_id, email, username, is_admin, operator, role)
|
||||
VALUES
|
||||
('test-workspace', 'operator@windmill.dev', 'operator', false, true, 'Operator');
|
||||
|
||||
-- Add users to password table (use ON CONFLICT since admin may exist from base.sql)
|
||||
INSERT INTO password (email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES
|
||||
('admin@windmill.dev', 'dummy_hash', 'password', false, true, 'Admin User'),
|
||||
('alice@windmill.dev', 'dummy_hash', 'password', false, true, 'Alice'),
|
||||
('bob@windmill.dev', 'dummy_hash', 'password', false, true, 'Bob'),
|
||||
('charlie@windmill.dev', 'dummy_hash', 'password', false, true, 'Charlie'),
|
||||
('operator@windmill.dev', 'dummy_hash', 'password', false, true, 'Operator')
|
||||
ON CONFLICT (email) DO NOTHING;
|
||||
|
||||
-- ============================================
|
||||
-- TOKENS for authentication
|
||||
-- ============================================
|
||||
|
||||
-- Tokens associated with emails (workspace-scoped)
|
||||
-- The auth system will look up the user by email in the usr table
|
||||
-- Note: tokens must be at least 10 characters (TOKEN_PREFIX_LEN)
|
||||
INSERT INTO token (token, email, label, super_admin, owner, workspace_id)
|
||||
VALUES
|
||||
('ADMIN_TOKEN_TEST', 'admin@windmill.dev', 'Admin token', false, 'u/admin', 'test-workspace'),
|
||||
('ALICE_TOKEN_TEST', 'alice@windmill.dev', 'Alice token', false, 'u/alice', 'test-workspace'),
|
||||
('BOB_TOKEN_TEST12', 'bob@windmill.dev', 'Bob token', false, 'u/bob', 'test-workspace'),
|
||||
('CHARLIE_TOKEN_01', 'charlie@windmill.dev', 'Charlie token', false, 'u/charlie', 'test-workspace'),
|
||||
('OPERATOR_TOKEN_1', 'operator@windmill.dev', 'Operator token', false, 'u/operator', 'test-workspace');
|
||||
|
||||
-- ============================================
|
||||
-- GROUPS
|
||||
-- ============================================
|
||||
|
||||
-- 'developers' group - Charlie is a member
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'developers', 'Developer group', '{}');
|
||||
|
||||
-- 'editors' group - Bob is a member (has write access to some folders)
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES ('test-workspace', 'editors', 'Editor group', '{}');
|
||||
|
||||
-- Group memberships
|
||||
INSERT INTO usr_to_group (workspace_id, group_, usr)
|
||||
VALUES
|
||||
('test-workspace', 'developers', 'charlie'),
|
||||
('test-workspace', 'editors', 'bob');
|
||||
|
||||
-- ============================================
|
||||
-- FOLDERS
|
||||
-- ============================================
|
||||
|
||||
-- 'shared' folder:
|
||||
-- - Alice has read-only access (extra_perms: u/alice -> false)
|
||||
-- - Bob has write access (extra_perms: u/bob -> true)
|
||||
INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'shared', 'Shared Folder', '{"u/admin"}',
|
||||
'{"u/alice": false, "u/bob": true}', 'admin');
|
||||
|
||||
-- 'team' folder:
|
||||
-- - 'developers' group has read-only access
|
||||
INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'team', 'Team Folder', '{"u/admin"}',
|
||||
'{"g/developers": false}', 'admin');
|
||||
|
||||
-- 'editable' folder:
|
||||
-- - 'editors' group has write access
|
||||
INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'editable', 'Editable Folder', '{"u/admin"}',
|
||||
'{"g/editors": true}', 'admin');
|
||||
|
||||
-- 'admin_only' folder:
|
||||
-- - Only admin has access (but we'll add item-level perms for specific items)
|
||||
INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'admin_only', 'Admin Only Folder', '{"u/admin"}',
|
||||
'{}', 'admin');
|
||||
|
||||
-- 'alice_owned' folder:
|
||||
-- - Alice is an owner (owners must also be in extra_perms for get_folders_for_user to find them)
|
||||
INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'alice_owned', 'Alice Owned Folder', '{"u/alice"}',
|
||||
'{"u/alice": true}', 'alice');
|
||||
|
||||
-- ============================================
|
||||
-- SCRIPTS
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal script
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1001, 'u/alice/my_script',
|
||||
'export function main() { return "alice script"; }',
|
||||
'deno', 'script', 'alice', '{}', 'Alice script', '', '', '{}');
|
||||
|
||||
-- Bob's personal script
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1002, 'u/bob/my_script',
|
||||
'export function main() { return "bob script"; }',
|
||||
'deno', 'script', 'bob', '{}', 'Bob script', '', '', '{}');
|
||||
|
||||
-- Script in shared folder (accessible by Alice read-only, Bob write)
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1003, 'f/shared/public_script',
|
||||
'export function main() { return "public"; }',
|
||||
'deno', 'script', 'admin', '{}', 'Public script', '', '', '{}');
|
||||
|
||||
-- Script in team folder (accessible by developers group)
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1004, 'f/team/team_script',
|
||||
'export function main() { return "team"; }',
|
||||
'deno', 'script', 'admin', '{}', 'Team script', '', '', '{}');
|
||||
|
||||
-- Script in admin_only folder but with explicit share to Alice
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1005, 'f/admin_only/shared_with_alice',
|
||||
'export function main() { return "shared"; }',
|
||||
'deno', 'script', 'admin', '{}', 'Shared with Alice', '', '',
|
||||
'{"u/alice": false}');
|
||||
|
||||
-- Script in alice_owned folder
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1006, 'f/alice_owned/owner_script',
|
||||
'export function main() { return "owner"; }',
|
||||
'deno', 'script', 'alice', '{}', 'Owner script', '', '', '{}');
|
||||
|
||||
-- Alice's script with extra_perms sharing to Bob (read-only)
|
||||
INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, extra_perms)
|
||||
VALUES ('test-workspace', 1007, 'u/alice/extra_shared_script',
|
||||
'export function main() { return "extra shared"; }',
|
||||
'deno', 'script', 'alice', '{}', 'Extra shared script', '', '',
|
||||
'{"u/bob": false}');
|
||||
|
||||
-- ============================================
|
||||
-- FLOWS (with flow_versions)
|
||||
-- ============================================
|
||||
|
||||
-- Create flows first (without versions)
|
||||
INSERT INTO flow (workspace_id, path, summary, description, value, edited_by, edited_at, schema, extra_perms)
|
||||
VALUES
|
||||
('test-workspace', 'u/alice/my_flow', 'Alice flow', '', '{"modules": []}', 'alice', NOW(), '{}', '{}'),
|
||||
('test-workspace', 'u/bob/my_flow', 'Bob flow', '', '{"modules": []}', 'bob', NOW(), '{}', '{}'),
|
||||
('test-workspace', 'f/shared/shared_flow', 'Shared flow', '', '{"modules": []}', 'admin', NOW(), '{}', '{}');
|
||||
|
||||
-- Create flow versions
|
||||
INSERT INTO flow_version (id, workspace_id, path, value, schema, created_by, created_at)
|
||||
VALUES
|
||||
(1001, 'test-workspace', 'u/alice/my_flow', '{"modules": []}', '{}', 'alice', NOW()),
|
||||
(1002, 'test-workspace', 'u/bob/my_flow', '{"modules": []}', '{}', 'bob', NOW()),
|
||||
(1003, 'test-workspace', 'f/shared/shared_flow', '{"modules": []}', '{}', 'admin', NOW());
|
||||
|
||||
-- Update flows with version references
|
||||
UPDATE flow SET versions = ARRAY[1001::bigint] WHERE path = 'u/alice/my_flow' AND workspace_id = 'test-workspace';
|
||||
UPDATE flow SET versions = ARRAY[1002::bigint] WHERE path = 'u/bob/my_flow' AND workspace_id = 'test-workspace';
|
||||
UPDATE flow SET versions = ARRAY[1003::bigint] WHERE path = 'f/shared/shared_flow' AND workspace_id = 'test-workspace';
|
||||
|
||||
-- ============================================
|
||||
-- RESOURCES
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal resource
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/alice/my_resource', '{"key": "alice_value"}',
|
||||
'Alice resource', 'object', '{}', 'alice');
|
||||
|
||||
-- Bob's personal resource
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/bob/my_resource', '{"key": "bob_value"}',
|
||||
'Bob resource', 'object', '{}', 'bob');
|
||||
|
||||
-- ============================================
|
||||
-- VARIABLES
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal variable (non-secret for testing permissions, not encryption)
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/alice/my_variable', 'alice_value', false,
|
||||
'Alice variable', '{}');
|
||||
|
||||
-- Bob's personal variable (non-secret for testing permissions, not encryption)
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/bob/my_variable', 'bob_value', false,
|
||||
'Bob variable', '{}');
|
||||
|
||||
-- ============================================
|
||||
-- SCHEDULES
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal schedule
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms)
|
||||
VALUES ('test-workspace', 'u/alice/my_schedule', 'alice', NOW(), '0 * * * *', false,
|
||||
'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}');
|
||||
|
||||
-- Bob's personal schedule
|
||||
INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms)
|
||||
VALUES ('test-workspace', 'u/bob/my_schedule', 'bob', NOW(), '0 * * * *', false,
|
||||
'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}');
|
||||
|
||||
-- ============================================
|
||||
-- APPS (with app_versions)
|
||||
-- ============================================
|
||||
|
||||
-- Alice's personal app
|
||||
INSERT INTO app (id, workspace_id, path, summary, versions, policy, extra_perms)
|
||||
VALUES (2001, 'test-workspace', 'u/alice/my_app', 'Alice app', '{}',
|
||||
'{"on_behalf_of": "u/alice", "on_behalf_of_email": "alice@windmill.dev", "execution_mode": "viewer"}', '{}');
|
||||
|
||||
-- Shared folder app
|
||||
INSERT INTO app (id, workspace_id, path, summary, versions, policy, extra_perms)
|
||||
VALUES (2002, 'test-workspace', 'f/shared/shared_app', 'Shared app', '{}',
|
||||
'{"on_behalf_of": "u/admin", "on_behalf_of_email": "admin@windmill.dev", "execution_mode": "viewer"}', '{}');
|
||||
|
||||
-- Create app versions
|
||||
INSERT INTO app_version (id, app_id, value, created_by, created_at)
|
||||
VALUES
|
||||
(2001, 2001, '{"grid": []}', 'alice', NOW()),
|
||||
(2002, 2002, '{"grid": []}', 'admin', NOW());
|
||||
|
||||
-- Update apps with version references
|
||||
UPDATE app SET versions = ARRAY[2001::bigint] WHERE id = 2001;
|
||||
UPDATE app SET versions = ARRAY[2002::bigint] WHERE id = 2002;
|
||||
69
backend/windmill-api-integration-tests/tests/fixtures/resources_test.sql
vendored
Normal file
69
backend/windmill-api-integration-tests/tests/fixtures/resources_test.sql
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
-- Combined fixture for resource endpoint tests
|
||||
|
||||
-- === resource type test data ===
|
||||
|
||||
INSERT INTO resource_type (workspace_id, name, schema, description, created_by)
|
||||
VALUES ('test-workspace', 'test_db', '{"type": "object", "properties": {"host": {"type": "string"}}}',
|
||||
'Test DB type', 'test-user');
|
||||
|
||||
-- === get_value_interpolated test data ===
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/simple_resource', '{"host": "localhost", "port": 5432}',
|
||||
'Simple resource', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/test-user/db_password', 'hunter2', false, 'DB password', '{}');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/resource_with_var', '{"host": "localhost", "password": "$var:u/test-user/db_password"}',
|
||||
'Resource with var ref', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/db_credentials', '{"user": "admin", "password": "secret123"}',
|
||||
'DB credentials', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/resource_with_res', '{"host": "localhost", "credentials": "$res:u/test-user/db_credentials"}',
|
||||
'Resource with res ref', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/resource_mixed', '{"host": "localhost", "password": "$var:u/test-user/db_password", "credentials": "$res:u/test-user/db_credentials"}',
|
||||
'Resource with mixed refs', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/null_resource', null,
|
||||
'Null resource', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/test-user/api_key', 'sk-abc123', false, 'API key', '{}');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/inner_resource', '{"key": "$var:u/test-user/api_key"}',
|
||||
'Inner resource', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/chained_resource', '{"service": "myapi", "auth": "$res:u/test-user/inner_resource"}',
|
||||
'Chained resource', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/resource_with_array', '{"hosts": ["host1", "host2"], "port": 5432}',
|
||||
'Resource with array', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/scalar_var_resource', '"$var:u/test-user/db_password"',
|
||||
'Scalar var ref', 'string', '{}', 'test-user');
|
||||
|
||||
-- === mcp_tools test data ===
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/mcp_valid', '{"name": "test-mcp", "url": "http://127.0.0.1:19999/mcp"}',
|
||||
'Valid MCP resource (unreachable)', 'mcp_server', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/mcp_invalid_format', '{"host": "localhost", "port": 5432}',
|
||||
'Not an MCP resource', 'object', '{}', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/mcp_null', null,
|
||||
'Null MCP resource', 'mcp_server', '{}', 'test-user');
|
||||
10
backend/windmill-api-integration-tests/tests/fixtures/variables_test.sql
vendored
Normal file
10
backend/windmill-api-integration-tests/tests/fixtures/variables_test.sql
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Fixture for variable endpoint tests
|
||||
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/test-user/plain_var', 'hello world', false, 'A plain variable', '{}');
|
||||
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/test-user/secret_var', 'supersecret', true, 'A secret variable', '{}');
|
||||
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/test-user/another_var', 'foobar', false, 'Another variable', '{}');
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn flow_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/flows/{endpoint}/{path}")
|
||||
@@ -40,7 +39,7 @@ fn new_flow(path: &str, summary: &str) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_flow_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn folder_url(port: u16, endpoint: &str, name: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/folders/{endpoint}/{name}")
|
||||
@@ -16,7 +15,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_folder_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn group_url(port: u16, endpoint: &str, name: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/groups/{endpoint}/{name}")
|
||||
@@ -16,7 +15,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_group_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
/// Helper to create a client authenticated as a specific user
|
||||
async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client {
|
||||
@@ -44,7 +43,7 @@ async fn can_write(client: &reqwest::Client, url: &str, body: serde_json::Value)
|
||||
/// `cargo test --features deno_core permissions -- --ignored`
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
async fn test_permissions_exhaustive(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -477,7 +476,7 @@ async fn test_permissions_exhaustive(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// Additional test for verifying group permission inheritance
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
async fn test_group_permission_inheritance(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -547,7 +546,7 @@ async fn test_group_permission_inheritance(db: Pool<Postgres>) -> anyhow::Result
|
||||
/// Test that permissions work correctly for all item types
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
async fn test_all_item_types_permissions(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -588,7 +587,7 @@ async fn test_all_item_types_permissions(db: Pool<Postgres>) -> anyhow::Result<(
|
||||
/// Operators have limited permissions - they can execute but cannot manage resources
|
||||
#[ignore]
|
||||
#[cfg(feature = "deno_core")]
|
||||
#[sqlx::test(fixtures("base", "permissions_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "permissions_test"))]
|
||||
async fn test_operator_cannot_create_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -7,8 +7,7 @@ use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::workspaces::invalidate_protection_rules_cache;
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
@@ -51,7 +50,7 @@ fn new_flow(path: &str, summary: &str) -> serde_json::Value {
|
||||
|
||||
/// Comprehensive test for protection rules functionality.
|
||||
/// Tests all essential cases in a single test to avoid cache interference.
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_protection_rules(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
invalidate_protection_rules_cache("test-workspace");
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn resource_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/resources/{endpoint}/{path}")
|
||||
@@ -23,7 +22,7 @@ async fn authed_get(port: u16, endpoint: &str, path: &str) -> reqwest::Response
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "resources_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "resources_test"))]
|
||||
async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -400,7 +399,7 @@ async fn test_resource_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "mcp")]
|
||||
#[sqlx::test(fixtures("base", "resources_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "resources_test"))]
|
||||
async fn test_mcp_tools(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn schedule_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/schedules/{endpoint}/{path}")
|
||||
@@ -23,7 +22,7 @@ async fn authed_get(port: u16, endpoint: &str, path: &str) -> reqwest::Response
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_schedule_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn script_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/scripts/{endpoint}/{path}")
|
||||
@@ -39,7 +38,7 @@ fn new_script(path: &str, summary: &str, content: &str) -> serde_json::Value {
|
||||
})
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_script_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -24,8 +24,7 @@ use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use std::time::Duration;
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
@@ -137,7 +136,7 @@ async fn insert_resource(
|
||||
/// -- --ignored --nocapture
|
||||
/// ```
|
||||
#[ignore = "requires running MQTT broker on localhost:1883"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_mqtt_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -230,7 +229,7 @@ async fn test_mqtt_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// -- --ignored --nocapture
|
||||
/// ```
|
||||
#[ignore = "requires running WebSocket echo server on localhost:8765"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_websocket_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -286,7 +285,7 @@ async fn test_websocket_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// -- --ignored --nocapture
|
||||
/// ```
|
||||
#[ignore = "requires PostgreSQL with wal_level=logical"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_postgres_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -387,7 +386,7 @@ async fn test_postgres_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// ```
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
#[ignore = "requires running Kafka broker on localhost:9092"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_kafka_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -478,7 +477,7 @@ async fn test_kafka_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// ```
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
#[ignore = "requires running NATS server on localhost:4222"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_nats_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -553,7 +552,7 @@ async fn test_nats_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// ```
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
#[ignore = "requires LocalStack SQS on localhost:4566"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_sqs_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -641,7 +640,7 @@ async fn test_sqs_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
/// ```
|
||||
#[cfg(all(feature = "enterprise", feature = "private"))]
|
||||
#[ignore = "requires GCP Pub/Sub emulator on localhost:8085"]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_gcp_e2e(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -12,14 +12,13 @@ use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
// ============================================================================
|
||||
// Capture Config Tests (direct DB)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_config_insert_and_query(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -56,7 +55,7 @@ async fn test_capture_config_insert_and_query(db: Pool<Postgres>) -> anyhow::Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_config_upsert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -104,7 +103,7 @@ async fn test_capture_config_upsert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_config_ping_updates_timestamp(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -159,7 +158,7 @@ async fn test_capture_config_ping_updates_timestamp(db: Pool<Postgres>) -> anyho
|
||||
// Capture Payload Tests (direct DB)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_insert_and_list(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
for i in 0..2 {
|
||||
sqlx::query!(
|
||||
@@ -192,7 +191,7 @@ async fn test_capture_insert_and_list(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let id = sqlx::query_scalar!(
|
||||
r#"
|
||||
@@ -227,7 +226,7 @@ async fn test_capture_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_filter_by_trigger_kind(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -304,7 +303,7 @@ struct CaptureResponse {
|
||||
main_args: serde_json::Value,
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_api_set_config_and_list(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -348,7 +347,7 @@ async fn test_capture_api_set_config_and_list(db: Pool<Postgres>) -> anyhow::Res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_api_list_captures(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -394,7 +393,7 @@ async fn test_capture_api_list_captures(db: Pool<Postgres>) -> anyhow::Result<()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_api_get_single(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -441,7 +440,7 @@ async fn test_capture_api_get_single(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_api_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -493,7 +492,7 @@ async fn test_capture_api_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_capture_api_pagination(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -555,7 +554,7 @@ async fn test_capture_api_pagination(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
// HTTP Trigger Tests (direct DB)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_http_trigger_insert_and_query(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -610,7 +609,7 @@ async fn test_http_trigger_insert_and_query(db: Pool<Postgres>) -> anyhow::Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_http_trigger_multiple_methods(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let methods = ["get", "post", "put", "delete", "patch"];
|
||||
|
||||
@@ -657,7 +656,7 @@ async fn test_http_trigger_multiple_methods(db: Pool<Postgres>) -> anyhow::Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_http_trigger_authentication_methods(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let auth_methods = ["none", "windmill", "api_key", "basic_http", "signature"];
|
||||
|
||||
@@ -708,7 +707,7 @@ async fn test_http_trigger_authentication_methods(db: Pool<Postgres>) -> anyhow:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_http_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -761,7 +760,7 @@ async fn test_http_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_http_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -813,7 +812,7 @@ async fn test_http_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_http_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let modes = ["enabled", "disabled", "suspended"];
|
||||
|
||||
@@ -870,7 +869,7 @@ async fn test_http_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<
|
||||
// Other Trigger Types Tests (DB schema validation)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_websocket_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -909,7 +908,7 @@ async fn test_websocket_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_kafka_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -952,7 +951,7 @@ async fn test_kafka_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_postgres_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -995,7 +994,7 @@ async fn test_postgres_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_nats_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1038,7 +1037,7 @@ async fn test_nats_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_sqs_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1089,7 +1088,7 @@ async fn test_sqs_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
// Cross-trigger tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_server_state_tracking(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1144,7 +1143,7 @@ async fn test_trigger_server_state_tracking(db: Pool<Postgres>) -> anyhow::Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let modes = ["enabled", "disabled", "enabled"];
|
||||
|
||||
@@ -1191,7 +1190,7 @@ async fn test_trigger_mode_filtering(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_multiple_capture_configs_per_path(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let trigger_kinds = ["webhook", "email", "kafka"];
|
||||
|
||||
@@ -1229,7 +1228,7 @@ async fn test_multiple_capture_configs_per_path(db: Pool<Postgres>) -> anyhow::R
|
||||
// Schedule Tests (DB-level)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_schedule_insert_and_query(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1277,7 +1276,7 @@ async fn test_schedule_insert_and_query(db: Pool<Postgres>) -> anyhow::Result<()
|
||||
// MQTT Trigger Tests (DB-level)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_mqtt_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -1321,7 +1320,7 @@ async fn test_mqtt_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_mqtt_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -1366,7 +1365,7 @@ async fn test_mqtt_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_mqtt_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -1414,7 +1413,7 @@ async fn test_mqtt_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
// GCP Trigger Tests (DB-level)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_gcp_trigger_insert_pull(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -1465,7 +1464,7 @@ async fn test_gcp_trigger_insert_pull(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_gcp_trigger_insert_push(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
r#"
|
||||
@@ -1515,7 +1514,7 @@ async fn test_gcp_trigger_insert_push(db: Pool<Postgres>) -> anyhow::Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_gcp_trigger_unique_constraint(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let insert_query = r#"
|
||||
INSERT INTO gcp_trigger (
|
||||
@@ -1569,7 +1568,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool<Postgres>) -> anyhow::Resul
|
||||
// Email Trigger Tests (DB-level)
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_email_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1612,7 +1611,7 @@ async fn test_email_trigger_insert(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_email_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1658,7 +1657,7 @@ async fn test_email_trigger_update(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_email_trigger_delete(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn user_url(port: u16, endpoint: &str, name: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/users/{endpoint}/{name}")
|
||||
@@ -16,7 +15,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_user_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn variable_url(port: u16, endpoint: &str, path: &str) -> String {
|
||||
format!("http://localhost:{port}/api/w/test-workspace/variables/{endpoint}/{path}")
|
||||
@@ -23,7 +22,7 @@ async fn authed_get(port: u16, endpoint: &str, path: &str) -> reqwest::Response
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "variables_test"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "variables_test"))]
|
||||
async fn test_variable_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
/// Comprehensive integration test for the compare_workspaces endpoint.
|
||||
///
|
||||
@@ -12,7 +11,7 @@ use common::*;
|
||||
/// 3. Making various changes in both workspaces (new items, modifications, conflicts, deletions, renames)
|
||||
/// 4. Populating the workspace_diff table to simulate Git sync tracking
|
||||
/// 5. Calling compare_workspaces and verifying all aspects of the comparison
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_compare_workspaces_comprehensive(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use serde_json::json;
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
use windmill_test_utils::*;
|
||||
|
||||
fn client() -> reqwest::Client {
|
||||
reqwest::Client::new()
|
||||
@@ -12,7 +11,7 @@ fn authed(builder: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
|
||||
builder.header("Authorization", "Bearer SECRET_TOKEN")
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_workspace_endpoints(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
146
backend/windmill-common/tests/fixtures/base.sql
vendored
Normal file
146
backend/windmill-common/tests/fixtures/base.sql
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
-- used for backend automated testing
|
||||
-- https://docs.rs/sqlx/latest/sqlx/attr.test.html
|
||||
|
||||
INSERT INTO workspace
|
||||
(id, name, owner)
|
||||
VALUES ('test-workspace', 'test-workspace', 'test-user');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin');
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key) VALUES
|
||||
('test-workspace', 'cloud', 'test-key');
|
||||
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id) VALUES
|
||||
('test-workspace');
|
||||
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES
|
||||
('test-workspace', 'all', 'All users', '{}');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username)
|
||||
VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test3@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 3');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test3@windmill.dev', 'test-user-3', false, 'User');
|
||||
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true);
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false);
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_3', 'test3@windmill.dev', 'test token 3', false);
|
||||
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin;
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user;
|
||||
|
||||
CREATE FUNCTION "notify_insert_on_completed_job" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('completed', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TRIGGER "notify_insert_on_completed_job"
|
||||
AFTER INSERT ON "v2_job_completed"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_insert_on_completed_job" ();
|
||||
|
||||
|
||||
CREATE FUNCTION "notify_queue" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('queued', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TRIGGER "notify_queue_after_insert"
|
||||
AFTER INSERT ON "v2_job_queue"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
CREATE TRIGGER "notify_queue_after_flow_status_update"
|
||||
AFTER UPDATE ON "v2_job_status"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status)
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
-- Apply phase 4:
|
||||
DROP FUNCTION IF EXISTS v2_job_after_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE;
|
||||
|
||||
DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE;
|
||||
|
||||
ALTER TABLE v2_job_queue
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __last_ping CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_status CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __same_worker CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __pre_run_error CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __mem_peak CASCADE,
|
||||
DROP COLUMN IF EXISTS __root_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __leaf_jobs CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrent_limit CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE,
|
||||
DROP COLUMN IF EXISTS __timeout CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_step_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __cache_ttl CASCADE;
|
||||
|
||||
LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE;
|
||||
ALTER TABLE v2_job_completed
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_at CASCADE,
|
||||
DROP COLUMN IF EXISTS __success CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_skipped CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __tag CASCADE,
|
||||
DROP COLUMN IF EXISTS __priority CASCADE;
|
||||
76
backend/windmill-common/tests/fixtures/instance_group_auto_add.sql
vendored
Normal file
76
backend/windmill-common/tests/fixtures/instance_group_auto_add.sql
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
-- Fixture for instance group auto-add tests
|
||||
-- Sets up test data for testing the interaction between instance groups and workspace auto-add
|
||||
|
||||
-- Create additional workspaces for testing
|
||||
INSERT INTO workspace (id, name, owner)
|
||||
VALUES
|
||||
('ws-with-auto-add', 'Workspace with Auto Add', 'admin'),
|
||||
('ws-no-auto-add', 'Workspace without Auto Add', 'admin'),
|
||||
('ws-multi-group', 'Workspace with Multiple Groups', 'admin')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id)
|
||||
VALUES
|
||||
('ws-with-auto-add'),
|
||||
('ws-no-auto-add'),
|
||||
('ws-multi-group')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key)
|
||||
VALUES
|
||||
('ws-with-auto-add', 'cloud', 'key-auto-add'),
|
||||
('ws-no-auto-add', 'cloud', 'key-no-auto'),
|
||||
('ws-multi-group', 'cloud', 'key-multi')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Create instance groups
|
||||
INSERT INTO instance_group (name, summary, id)
|
||||
VALUES
|
||||
('engineering', 'Engineering team', 'eng-uuid-001'),
|
||||
('sales', 'Sales team', 'sales-uuid-002'),
|
||||
('admins', 'Admin group', 'admin-uuid-003')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Create users in the password table (required for auto-add eligibility)
|
||||
INSERT INTO password (email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES
|
||||
('alice@example.com', 'hash1', 'password', false, true, 'Alice'),
|
||||
('bob@example.com', 'hash2', 'password', false, true, 'Bob'),
|
||||
('charlie@example.com', 'hash3', 'password', false, true, 'Charlie'),
|
||||
('dave@example.com', 'hash4', 'password', false, true, 'Dave'),
|
||||
('admin@windmill.dev', 'hash5', 'password', true, true, 'Admin')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Add users to instance groups
|
||||
INSERT INTO email_to_igroup (email, igroup)
|
||||
VALUES
|
||||
('alice@example.com', 'engineering'),
|
||||
('bob@example.com', 'engineering'),
|
||||
('bob@example.com', 'sales'),
|
||||
('charlie@example.com', 'sales'),
|
||||
('dave@example.com', 'admins')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Create admin user in workspaces for running tests
|
||||
INSERT INTO usr (workspace_id, username, email, is_admin, operator)
|
||||
VALUES
|
||||
('ws-with-auto-add', 'admin', 'admin@windmill.dev', true, false),
|
||||
('ws-no-auto-add', 'admin', 'admin@windmill.dev', true, false),
|
||||
('ws-multi-group', 'admin', 'admin@windmill.dev', true, false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Create the 'all' group for each workspace (required for user auto-add)
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms)
|
||||
VALUES
|
||||
('ws-with-auto-add', 'all', 'All users', '{}'),
|
||||
('ws-no-auto-add', 'all', 'All users', '{}'),
|
||||
('ws-multi-group', 'all', 'All users', '{}')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Add admin to the 'all' group
|
||||
INSERT INTO usr_to_group (workspace_id, usr, group_)
|
||||
VALUES
|
||||
('ws-with-auto-add', 'admin', 'all'),
|
||||
('ws-no-auto-add', 'admin', 'all'),
|
||||
('ws-multi-group', 'admin', 'all')
|
||||
ON CONFLICT DO NOTHING;
|
||||
31
backend/windmill-common/tests/fixtures/secret_backend.sql
vendored
Normal file
31
backend/windmill-common/tests/fixtures/secret_backend.sql
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Fixture for secret backend migration tests
|
||||
-- Sets up test secrets in the variable table
|
||||
|
||||
-- Create a second workspace for testing workspace isolation
|
||||
INSERT INTO workspace (id, name, owner)
|
||||
VALUES ('test-workspace-2', 'test-workspace-2', 'test-user')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id)
|
||||
VALUES ('test-workspace-2')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key)
|
||||
VALUES ('test-workspace-2', 'cloud', 'test-key-2')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Insert test variables with placeholder values.
|
||||
-- Secret values are encrypted by the test setup using build_crypt + encrypt
|
||||
-- with the workspace key, matching production behavior.
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES
|
||||
('test-workspace', 'u/test-user/db_password', 'PLACEHOLDER', true, 'Database password', '{}'),
|
||||
('test-workspace', 'u/test-user/api_key', 'PLACEHOLDER', true, 'API key for external service', '{}'),
|
||||
('test-workspace', 'u/test-user/public_var', 'not-a-secret', false, 'A non-secret variable', '{}')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- Insert test secrets for workspace 2 (to test isolation)
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES
|
||||
('test-workspace-2', 'u/test-user/other_secret', 'PLACEHOLDER', true, 'Secret in workspace 2', '{}')
|
||||
ON CONFLICT DO NOTHING;
|
||||
@@ -31,7 +31,7 @@ mod tests {
|
||||
|
||||
/// Test that configuring instance groups for a workspace auto-adds existing group members
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_configure_instance_groups_adds_existing_members(db: Pool<Postgres>) {
|
||||
// Configure workspace to auto-add users from 'engineering' group with 'developer' role
|
||||
let groups = vec!["engineering".to_string()];
|
||||
@@ -158,7 +158,7 @@ mod tests {
|
||||
|
||||
/// Test role assignment based on instance group configuration
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_role_assignment_admin(db: Pool<Postgres>) {
|
||||
// Configure workspace with admins group having admin role
|
||||
let groups = vec!["admins".to_string()];
|
||||
@@ -209,7 +209,7 @@ mod tests {
|
||||
|
||||
/// Test role assignment for operator
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_role_assignment_operator(db: Pool<Postgres>) {
|
||||
// Configure workspace with sales group having operator role
|
||||
let groups = vec!["sales".to_string()];
|
||||
@@ -260,7 +260,7 @@ mod tests {
|
||||
|
||||
/// Test role precedence when user is in multiple instance groups
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_role_precedence_multiple_groups(db: Pool<Postgres>) {
|
||||
// Configure workspace with multiple groups: engineering (admin), sales (operator)
|
||||
// Bob is in both groups, should get admin role (highest precedence)
|
||||
@@ -322,7 +322,7 @@ mod tests {
|
||||
|
||||
/// Test removing user from instance group removes them from workspace
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_remove_user_from_instance_group(db: Pool<Postgres>) {
|
||||
// First, add alice to the workspace via engineering group
|
||||
let added_via = json!({"source": "instance_group", "group": "engineering"});
|
||||
@@ -416,7 +416,7 @@ mod tests {
|
||||
|
||||
/// Test that users added via domain are not affected by instance group removal
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_domain_added_users_not_affected_by_group_removal(db: Pool<Postgres>) {
|
||||
// Add alice via domain (not instance group)
|
||||
let added_via = json!({"source": "domain", "domain": "example.com"});
|
||||
@@ -469,7 +469,7 @@ mod tests {
|
||||
|
||||
/// Test cleanup when instance group is removed from workspace configuration
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_cleanup_removed_instance_groups(db: Pool<Postgres>) {
|
||||
// First, add users via engineering group
|
||||
for (username, email) in &[("alice", "alice@example.com"), ("bob", "bob@example.com")] {
|
||||
@@ -588,7 +588,7 @@ mod tests {
|
||||
|
||||
/// Test that users are not duplicated if already in workspace
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_no_duplicate_users(db: Pool<Postgres>) {
|
||||
// Add alice to workspace first (without instance group tracking)
|
||||
sqlx::query!(
|
||||
@@ -636,7 +636,7 @@ mod tests {
|
||||
|
||||
/// Test workspace without auto-add configured is not affected
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_workspace_without_auto_add_not_affected(db: Pool<Postgres>) {
|
||||
// ws-no-auto-add has no instance_groups configured
|
||||
|
||||
@@ -672,7 +672,7 @@ mod tests {
|
||||
|
||||
/// Test querying workspaces configured with a specific instance group
|
||||
#[ignore = "requires database setup - run with --ignored flag"]
|
||||
#[sqlx::test(fixtures("base", "instance_group_auto_add"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "instance_group_auto_add"))]
|
||||
async fn test_query_workspaces_with_instance_group(db: Pool<Postgres>) {
|
||||
// Configure ws-with-auto-add to use engineering group
|
||||
let groups = vec!["engineering".to_string()];
|
||||
@@ -11,8 +11,6 @@
|
||||
use sqlx::{Pool, Postgres};
|
||||
use windmill_common::notify_events::{cleanup_old_events, get_latest_event_id, poll_notify_events};
|
||||
|
||||
mod common;
|
||||
|
||||
/// Helper to insert a test event directly
|
||||
async fn insert_test_event(db: &Pool<Postgres>, channel: &str, payload: &str) -> i64 {
|
||||
sqlx::query_scalar::<_, i64>(
|
||||
@@ -39,7 +37,7 @@ async fn count_events_for_channel(db: &Pool<Postgres>, channel: &str) -> i64 {
|
||||
// Basic Functionality Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_get_latest_event_id_returns_valid_id(db: Pool<Postgres>) {
|
||||
// Get current latest id
|
||||
let latest_id = get_latest_event_id(&db).await.expect("Should get latest event id");
|
||||
@@ -51,7 +49,7 @@ async fn test_get_latest_event_id_returns_valid_id(db: Pool<Postgres>) {
|
||||
assert!(new_latest_id >= new_id, "Latest id should be >= new event id");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_get_latest_event_id_with_events(db: Pool<Postgres>) {
|
||||
let _id1 = insert_test_event(&db, "test_channel_1", "payload1").await;
|
||||
let _id2 = insert_test_event(&db, "test_channel_2", "payload2").await;
|
||||
@@ -61,7 +59,7 @@ async fn test_get_latest_event_id_with_events(db: Pool<Postgres>) {
|
||||
assert!(latest_id >= id3, "Latest id should be >= last inserted id");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_poll_notify_events_no_new_events(db: Pool<Postgres>) {
|
||||
// Get latest id first
|
||||
let latest_id = get_latest_event_id(&db).await.unwrap();
|
||||
@@ -71,7 +69,7 @@ async fn test_poll_notify_events_no_new_events(db: Pool<Postgres>) {
|
||||
assert!(events.is_empty(), "Should return empty vec when polling from latest id");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_poll_notify_events_returns_new_events(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -92,7 +90,7 @@ async fn test_poll_notify_events_returns_new_events(db: Pool<Postgres>) {
|
||||
assert!(our_events[0].id < our_events[1].id, "Events should be ordered by id ascending");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_poll_notify_events_respects_last_event_id(db: Pool<Postgres>) {
|
||||
let id1 = insert_test_event(&db, "test_respect_id", "payload1").await;
|
||||
let _id2 = insert_test_event(&db, "test_respect_id", "payload2").await;
|
||||
@@ -109,7 +107,7 @@ async fn test_poll_notify_events_respects_last_event_id(db: Pool<Postgres>) {
|
||||
assert!(our_events.iter().all(|e| e.id > id1), "All events should have id > id1");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_cleanup_old_events(db: Pool<Postgres>) {
|
||||
// Use unique channel names to avoid interference from other tests
|
||||
let old_channel = format!("test_cleanup_old_{}", uuid::Uuid::new_v4());
|
||||
@@ -156,7 +154,7 @@ async fn test_cleanup_old_events(db: Pool<Postgres>) {
|
||||
// Database Trigger Tests - Verify triggers insert events correctly
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_config_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -178,7 +176,7 @@ async fn test_trigger_notify_config_change(db: Pool<Postgres>) {
|
||||
assert!(!config_events.is_empty(), "Should have notify_config_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_global_setting_change_insert(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -201,7 +199,7 @@ async fn test_trigger_notify_global_setting_change_insert(db: Pool<Postgres>) {
|
||||
assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on insert");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_global_setting_change_update(db: Pool<Postgres>) {
|
||||
// Use a unique setting name for testing
|
||||
let setting_name = format!("test_setting_update_{}", uuid::Uuid::new_v4());
|
||||
@@ -231,7 +229,7 @@ async fn test_trigger_notify_global_setting_change_update(db: Pool<Postgres>) {
|
||||
assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on update");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_global_setting_change_delete(db: Pool<Postgres>) {
|
||||
// Use a unique setting name for testing
|
||||
let setting_name = format!("test_setting_delete_{}", uuid::Uuid::new_v4());
|
||||
@@ -261,7 +259,7 @@ async fn test_trigger_notify_global_setting_change_delete(db: Pool<Postgres>) {
|
||||
assert!(!setting_events.is_empty(), "Should have notify_global_setting_change event on delete");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_workspace_envs_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -283,7 +281,7 @@ async fn test_trigger_notify_workspace_envs_change(db: Pool<Postgres>) {
|
||||
assert!(!env_events.is_empty(), "Should have notify_workspace_envs_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_workspace_key_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -305,7 +303,7 @@ async fn test_trigger_notify_workspace_key_change(db: Pool<Postgres>) {
|
||||
assert!(!key_events.is_empty(), "Should have notify_workspace_key_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_token_invalidation(db: Pool<Postgres>) {
|
||||
// First insert a session token
|
||||
let token = format!("test_token_{}", uuid::Uuid::new_v4());
|
||||
@@ -336,7 +334,7 @@ async fn test_trigger_notify_token_invalidation(db: Pool<Postgres>) {
|
||||
assert!(!token_events.is_empty(), "Should have notify_token_invalidation event");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_webhook_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -355,7 +353,7 @@ async fn test_trigger_notify_webhook_change(db: Pool<Postgres>) {
|
||||
assert!(!webhook_events.is_empty(), "Should have notify_webhook_change event");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_workspace_premium_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -378,7 +376,7 @@ async fn test_trigger_notify_workspace_premium_change(db: Pool<Postgres>) {
|
||||
// HTTP Trigger Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_http_trigger_change(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -408,7 +406,7 @@ async fn test_trigger_notify_http_trigger_change(db: Pool<Postgres>) {
|
||||
// Script/Flow Version Change Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_runnable_version_change_script(db: Pool<Postgres>) {
|
||||
// First create a script without lock
|
||||
let script_path = format!("f/test/script_{}", uuid::Uuid::new_v4());
|
||||
@@ -449,7 +447,7 @@ async fn test_trigger_notify_runnable_version_change_script(db: Pool<Postgres>)
|
||||
assert_eq!(parts[1], "script", "Second part should be 'script'");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_trigger_notify_runnable_version_change_flow(db: Pool<Postgres>) {
|
||||
// First create a flow with empty versions array
|
||||
let flow_path = format!("f/test/flow_{}", uuid::Uuid::new_v4());
|
||||
@@ -494,7 +492,7 @@ async fn test_trigger_notify_runnable_version_change_flow(db: Pool<Postgres>) {
|
||||
// Concurrent Access Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_concurrent_event_insertion(db: Pool<Postgres>) {
|
||||
// Use a unique channel name for this test run
|
||||
let channel = format!("test_concurrent_{}", uuid::Uuid::new_v4());
|
||||
@@ -536,7 +534,7 @@ async fn test_concurrent_event_insertion(db: Pool<Postgres>) {
|
||||
assert_eq!(ids.len(), 10, "All events should have unique IDs");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_polling_isolation(db: Pool<Postgres>) {
|
||||
// Use a unique channel name for this test
|
||||
let channel = format!("test_isolation_{}", uuid::Uuid::new_v4());
|
||||
@@ -591,7 +589,7 @@ async fn test_polling_isolation(db: Pool<Postgres>) {
|
||||
// Edge Case Tests
|
||||
// ============================================================================
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_empty_payload(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -607,7 +605,7 @@ async fn test_empty_payload(db: Pool<Postgres>) {
|
||||
assert_eq!(empty_events[0].payload, "", "Payload should be empty string");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_large_payload(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -625,7 +623,7 @@ async fn test_large_payload(db: Pool<Postgres>) {
|
||||
assert_eq!(large_events[0].payload.len(), 1024, "Payload should be preserved");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_special_characters_in_payload(db: Pool<Postgres>) {
|
||||
let before_id = get_latest_event_id(&db).await.unwrap();
|
||||
|
||||
@@ -642,7 +640,7 @@ async fn test_special_characters_in_payload(db: Pool<Postgres>) {
|
||||
assert_eq!(special_events[0].payload, special_payload, "Special characters should be preserved");
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base"))]
|
||||
async fn test_cleanup_with_no_old_events(db: Pool<Postgres>) {
|
||||
// Use a unique channel name for this test
|
||||
let channel = format!("test_no_old_{}", uuid::Uuid::new_v4());
|
||||
@@ -114,7 +114,7 @@ mod tests {
|
||||
// ==================== Static Token Tests ====================
|
||||
|
||||
/// Test Vault connection with static token
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_vault_connection_static_token(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
|
||||
@@ -132,7 +132,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Test basic CRUD operations with static token
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_vault_crud_static_token(_db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
|
||||
@@ -193,7 +193,7 @@ mod tests {
|
||||
|
||||
/// Test Vault connection with JWT authentication
|
||||
#[cfg(feature = "openidconnect")]
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_vault_connection_jwt(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
setup_base_url().await;
|
||||
@@ -215,7 +215,7 @@ mod tests {
|
||||
|
||||
/// Test basic CRUD operations with JWT authentication
|
||||
#[cfg(feature = "openidconnect")]
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_vault_crud_jwt(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
setup_base_url().await;
|
||||
@@ -260,7 +260,7 @@ mod tests {
|
||||
// ==================== Migration Tests ====================
|
||||
|
||||
/// Test migration from database to Vault
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_migrate_db_to_vault(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
|
||||
@@ -314,7 +314,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Test migration from Vault back to database
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_migrate_vault_to_db(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
|
||||
@@ -373,7 +373,7 @@ mod tests {
|
||||
// ==================== Variable Rename Tests ====================
|
||||
|
||||
/// Test renaming a variable path in Vault
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_variable_rename(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
let _ = &db; // suppress unused warning
|
||||
@@ -445,7 +445,7 @@ mod tests {
|
||||
// ==================== Full Round Trip Test ====================
|
||||
|
||||
/// Test full round-trip: DB -> Vault -> DB with verification
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_full_round_trip(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
|
||||
@@ -501,7 +501,7 @@ mod tests {
|
||||
// ==================== Workspace Isolation Test ====================
|
||||
|
||||
/// Test that workspace isolation is maintained
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
async fn test_workspace_isolation(db: Pool<Postgres>) {
|
||||
skip_if_no_vault!();
|
||||
|
||||
@@ -27,8 +27,6 @@ use windmill_common::secret_backend::{
|
||||
SecretBackend, VaultSettings,
|
||||
};
|
||||
|
||||
mod common;
|
||||
|
||||
fn test_vault_settings() -> VaultSettings {
|
||||
VaultSettings {
|
||||
address: std::env::var("VAULT_ADDR").unwrap_or_else(|_| "http://127.0.0.1:8200".to_string()),
|
||||
@@ -42,7 +40,7 @@ fn test_vault_settings() -> VaultSettings {
|
||||
}
|
||||
|
||||
/// Test that we can connect to Vault
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
#[ignore = "requires running Vault instance"]
|
||||
async fn test_vault_connection_works(db: Pool<Postgres>) {
|
||||
let settings = test_vault_settings();
|
||||
@@ -53,7 +51,7 @@ async fn test_vault_connection_works(db: Pool<Postgres>) {
|
||||
}
|
||||
|
||||
/// Test migration from database to Vault
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
#[ignore = "requires running Vault instance"]
|
||||
async fn test_migrate_db_to_vault(db: Pool<Postgres>) {
|
||||
let settings = test_vault_settings();
|
||||
@@ -119,7 +117,7 @@ async fn test_migrate_db_to_vault(db: Pool<Postgres>) {
|
||||
}
|
||||
|
||||
/// Test migration from Vault to database
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
#[ignore = "requires running Vault instance"]
|
||||
async fn test_migrate_vault_to_db(db: Pool<Postgres>) {
|
||||
let settings = test_vault_settings();
|
||||
@@ -196,7 +194,7 @@ async fn test_migrate_vault_to_db(db: Pool<Postgres>) {
|
||||
}
|
||||
|
||||
/// Test full round-trip migration: DB -> Vault -> DB
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
#[ignore = "requires running Vault instance"]
|
||||
async fn test_full_round_trip_migration(db: Pool<Postgres>) {
|
||||
let settings = test_vault_settings();
|
||||
@@ -276,7 +274,7 @@ async fn test_full_round_trip_migration(db: Pool<Postgres>) {
|
||||
}
|
||||
|
||||
/// Test that workspace isolation is maintained during migration
|
||||
#[sqlx::test(fixtures("base", "secret_backend"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "secret_backend"))]
|
||||
#[ignore = "requires running Vault instance"]
|
||||
async fn test_workspace_isolation(db: Pool<Postgres>) {
|
||||
let settings = test_vault_settings();
|
||||
146
backend/windmill-queue/tests/fixtures/base.sql
vendored
Normal file
146
backend/windmill-queue/tests/fixtures/base.sql
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
-- used for backend automated testing
|
||||
-- https://docs.rs/sqlx/latest/sqlx/attr.test.html
|
||||
|
||||
INSERT INTO workspace
|
||||
(id, name, owner)
|
||||
VALUES ('test-workspace', 'test-workspace', 'test-user');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test@windmill.dev', 'test-user', true, 'Admin');
|
||||
|
||||
INSERT INTO workspace_key(workspace_id, kind, key) VALUES
|
||||
('test-workspace', 'cloud', 'test-key');
|
||||
|
||||
|
||||
INSERT INTO workspace_settings (workspace_id) VALUES
|
||||
('test-workspace');
|
||||
|
||||
INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES
|
||||
('test-workspace', 'all', 'All users', '{}');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username)
|
||||
VALUES ('test@windmill.dev', 'not-a-real-hash', 'password', true, true, 'Test User', 'test-user');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test2@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 2');
|
||||
|
||||
INSERT INTO password(email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES ('test3@windmill.dev', 'not-a-real-hash', 'password', false, true, 'Test User 3');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test2@windmill.dev', 'test-user-2', false, 'User');
|
||||
|
||||
INSERT INTO usr(workspace_id, email, username, is_admin, role) VALUES
|
||||
('test-workspace', 'test3@windmill.dev', 'test-user-3', false, 'User');
|
||||
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN', 'test@windmill.dev', 'test token', true);
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_2', 'test2@windmill.dev', 'test token 2', false);
|
||||
insert INTO token(token, email, label, super_admin) VALUES ('SECRET_TOKEN_3', 'test3@windmill.dev', 'test token 3', false);
|
||||
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin;
|
||||
GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user;
|
||||
|
||||
CREATE FUNCTION "notify_insert_on_completed_job" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('completed', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TRIGGER "notify_insert_on_completed_job"
|
||||
AFTER INSERT ON "v2_job_completed"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_insert_on_completed_job" ();
|
||||
|
||||
|
||||
CREATE FUNCTION "notify_queue" ()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
PERFORM pg_notify('queued', NEW.id::text);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE PLPGSQL;
|
||||
|
||||
CREATE TRIGGER "notify_queue_after_insert"
|
||||
AFTER INSERT ON "v2_job_queue"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
CREATE TRIGGER "notify_queue_after_flow_status_update"
|
||||
AFTER UPDATE ON "v2_job_status"
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.flow_status IS DISTINCT FROM OLD.flow_status)
|
||||
EXECUTE FUNCTION "notify_queue" ();
|
||||
|
||||
-- Apply phase 4:
|
||||
DROP FUNCTION IF EXISTS v2_job_after_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_completed_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_after_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_queue_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_runtime_before_update CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_insert CASCADE;
|
||||
DROP FUNCTION IF EXISTS v2_job_status_before_update CASCADE;
|
||||
|
||||
DROP VIEW IF EXISTS completed_job, completed_job_view, job, queue, queue_view CASCADE;
|
||||
|
||||
ALTER TABLE v2_job_queue
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __last_ping CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_status CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __same_worker CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __pre_run_error CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __mem_peak CASCADE,
|
||||
DROP COLUMN IF EXISTS __root_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __leaf_jobs CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrent_limit CASCADE,
|
||||
DROP COLUMN IF EXISTS __concurrency_time_window_s CASCADE,
|
||||
DROP COLUMN IF EXISTS __timeout CASCADE,
|
||||
DROP COLUMN IF EXISTS __flow_step_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __cache_ttl CASCADE;
|
||||
|
||||
LOCK TABLE v2_job_queue IN ACCESS EXCLUSIVE MODE;
|
||||
ALTER TABLE v2_job_completed
|
||||
DROP COLUMN IF EXISTS __parent_job CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_by CASCADE,
|
||||
DROP COLUMN IF EXISTS __created_at CASCADE,
|
||||
DROP COLUMN IF EXISTS __success CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_hash CASCADE,
|
||||
DROP COLUMN IF EXISTS __script_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __args CASCADE,
|
||||
DROP COLUMN IF EXISTS __logs CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_code CASCADE,
|
||||
DROP COLUMN IF EXISTS __canceled CASCADE,
|
||||
DROP COLUMN IF EXISTS __job_kind CASCADE,
|
||||
DROP COLUMN IF EXISTS __env_id CASCADE,
|
||||
DROP COLUMN IF EXISTS __schedule_path CASCADE,
|
||||
DROP COLUMN IF EXISTS __permissioned_as CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_flow CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_flow_step CASCADE,
|
||||
DROP COLUMN IF EXISTS __language CASCADE,
|
||||
DROP COLUMN IF EXISTS __is_skipped CASCADE,
|
||||
DROP COLUMN IF EXISTS __raw_lock CASCADE,
|
||||
DROP COLUMN IF EXISTS __email CASCADE,
|
||||
DROP COLUMN IF EXISTS __visible_to_owner CASCADE,
|
||||
DROP COLUMN IF EXISTS __tag CASCADE,
|
||||
DROP COLUMN IF EXISTS __priority CASCADE;
|
||||
84
backend/windmill-queue/tests/fixtures/schedule_push.sql
vendored
Normal file
84
backend/windmill-queue/tests/fixtures/schedule_push.sql
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
-- Fixture for schedule push tests
|
||||
-- Sets up scripts, flows, users, and schedules needed to test push_scheduled_job
|
||||
|
||||
-- Password entries for auth resolution
|
||||
INSERT INTO password (email, password_hash, login_type, super_admin, verified, name)
|
||||
VALUES
|
||||
('test@windmill.dev', 'dummy_hash', 'password', false, true, 'Test User'),
|
||||
('obo@windmill.dev', 'dummy_hash', 'password', false, true, 'OBO User')
|
||||
ON CONFLICT (email) DO NOTHING;
|
||||
|
||||
-- OBO user in workspace
|
||||
INSERT INTO usr (workspace_id, email, username, is_admin, role)
|
||||
VALUES ('test-workspace', 'obo@windmill.dev', 'obo-user', false, 'Developer')
|
||||
ON CONFLICT (workspace_id, username) DO NOTHING;
|
||||
|
||||
-- A simple script
|
||||
INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind)
|
||||
VALUES (
|
||||
'test-workspace', 'test-user',
|
||||
'export async function main() { return "ok"; }',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'Test script', '', 'f/system/test_script', 100001, 'deno', '', 'script'
|
||||
);
|
||||
|
||||
-- A script with on_behalf_of_email
|
||||
INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, on_behalf_of_email)
|
||||
VALUES (
|
||||
'test-workspace', 'test-user',
|
||||
'export async function main() { return "obo"; }',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'OBO script', '', 'f/system/obo_script', 100002, 'deno', '', 'script', 'obo@windmill.dev'
|
||||
);
|
||||
|
||||
-- A script with a tag
|
||||
INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, tag)
|
||||
VALUES (
|
||||
'test-workspace', 'test-user',
|
||||
'export async function main() { return "tagged"; }',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'Tagged script', '', 'f/system/tagged_script', 100003, 'deno', '', 'script', 'custom-tag'
|
||||
);
|
||||
|
||||
-- A script with timeout
|
||||
INSERT INTO script (workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, kind, timeout)
|
||||
VALUES (
|
||||
'test-workspace', 'test-user',
|
||||
'export async function main() { return "timeout"; }',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'Timeout script', '', 'f/system/timeout_script', 100004, 'deno', '', 'script', 300
|
||||
);
|
||||
|
||||
-- A flow
|
||||
INSERT INTO flow (workspace_id, summary, description, path, versions, schema, value, edited_by)
|
||||
VALUES (
|
||||
'test-workspace', 'Test flow', '', 'f/system/test_flow', '{200001}',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}',
|
||||
'test-user'
|
||||
);
|
||||
|
||||
INSERT INTO flow_version (id, workspace_id, path, schema, value, created_by)
|
||||
VALUES (
|
||||
200001, 'test-workspace', 'f/system/test_flow',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}',
|
||||
'test-user'
|
||||
);
|
||||
|
||||
-- A flow with on_behalf_of_email
|
||||
INSERT INTO flow (workspace_id, summary, description, path, versions, schema, value, edited_by, on_behalf_of_email)
|
||||
VALUES (
|
||||
'test-workspace', 'OBO flow', '', 'f/system/obo_flow', '{200002}',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}',
|
||||
'test-user', 'obo@windmill.dev'
|
||||
);
|
||||
|
||||
INSERT INTO flow_version (id, workspace_id, path, schema, value, created_by)
|
||||
VALUES (
|
||||
200002, 'test-workspace', 'f/system/obo_flow',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'{"modules": [{"id": "a", "value": {"path": "f/system/test_script", "type": "script", "input_transforms": {}}}]}',
|
||||
'test-user'
|
||||
);
|
||||
@@ -1,5 +1,3 @@
|
||||
mod common;
|
||||
|
||||
mod schedule_push {
|
||||
use chrono::Utc;
|
||||
use sqlx::{Pool, Postgres};
|
||||
@@ -118,7 +116,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: basic script schedule
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_script_schedule(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let authed = make_authed();
|
||||
@@ -140,7 +138,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: flow schedule
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_flow_schedule(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.is_flow = true;
|
||||
@@ -164,7 +162,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: on_behalf_of_email (script)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_script_on_behalf_of_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.script_path = "f/system/obo_script".to_string();
|
||||
@@ -191,7 +189,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: on_behalf_of_email (flow)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_flow_on_behalf_of_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.is_flow = true;
|
||||
@@ -218,7 +216,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: with retry wraps in SingleStepFlow
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_script_with_retry(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.retry = Some(serde_json::json!({
|
||||
@@ -247,7 +245,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: duplicate detection (same schedule + time = skip)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_duplicate_skipped(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let authed = make_authed();
|
||||
@@ -270,7 +268,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: invalid timezone
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_invalid_timezone(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.timezone = "Invalid/Timezone".to_string();
|
||||
@@ -288,7 +286,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: invalid cron expression
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_invalid_cron(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.schedule = "not a cron".to_string();
|
||||
@@ -306,7 +304,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: invalid args (not a dict)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_invalid_args(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
let raw = serde_json::value::RawValue::from_string("[1,2,3]".to_string()).unwrap();
|
||||
@@ -325,7 +323,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: with schedule args passed to job
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_with_args(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
let raw =
|
||||
@@ -353,7 +351,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: script not found
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_script_not_found(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.script_path = "f/system/nonexistent".to_string();
|
||||
@@ -371,7 +369,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: flow not found
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_flow_not_found(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.is_flow = true;
|
||||
@@ -390,7 +388,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: paused schedule (paused_until in future)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_paused_schedule(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.paused_until = Some(Utc::now() + chrono::Duration::hours(1));
|
||||
@@ -417,7 +415,7 @@ mod schedule_push {
|
||||
// push_scheduled_job: clock shift detection (now_cutoff >= now)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_clock_shift(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let authed = make_authed();
|
||||
@@ -445,7 +443,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: disabled schedule does not push
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_handle_disabled_schedule(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.enabled = false;
|
||||
@@ -471,7 +469,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: script path mismatch does not push
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_handle_path_mismatch(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let job = make_completed_job(&schedule);
|
||||
@@ -495,7 +493,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: enabled + matching path pushes next job
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_handle_enabled_pushes_next_job(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let job = make_completed_job(&schedule);
|
||||
@@ -524,7 +522,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: on_behalf_of_email via handle path
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_handle_on_behalf_of_email(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.script_path = "f/system/obo_script".to_string();
|
||||
@@ -559,7 +557,7 @@ mod schedule_push {
|
||||
// (caller is responsible for retry + eventual disable)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_handle_push_failure_disables_schedule(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -604,7 +602,7 @@ mod schedule_push {
|
||||
// If the caller commits, both the next tick and any prior writes persist.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_success_atomic_with_commit(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -646,7 +644,7 @@ mod schedule_push {
|
||||
// Ensures no next tick leaks when the outer tx is not committed.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_success_rolls_back_on_drop(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let job = make_completed_job(&schedule);
|
||||
@@ -675,7 +673,7 @@ mod schedule_push {
|
||||
// The schedule must stay enabled when the caller doesn't commit.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failure_disable_rolls_back_on_drop(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -721,7 +719,7 @@ mod schedule_push {
|
||||
// The caller can perform additional writes on the returned tx.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_tx_usable_after_success(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|_| {});
|
||||
let job = make_completed_job(&schedule);
|
||||
@@ -759,7 +757,7 @@ mod schedule_push {
|
||||
// The caller can still write on the returned tx after a schedule disable.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_tx_usable_after_failure(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -815,7 +813,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: flow schedule pushes next job
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_try_schedule_flow(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.is_flow = true;
|
||||
@@ -847,7 +845,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: script with retry wraps in SingleStepFlow
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_try_schedule_with_retry(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
let schedule = make_schedule(|s| {
|
||||
s.retry = Some(serde_json::json!({
|
||||
@@ -882,7 +880,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: push failure error message is stored on schedule
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_failure_stores_error_message(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -927,7 +925,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: disabled schedule leaves no side effects
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_disabled_schedule_no_side_effects(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -970,7 +968,7 @@ mod schedule_push {
|
||||
// try_schedule_next_job: path mismatch leaves schedule unchanged
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_path_mismatch_no_side_effects(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1012,7 +1010,7 @@ mod schedule_push {
|
||||
// doesn't error. The function should return (tx, None).
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_push_failure_schedule_not_in_db(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
// Do NOT insert a schedule row — the disable UPDATE will match 0 rows
|
||||
let schedule = make_schedule(|s| {
|
||||
@@ -1044,7 +1042,7 @@ mod schedule_push {
|
||||
// unmodified enabled state are committed together.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_invariant_success_means_tick_committed(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1089,7 +1087,7 @@ mod schedule_push {
|
||||
// enabled and no next tick.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_invariant_failure_means_disabled_after_commit(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -1139,7 +1137,7 @@ mod schedule_push {
|
||||
// enabled so that zombie retry can re-attempt.
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_invariant_rollback_preserves_schedule_for_retry(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -1187,6 +1185,7 @@ mod schedule_push {
|
||||
// Failpoint tests — feature-gated, only compiled under `failpoints`
|
||||
// ===================================================================
|
||||
|
||||
#[cfg(feature = "failpoints")]
|
||||
mod failpoint_tests {
|
||||
use super::*;
|
||||
use windmill_queue::jobs::schedule_failpoints::{ScheduleFailPoint, ACTIVE};
|
||||
@@ -1195,7 +1194,7 @@ mod schedule_push {
|
||||
// SavepointCreate failpoint → schedule disabled, 0 jobs
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_savepoint_create_disables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1232,7 +1231,7 @@ mod schedule_push {
|
||||
// Push failpoint → schedule disabled, 0 jobs
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_push_disables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1269,7 +1268,7 @@ mod schedule_push {
|
||||
// SavepointCommit failpoint → schedule disabled, 0 jobs
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_savepoint_commit_disables(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1306,7 +1305,7 @@ mod schedule_push {
|
||||
// ScheduleDisable failpoint → returns Some(err), caller doesn't commit
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_schedule_disable_returns_err(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1335,7 +1334,7 @@ mod schedule_push {
|
||||
// ScheduleDisable failpoint + tx drop → schedule stays enabled
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_disable_failure_rollback(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1375,7 +1374,7 @@ mod schedule_push {
|
||||
// no error handler notification (QuotaExceeded is silenced)
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_push_quota_exceeded_script(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1414,7 +1413,7 @@ mod schedule_push {
|
||||
// PushQuotaExceeded failpoint (flow) → schedule disabled, 0 jobs
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_failpoint_push_quota_exceeded_flow(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
sqlx::query(
|
||||
"INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap)
|
||||
@@ -1470,7 +1469,7 @@ mod schedule_push {
|
||||
// 4. The schedule remains enabled for retry on next flow execution
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
#[sqlx::test(fixtures("base", "schedule_push"))]
|
||||
#[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))]
|
||||
async fn test_zombie_flow_after_schedule_push_failure_meets_restart_criteria(
|
||||
db: Pool<Postgres>,
|
||||
) -> anyhow::Result<()> {
|
||||
37
backend/windmill-test-utils/Cargo.toml
Normal file
37
backend/windmill-test-utils/Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "windmill-test-utils"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "windmill_test_utils"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
private = []
|
||||
enterprise = []
|
||||
python = ["windmill-common/python"]
|
||||
deno_core = ["dep:windmill-runtime-nativets"]
|
||||
agent_worker_server = ["dep:windmill-api-agent-workers"]
|
||||
|
||||
[dependencies]
|
||||
windmill-api = { workspace = true, default-features = false }
|
||||
windmill-worker.workspace = true
|
||||
windmill-queue.workspace = true
|
||||
windmill-common = { workspace = true, default-features = false }
|
||||
windmill-api-client.workspace = true
|
||||
windmill-runtime-nativets = { workspace = true, optional = true }
|
||||
windmill-api-agent-workers = { workspace = true, optional = true }
|
||||
|
||||
sqlx.workspace = true
|
||||
futures.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tokio.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
axum.workspace = true
|
||||
anyhow.workspace = true
|
||||
tracing.workspace = true
|
||||
@@ -574,7 +574,6 @@ pub async fn test_for_versions<F: Future<Output = ()>>(
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
// #[cfg(feature = "python")]
|
||||
pub async fn assert_lockfile(
|
||||
db: &Pool<Postgres>,
|
||||
script_content: String,
|
||||
Reference in New Issue
Block a user