Compare commits

..

15 Commits

Author SHA1 Message Date
Ruben Fiszel
a9bac09b14 all 2026-02-11 18:37:15 +00:00
Ruben Fiszel
607a861b80 all 2026-02-11 18:20:14 +00:00
Ruben Fiszel
5ecfa92ac3 fix: install powershell via dotnet tool for arm64 ci runner
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:48:41 +00:00
Ruben Fiszel
a3d5ae5425 all 2026-02-11 16:37:11 +00:00
Ruben Fiszel
d363fa6098 all 2026-02-11 16:28:20 +00:00
Ruben Fiszel
44143e7576 replace leftover common:: references in dependency_map test (#7911)
* fix: replace leftover common:: references in dependency_map test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing deno_core/mcp features and gate dead code in permissions test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 16:05:34 +00:00
Ruben Fiszel
4d94d80835 add missing feature-gated deps to api-integration-tests crate
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:09:14 +00:00
Ruben Fiszel
c9d5c24c34 chore(main): release 1.631.2 (#7906)
* chore(main): release 1.631.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-11 14:43:13 +00:00
Ruben Fiszel
b11d6ed794 fix(frontend): revert CloseButton refactor that broke tag removal in MultiSelect (#7909)
The recent refactor of CloseButton (from on:close component events to
onClick prop) broke tag removal in MultiSelect/TagsToListenTo. The
refactor changed on:pointerdown (component event) to onPointerdown
(native DOM event), which stopped native pointerdown propagation and
broke the drag tracking in DraggableTags, causing the dropdown to open
on every close button click.

Reverts CloseButton and all callers back to using createEventDispatcher
and on:close.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 14:27:50 +00:00
Ruben Fiszel
ea52a8b8ce refactor: move integration tests to subcrates to reduce recompilation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 14:04:54 +00:00
Ruben Fiszel
b9e0ccaefd chore: split api-scripts and api-flows 2026-02-11 13:27:00 +00:00
Devdatta Talele
5effb87a36 fix: strip unsupported schema fields for Google AI (#7894)
Closes #7759
2026-02-11 13:26:08 +00:00
wendrul
900c76ccad fix: nix flake libz.so for deno_core (#7905) 2026-02-11 13:13:36 +00:00
Ruben Fiszel
ff72b81004 chore(main): release 1.631.1 (#7904)
* chore(main): release 1.631.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-11 12:45:39 +00:00
Ruben Fiszel
2815cfae1a fix: add kafka-gssapi support to ee builds 2026-02-11 12:40:59 +00:00
94 changed files with 1620 additions and 360 deletions

View File

@@ -19,7 +19,7 @@ defaults:
jobs:
cargo_test:
runs-on: ubicloud-standard-16
runs-on: blacksmith-32vcpu-ubuntu-2404
services:
postgres:
image: postgres
@@ -50,9 +50,9 @@ jobs:
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- uses: actions/setup-go@v2
- uses: actions/setup-go@v6
with:
go-version: 1.21.5
go-version: 1.25.0
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.8
@@ -72,11 +72,13 @@ jobs:
bundler-cache: false
- name: Install PowerShell, mold and clang
run: |
sudo apt-get update && sudo apt-get install -y powershell mold clang libcurl4-openssl-dev
dotnet tool install --global PowerShell
echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
sudo apt-get update && sudo apt-get install -y mold clang libcurl4-openssl-dev
working-directory: /
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false
cache: true
toolchain: 1.93.0
- name: Read EE repo commit hash
run: |
@@ -214,7 +216,7 @@ jobs:
RUST_LOG: "off"
RUST_LOG_STYLE: never
CARGO_NET_GIT_FETCH_WITH_CLI: true
CARGO_BUILD_JOBS: 12
CARGO_BUILD_JOBS: 16
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
@@ -222,4 +224,4 @@ jobs:
run: |
deno --version && bun -v && node --version && go version && python3 --version && php --version && ruby --version && pwsh --version && dotnet --version
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp --all -- --nocapture --test-threads=10
DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) PHP_PATH=$(which php) COMPOSER_PATH=$(which composer) RUBY_PATH=$(which ruby) RUBY_BUNDLE_PATH=$(which bundle) RUBY_GEM_PATH=$(which gem) POWERSHELL_PATH=$(which pwsh) DOTNET_PATH=$(which dotnet) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test,csharp,php,ruby,mysql,quickjs,mcp --all -- --nocapture --test-threads=12

View File

@@ -1,5 +1,21 @@
# Changelog
## [1.631.2](https://github.com/windmill-labs/windmill/compare/v1.631.1...v1.631.2) (2026-02-11)
### Bug Fixes
* **frontend:** revert CloseButton refactor that broke tag removal in MultiSelect ([#7909](https://github.com/windmill-labs/windmill/issues/7909)) ([b11d6ed](https://github.com/windmill-labs/windmill/commit/b11d6ed7940faddfe74a22b25bcb132527cbcec8))
* nix flake libz.so for deno_core ([#7905](https://github.com/windmill-labs/windmill/issues/7905)) ([900c76c](https://github.com/windmill-labs/windmill/commit/900c76ccad09f58fc6adcfa8151db780249617f6))
* strip unsupported schema fields for Google AI ([#7894](https://github.com/windmill-labs/windmill/issues/7894)) ([5effb87](https://github.com/windmill-labs/windmill/commit/5effb87a36793e20d17e678619091ef458fe8f0a)), closes [#7759](https://github.com/windmill-labs/windmill/issues/7759)
## [1.631.1](https://github.com/windmill-labs/windmill/compare/v1.631.0...v1.631.1) (2026-02-11)
### Bug Fixes
* add kafka-gssapi support to ee builds ([2815cfa](https://github.com/windmill-labs/windmill/commit/2815cfae1a5eefb2c553e893dc926e62ad1df528))
## [1.631.0](https://github.com/windmill-labs/windmill/compare/v1.630.2...v1.631.0) (2026-02-11)

View File

@@ -49,6 +49,10 @@ Windmill uses a workspace-based architecture with multiple crates:
- Use feature flags: `#[cfg(feature = "enterprise")]`
- Isolate enterprise code in separate modules
## Git Workflow
- **Never push directly to main** — always create a branch and open a pull request
## Testing
- Write unit tests for core functionality

211
backend/Cargo.lock generated
View File

@@ -2322,9 +2322,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.57"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a"
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
dependencies = [
"clap_builder",
"clap_derive",
@@ -2332,9 +2332,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.57"
version = "4.5.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238"
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
dependencies = [
"anstream",
"anstyle",
@@ -2356,9 +2356,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.7.7"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "clipboard-win"
@@ -15725,7 +15725,7 @@ dependencies = [
[[package]]
name = "windmill"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-nats",
@@ -15778,6 +15778,7 @@ dependencies = [
"windmill-indexer",
"windmill-queue",
"windmill-runtime-nativets",
"windmill-test-utils",
"windmill-worker",
"windows-service",
"windows-sys 0.52.0",
@@ -15785,7 +15786,7 @@ dependencies = [
[[package]]
name = "windmill-alerting"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -15798,7 +15799,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"argon2",
@@ -15890,6 +15891,7 @@ dependencies = [
"windmill-api-debug",
"windmill-api-embeddings",
"windmill-api-flow-conversations",
"windmill-api-flows",
"windmill-api-groups",
"windmill-api-inputs",
"windmill-api-jobs",
@@ -15934,7 +15936,7 @@ dependencies = [
[[package]]
name = "windmill-api-agent-workers"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -15957,7 +15959,7 @@ dependencies = [
[[package]]
name = "windmill-api-assets"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -15970,7 +15972,7 @@ dependencies = [
[[package]]
name = "windmill-api-auth"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -15996,7 +15998,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"reqwest 0.12.28",
"serde",
@@ -16006,7 +16008,7 @@ dependencies = [
[[package]]
name = "windmill-api-configs"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16023,7 +16025,7 @@ dependencies = [
[[package]]
name = "windmill-api-debug"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"base64 0.22.1",
@@ -16046,7 +16048,7 @@ dependencies = [
[[package]]
name = "windmill-api-embeddings"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16069,7 +16071,7 @@ dependencies = [
[[package]]
name = "windmill-api-flow-conversations"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16083,9 +16085,29 @@ dependencies = [
"windmill-common",
]
[[package]]
name = "windmill-api-flows"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
"hyper 1.8.1",
"serde",
"serde_json",
"sql-builder",
"sqlx",
"tracing",
"windmill-api-auth",
"windmill-audit",
"windmill-common",
"windmill-dep-map",
"windmill-git-sync",
"windmill-queue",
]
[[package]]
name = "windmill-api-groups"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16105,7 +16127,7 @@ dependencies = [
[[package]]
name = "windmill-api-inputs"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16117,9 +16139,33 @@ dependencies = [
"windmill-common",
]
[[package]]
name = "windmill-api-integration-tests"
version = "1.631.2"
dependencies = [
"anyhow",
"async-nats",
"aws-config",
"aws-credential-types",
"aws-sdk-sqs",
"base64 0.22.1",
"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.0"
version = "1.631.2"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16144,7 +16190,7 @@ dependencies = [
[[package]]
name = "windmill-api-npm-proxy"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"flate2",
@@ -16160,7 +16206,7 @@ dependencies = [
[[package]]
name = "windmill-api-openapi"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16181,7 +16227,7 @@ dependencies = [
[[package]]
name = "windmill-api-schedule"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16201,7 +16247,7 @@ dependencies = [
[[package]]
name = "windmill-api-scripts"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16230,7 +16276,7 @@ dependencies = [
[[package]]
name = "windmill-api-settings"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16256,7 +16302,7 @@ dependencies = [
[[package]]
name = "windmill-api-sse"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"lazy_static",
"serde",
@@ -16268,7 +16314,7 @@ dependencies = [
[[package]]
name = "windmill-api-users"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"argon2",
"axum 0.7.9",
@@ -16291,7 +16337,7 @@ dependencies = [
[[package]]
name = "windmill-api-workers"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16305,7 +16351,7 @@ dependencies = [
[[package]]
name = "windmill-api-workspaces"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"axum 0.7.9",
"chrono",
@@ -16334,7 +16380,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"chrono",
"lazy_static",
@@ -16348,7 +16394,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"axum 0.7.9",
@@ -16367,7 +16413,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"aes-gcm",
"anyhow",
@@ -16466,7 +16512,7 @@ dependencies = [
[[package]]
name = "windmill-dep-map"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"chrono",
"itertools 0.14.0",
@@ -16485,7 +16531,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"regex",
"serde",
@@ -16500,7 +16546,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"astral-tokio-tar",
@@ -16524,7 +16570,7 @@ dependencies = [
[[package]]
name = "windmill-jseval"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"futures",
@@ -16541,7 +16587,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"itertools 0.14.0",
"lazy_static",
@@ -16557,7 +16603,7 @@ dependencies = [
[[package]]
name = "windmill-mcp"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -16578,7 +16624,7 @@ dependencies = [
[[package]]
name = "windmill-native-triggers"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -16609,7 +16655,7 @@ dependencies = [
[[package]]
name = "windmill-oauth"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-oauth2",
@@ -16633,7 +16679,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -16642,7 +16688,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -16654,7 +16700,7 @@ dependencies = [
[[package]]
name = "windmill-parser-csharp"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"serde_json",
@@ -16666,7 +16712,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"gosyn",
@@ -16678,7 +16724,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -16690,7 +16736,7 @@ dependencies = [
[[package]]
name = "windmill-parser-java"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"serde_json",
@@ -16702,7 +16748,7 @@ dependencies = [
[[package]]
name = "windmill-parser-nu"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"nu-parser",
@@ -16713,7 +16759,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16724,7 +16770,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"itertools 0.14.0",
@@ -16737,7 +16783,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -16761,7 +16807,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ruby"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -16775,7 +16821,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -16792,7 +16838,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -16806,7 +16852,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -16825,7 +16871,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"serde",
@@ -16836,7 +16882,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -16873,7 +16919,7 @@ dependencies = [
[[package]]
name = "windmill-runtime-nativets"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"const_format",
@@ -16911,7 +16957,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -16921,7 +16967,7 @@ dependencies = [
[[package]]
name = "windmill-store"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -16948,9 +16994,32 @@ dependencies = [
"windmill-oauth",
]
[[package]]
name = "windmill-test-utils"
version = "1.631.2"
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.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -16983,7 +17052,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-email"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17003,7 +17072,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-gcp"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17037,7 +17106,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-http"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17071,7 +17140,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-kafka"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17094,7 +17163,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-mqtt"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17118,7 +17187,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-nats"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-nats",
@@ -17142,7 +17211,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-postgres"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17177,7 +17246,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-sqs"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17205,7 +17274,7 @@ dependencies = [
[[package]]
name = "windmill-trigger-websocket"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-trait",
@@ -17228,7 +17297,7 @@ dependencies = [
[[package]]
name = "windmill-types"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"chrono",
@@ -17245,7 +17314,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.631.0"
version = "1.631.2"
dependencies = [
"anyhow",
"async-once-cell",

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.631.0"
version = "1.631.2"
authors.workspace = true
edition.workspace = true
@@ -9,6 +9,7 @@ resolver = "2"
members = [
"./windmill-api",
"./windmill-api-scripts",
"./windmill-api-flows",
"./windmill-api-users",
"./windmill-api-workspaces",
"./windmill-api-groups",
@@ -66,12 +67,14 @@ 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"]
[workspace.package]
version = "1.631.0"
version = "1.631.2"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -172,8 +175,8 @@ oss = ["oss_core", "all_languages", "no_auth"]
ce_rpi = ["ce_core", "all_languages"]
ce = ["ce_rpi", "jemalloc", "dind", "agent_worker_server"]
# Edition meta-features: EE variants
ee = ["ce", "ee_core", "ee_server"]
ee_rhel = ["ce_core", "ee_core", "all_languages"]
ee = ["ce", "ee_core", "ee_server", "kafka-gssapi"]
ee_rhel = ["ce_core", "ee_core", "kafka-gssapi", "all_languages"]
ee_windows = ["ce_core", "ee_core", "all_languages_windows"]
all_sqlx_features = ["all_languages", "enterprise", "enterprise_saml", "embedding", "parquet", "prometheus", "flow_testing",
"openidconnect", "cloud", "jemalloc", "tantivy", "sqlx", "kafka", "kafka-gssapi", "nats", "otel", "dind", "websocket", "http_trigger",
@@ -239,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
@@ -267,6 +271,7 @@ windmill-oauth = {path = "./windmill-oauth"}
windmill-macros = {path = "./windmill-macros"}
windmill-api-auth = { path = "./windmill-api-auth" }
windmill-api-scripts = { path = "./windmill-api-scripts" }
windmill-api-flows = { path = "./windmill-api-flows" }
windmill-api-users = { path = "./windmill-api-users" }
windmill-api-workspaces = { path = "./windmill-api-workspaces" }
windmill-api-groups = { path = "./windmill-api-groups" }
@@ -315,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"] }

View File

@@ -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::{

View File

@@ -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::{

View File

@@ -2,8 +2,9 @@ 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, rebuild_dmap, ApiServer,
};
mod dependency_map {
use super::*;
@@ -170,7 +171,7 @@ mod dependency_map {
let (client, _port, _s) = init(db.clone()).await;
assert_dmap(&db, None, CORRECT_DMAP.clone()).await;
// rebuild map
assert!(common::rebuild_dmap(&client).await);
assert!(rebuild_dmap(&client).await);
assert_dmap(&db, None, CORRECT_DMAP.clone()).await;
Ok(())
}
@@ -183,7 +184,7 @@ mod dependency_map {
// Spawn first rebuild
let handle = {
let client = client.clone();
tokio::spawn(async move { common::rebuild_dmap(&client).await })
tokio::spawn(async move { rebuild_dmap(&client).await })
};
// Immidiately spawn another

View File

@@ -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")]

View File

@@ -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 {

View File

@@ -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,

View File

@@ -7,8 +7,7 @@ use windmill_common::{
scripts::ScriptLang,
};
mod common;
use common::*;
use windmill_test_utils::*;
#[derive(Debug, Deserialize)]
struct ListJobsResponse {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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};

View File

@@ -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")]

View File

@@ -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};

View File

@@ -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;

View File

@@ -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;

View File

@@ -0,0 +1,31 @@
[package]
name = "windmill-api-flows"
version.workspace = true
authors.workspace = true
edition.workspace = true
[lib]
name = "windmill_api_flows"
path = "src/lib.rs"
[features]
default = []
enterprise = ["windmill-common/enterprise"]
private = ["windmill-common/private"]
[dependencies]
windmill-common = { workspace = true, default-features = false }
windmill-api-auth.workspace = true
windmill-queue.workspace = true
windmill-audit.workspace = true
windmill-git-sync.workspace = true
windmill-dep-map.workspace = true
axum.workspace = true
hyper.workspace = true
serde.workspace = true
serde_json.workspace = true
sql-builder.workspace = true
sqlx.workspace = true
tracing.workspace = true
chrono.workspace = true

View File

@@ -0,0 +1,9 @@
/*
* Author: Ruben Fiszel
* Copyright: Windmill Labs, Inc 2022
* This file and its contents are licensed under the AGPLv3 License.
* Please see the included NOTICE for copyright information and
* LICENSE-AGPL for a copy of the license.
*/
pub mod flows;

View File

@@ -0,0 +1,36 @@
[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", "dep:aws-config", "dep:aws-credential-types", "dep:aws-sdk-sqs"]
enterprise = ["windmill-test-utils/enterprise", "dep:base64"]
deno_core = ["windmill-test-utils/deno_core"]
mcp = []
[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
aws-config = { workspace = true, optional = true }
aws-credential-types = { workspace = true, optional = true }
aws-sdk-sqs = { workspace = true, optional = true }
base64 = { workspace = true, optional = true }

View File

@@ -0,0 +1,2 @@
// This crate exists only to host integration tests.
// See tests/ directory for the actual test files.

View File

@@ -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?;

View File

@@ -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?;

View 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;

View 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;

View 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');

View 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', '{}');

View File

@@ -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?;

View File

@@ -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?;

View File

@@ -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?;

View File

@@ -1,10 +1,13 @@
#[cfg(feature = "deno_core")]
use serde_json::json;
#[cfg(feature = "deno_core")]
use sqlx::{Pool, Postgres};
mod common;
use common::*;
#[cfg(feature = "deno_core")]
use windmill_test_utils::*;
/// Helper to create a client authenticated as a specific user
#[cfg(feature = "deno_core")]
async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
@@ -18,12 +21,14 @@ async fn create_client_for_user(_port: u16, token: &str) -> reqwest::Client {
}
/// Test helper to check if a GET request succeeds
#[cfg(feature = "deno_core")]
async fn can_read(client: &reqwest::Client, url: &str) -> bool {
let resp = client.get(url).send().await.unwrap();
resp.status().is_success()
}
/// Test helper to check if a POST request succeeds (for write operations)
#[cfg(feature = "deno_core")]
async fn can_write(client: &reqwest::Client, url: &str, body: serde_json::Value) -> bool {
let resp = client.post(url).json(&body).send().await.unwrap();
let status = resp.status();
@@ -44,7 +49,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 +482,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 +552,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 +593,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?;

View File

@@ -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");

View File

@@ -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?;

View File

@@ -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?;

View File

@@ -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?;

View File

@@ -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;

View File

@@ -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#"

View File

@@ -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?;

View File

@@ -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?;

View File

@@ -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;

View File

@@ -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?;

View File

@@ -6,5 +6,4 @@
* LICENSE-AGPL for a copy of the license.
*/
pub mod flows;
pub mod scripts;

View File

@@ -11,7 +11,7 @@ path = "src/lib.rs"
[features]
default = []
private = ["windmill-audit/private", "windmill-common/private", "windmill-api-auth/private", "windmill-store/private", "windmill-api-users/private", "windmill-api-workspaces/private", "windmill-api-groups/private", "windmill-api-configs/private", "windmill-api-settings/private", "windmill-api-agent-workers?/private", "windmill-trigger-kafka?/private", "windmill-trigger-postgres?/private", "windmill-trigger-mqtt?/private", "windmill-trigger-websocket?/private", "windmill-trigger-nats?/private", "windmill-trigger-sqs?/private", "windmill-trigger-gcp?/private", "windmill-trigger-email?/private"]
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"]
enterprise = ["windmill-queue/enterprise", "windmill-audit/enterprise", "windmill-git-sync/enterprise", "windmill-common/enterprise", "windmill-worker?/enterprise", "windmill-api-auth/enterprise", "windmill-store/enterprise", "windmill-api-jobs/enterprise", "windmill-api-scripts/enterprise", "windmill-api-flows/enterprise", "windmill-api-users/enterprise", "windmill-api-workspaces/enterprise", "windmill-api-groups/enterprise", "windmill-api-configs/enterprise", "windmill-api-settings/enterprise", "windmill-api-agent-workers?/enterprise", "windmill-trigger/enterprise", "windmill-trigger-kafka?/enterprise", "windmill-trigger-postgres?/enterprise", "windmill-trigger-mqtt?/enterprise", "windmill-trigger-websocket?/enterprise", "windmill-trigger-email?/enterprise", "windmill-trigger-nats?/enterprise", "windmill-trigger-sqs?/enterprise", "windmill-trigger-gcp?/enterprise", "windmill-trigger-http?/enterprise", "windmill-native-triggers?/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise"]
stripe = []
inline_preview = ["dep:windmill-worker", "windmill-api-configs/inline_preview"]
agent_worker_server = ["dep:windmill-worker", "dep:windmill-api-agent-workers"]
@@ -48,6 +48,7 @@ quickjs = ["windmill-jseval/quickjs"]
windmill-mcp = { workspace = true, optional = true }
windmill-api-auth.workspace = true
windmill-api-scripts.workspace = true
windmill-api-flows.workspace = true
windmill-api-users.workspace = true
windmill-api-workspaces.workspace = true
windmill-api-groups.workspace = true

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.631.0
version: 1.631.2
title: Windmill API
contact:

View File

@@ -6,8 +6,8 @@
* LICENSE-AGPL for a copy of the license.
*/
// Re-export everything from windmill-api-scripts
pub use windmill_api_scripts::flows::*;
// Re-export everything from windmill-api-flows
pub use windmill_api_flows::flows::*;
use crate::triggers::{get_triggers_count_internal, TriggersCount};
use axum::{
@@ -20,7 +20,7 @@ use windmill_common::{error::JsonResult, utils::StripPath, DB};
/// Wraps the subcrate's workspaced_service with the trigger count route
/// that depends on windmill-api internals.
pub fn workspaced_service() -> Router {
windmill_api_scripts::flows::workspaced_service()
windmill_api_flows::flows::workspaced_service()
.route("/get_triggers_count/*path", get(get_triggers_count))
}

View 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;

View 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;

View 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;

View File

@@ -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()];

View File

@@ -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());

View File

@@ -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!();

View File

@@ -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();

View 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;

View 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'
);

View File

@@ -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<()> {

View 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

View File

@@ -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,

View File

@@ -784,10 +784,14 @@ impl OpenAPISchema {
}
/// Sanitizes this schema for Google AI's API by removing unsupported fields.
/// Google's Gemini API does not accept JSON Schema metadata fields like $schema.
/// See https://github.com/windmill-labs/windmill/issues/7759
pub fn sanitize_for_google(&mut self) {
// Remove $schema field - not supported by Google AI
self.schema_url = None;
self.default = None;
self.exclusive_minimum = None;
self.exclusive_maximum = None;
self.r#const = None;
self.multiple_of = None;
// Recursively sanitize nested schemas
if let Some(ref mut items) = self.items {
@@ -1493,6 +1497,8 @@ mod tests {
schema_url: Some("http://json-schema.org/draft-07/schema#".to_string()),
title: Some("Test Schema".to_string()),
description: Some("A test schema".to_string()),
minimum: Some(1.0),
maximum: Some(100.0),
properties: Some(
vec![("name".to_string(), Box::new(string_schema()))]
.into_iter()
@@ -1512,5 +1518,131 @@ mod tests {
assert!(schema.properties.is_some());
assert!(schema.required.is_some());
assert!(matches!(&schema.r#type, Some(SchemaType::Single(t)) if t == "object"));
assert_eq!(schema.minimum, Some(1.0));
assert_eq!(schema.maximum, Some(100.0));
}
#[test]
fn test_sanitize_for_google_removes_default() {
let mut schema = OpenAPISchema {
r#type: Some(SchemaType::Single("string".to_string())),
default: Some(serde_json::Value::String("hello".to_string())),
..Default::default()
};
schema.sanitize_for_google();
assert!(schema.default.is_none(), "default should be removed");
assert!(matches!(&schema.r#type, Some(SchemaType::Single(t)) if t == "string"));
}
#[test]
fn test_sanitize_for_google_removes_exclusive_minimum() {
let mut schema = OpenAPISchema {
r#type: Some(SchemaType::Single("number".to_string())),
exclusive_minimum: Some(0.0),
..Default::default()
};
schema.sanitize_for_google();
assert!(
schema.exclusive_minimum.is_none(),
"exclusiveMinimum should be removed"
);
}
#[test]
fn test_sanitize_for_google_removes_exclusive_maximum() {
let mut schema = OpenAPISchema {
r#type: Some(SchemaType::Single("number".to_string())),
exclusive_maximum: Some(100.0),
..Default::default()
};
schema.sanitize_for_google();
assert!(
schema.exclusive_maximum.is_none(),
"exclusiveMaximum should be removed"
);
}
#[test]
fn test_sanitize_for_google_removes_const() {
let mut schema = OpenAPISchema {
r#type: Some(SchemaType::Single("string".to_string())),
r#const: Some(serde_json::Value::String("fixed".to_string())),
..Default::default()
};
schema.sanitize_for_google();
assert!(schema.r#const.is_none(), "const should be removed");
}
#[test]
fn test_sanitize_for_google_removes_multiple_of() {
let mut schema = OpenAPISchema {
r#type: Some(SchemaType::Single("integer".to_string())),
multiple_of: Some(5.0),
..Default::default()
};
schema.sanitize_for_google();
assert!(
schema.multiple_of.is_none(),
"multipleOf should be removed"
);
}
#[test]
fn test_sanitize_for_google_removes_unsupported_fields_recursively() {
let nested = OpenAPISchema {
r#type: Some(SchemaType::Single("number".to_string())),
default: Some(serde_json::json!(42)),
exclusive_minimum: Some(0.0),
exclusive_maximum: Some(100.0),
multiple_of: Some(2.0),
r#const: Some(serde_json::json!(10)),
..Default::default()
};
let mut schema = OpenAPISchema {
r#type: Some(SchemaType::Single("object".to_string())),
schema_url: Some("http://json-schema.org/draft-07/schema#".to_string()),
default: Some(serde_json::json!({})),
properties: Some(
vec![("value".to_string(), Box::new(nested))]
.into_iter()
.collect(),
),
..Default::default()
};
schema.sanitize_for_google();
assert!(schema.schema_url.is_none());
assert!(schema.default.is_none());
let value_prop = schema.properties.as_ref().unwrap().get("value").unwrap();
assert!(value_prop.default.is_none(), "nested default should be removed");
assert!(
value_prop.exclusive_minimum.is_none(),
"nested exclusiveMinimum should be removed"
);
assert!(
value_prop.exclusive_maximum.is_none(),
"nested exclusiveMaximum should be removed"
);
assert!(
value_prop.multiple_of.is_none(),
"nested multipleOf should be removed"
);
assert!(value_prop.r#const.is_none(), "nested const should be removed");
assert!(schema.properties.is_some());
assert!(matches!(&schema.r#type, Some(SchemaType::Single(t)) if t == "object"));
}
}

View File

@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.631.0";
export const VERSION = "v1.631.2";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({

View File

@@ -77,7 +77,7 @@ export {
// }
// });
export const VERSION = "1.631.0";
export const VERSION = "1.631.2";
// Re-exported from constants.ts to maintain backwards compatibility
export { WM_FORK_PREFIX } from "./core/constants.ts";

View File

@@ -51,6 +51,7 @@
libxslt.dev
libclang.dev
curl.dev
zlib.dev
libffi # For deno_ffi
libtool
nodejs
@@ -324,6 +325,9 @@
# LD_LIBRARY_PATH set in shellHook with a wrapper to avoid leaking into git/ssh
# LD_LIBRARY_PATH = "${pkgs.gcc.lib}/lib";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
pkgs.zlib
];
# Set C flags for Rust's bindgen program. Unlike ordinary C
# compilation, bindgen does not invoke $CC directly. Instead it

View File

@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.631.0",
"version": "1.631.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.631.0",
"version": "1.631.2",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.631.0",
"version": "1.631.2",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -372,7 +372,7 @@
label: 'Reset to all tags minus native ones',
onClick: () => {
if (nconfig != undefined) {
nconfig.worker_tags = defaultTags.concat(nativeTags)
nconfig.worker_tags = defaultTags
}
},
disabled: !canEditConfig,

View File

@@ -157,7 +157,7 @@
>
<div class="leading-6 font-semibold text-sm w-full flex justify-between">
<div>Migrate to CSS editor</div><CloseButton
onClick={() => (migrationModalOpen = false)}
on:close={() => (migrationModalOpen = false)}
/>
</div>

View File

@@ -164,7 +164,7 @@
<CloseButton
noBg
small
onClick={() => {
on:close={() => {
items = items.filter((_, i) => i !== index)
}}
/>

View File

@@ -205,7 +205,7 @@
bind:value={items[index].value}
/>
<div class="absolute right-8">
<CloseButton noBg small onClick={() => deleteSubgrid(index)} />
<CloseButton noBg small on:close={() => deleteSubgrid(index)} />
</div>
<div class="flex flex-col justify-center gap-2">

View File

@@ -104,7 +104,7 @@
/>
{#if deletable}
<div class="flex flex-row-reverse -mt-4">
<CloseButton noBg onClick={() => dispatch('delete', k)} />
<CloseButton noBg on:close={() => dispatch('delete', k)} />
</div>
{/if}
{/if}

View File

@@ -195,7 +195,7 @@
<div class="flex flex-row items-center gap-1">
<CloseButton
small
onClick={() => deleteComponent(component.id, item.originalIndex)}
on:close={() => deleteComponent(component.id, item.originalIndex)}
/>
</div>
</div>

View File

@@ -29,7 +29,7 @@
<div
class="flex justify-between {right ? '' : 'flex-row-reverse'} items-center gap-1 px-3 py-2"
>
<CloseButton onClick={() => secondaryMenu?.close()} />
<CloseButton on:close={() => secondaryMenu?.close()} />
{#if $secondaryMenu?.props?.type === 'style'}
<div class="flex flex-row items-center gap-1">
<div class="text-xs font-bold"> Style Panel</div>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import Button from './button/Button.svelte'
import { X } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
@@ -9,15 +10,17 @@
Icon?: any | undefined
class?: string
id?: string | undefined
onClick?: (e: Event) => void
onClick?: () => void | undefined | any
}
let { noBg = false, small = false, Icon, class: className, id, onClick }: Props = $props()
const dispatch = createEventDispatcher()
</script>
<Button
{onClick}
onPointerdown={(e) => e.stopPropagation()}
on:click={() => (dispatch('close'), onClick?.())}
on:pointerdown={(e) => e.stopPropagation()}
{id}
startIcon={{ icon: Icon ?? X }}
iconOnly

View File

@@ -91,7 +91,7 @@
dropdownBtnClasses?: string
dropdownItems?: MenuItem[] | (() => MenuItem[]) | undefined
hideDropdown?: boolean
onClick?: (e: Event) => void
onClick?: (e?: Event) => void
children?: import('svelte').Snippet
tooltip?: import('svelte').Snippet
[key: string]: any

View File

@@ -61,7 +61,7 @@
}
}}
>
<CloseButton onClick={() => dispatch('close')} Icon={CloseIcon} id="{id}-close-btn" />
<CloseButton on:close Icon={CloseIcon} id="{id}-close-btn" />
</div>
<span class="font-semibold text-emphasis truncate text-lg max-w-sm"
>{title ?? ''}

View File

@@ -90,7 +90,7 @@
{style}
>
{#if kind == 'X'}
<div class="absolute top-4 right-4"><CloseButton onClick={() => (open = false)} /></div
<div class="absolute top-4 right-4"><CloseButton on:close={() => (open = false)} /></div
>
{/if}
<div class="flex">

View File

@@ -142,7 +142,7 @@
onblur={handleBlur}
/>
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
><span class="invisible">days</span><span>{day !== 1 ? 'days' : 'day'}</span></span
><span class="invisible">days</span><span>{day && day > 1 ? 'days' : 'day'}</span></span
>
</div>
<div class="flex items-baseline">
@@ -166,7 +166,7 @@
onblur={handleBlur}
/>
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
><span class="invisible">hrs</span><span>{hour !== 1 ? 'hrs' : 'hr'}</span></span
><span class="invisible">hrs</span><span>{hour && hour > 1 ? 'hrs' : 'hr'}</span></span
>
</div>
<div class="flex items-baseline">
@@ -190,7 +190,7 @@
onblur={handleBlur}
/>
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
><span class="invisible">mins</span><span>{min !== 1 ? 'mins' : 'min'}</span></span
><span class="invisible">mins</span><span>{min && min > 1 ? 'mins' : 'min'}</span></span
>
</div>
<div class="flex items-baseline">
@@ -214,7 +214,7 @@
onblur={handleBlur}
/>
<span class="text-secondary text-2xs inline-grid [&>*]:col-start-1 [&>*]:row-start-1"
><span class="invisible">secs</span><span>{sec !== 1 ? 'secs' : 'sec'}</span></span
><span class="invisible">secs</span><span>{sec && sec > 1 ? 'secs' : 'sec'}</span></span
>
</div>
</div>
@@ -223,7 +223,7 @@
class="bg-transparent text-secondary hover:text-primary"
noBg
small
onClick={() => {
on:close={() => {
seconds = defaultValue
}}
/>

View File

@@ -56,7 +56,7 @@
<CloseButton
class="text-hint bg-transparent border-none"
small
onClick={(e) => { e.stopPropagation(); onRemove(item) }}
on:close={(e) => (onRemove(item), e.stopPropagation())}
/>
{/if}
</li>

View File

@@ -158,7 +158,7 @@
noBg
class="ml-2 remove-all bg-transparent text-hint"
small
onClick={(e) => { e.stopPropagation(); clearValue() }}
on:close={(e) => (clearValue(), e.stopPropagation())}
/>
{/if}
<SelectDropdown

View File

@@ -147,7 +147,7 @@
class="bg-transparent text-secondary hover:text-primary"
noBg
small
onClick={clearValue}
on:close={clearValue}
/>
</div>
{:else if RightIcon}

View File

@@ -280,7 +280,7 @@
{/if}
</Cell>
<Cell class="w-12">
<CloseButton small onClick={() => removeDataTable(dataTableIndex)} />
<CloseButton small on:close={() => removeDataTable(dataTableIndex)} />
</Cell>
</Row>
{/each}

View File

@@ -378,7 +378,7 @@
</div>
</Cell>
<Cell class="w-12">
<CloseButton small onClick={() => removeDucklake(ducklakeIndex)} />
<CloseButton small on:close={() => removeDucklake(ducklakeIndex)} />
</Cell>
</Row>
{/each}

View File

@@ -227,7 +227,7 @@
{#if tableRow[0] !== null}
<CloseButton
small
onClick={() => {
on:close={() => {
if (s3ResourceSettings.secondaryStorage) {
s3ResourceSettings.secondaryStorage.splice(idx - 1, 1)
s3ResourceSettings.secondaryStorage = [...s3ResourceSettings.secondaryStorage]

View File

@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.631.0"
wmill_pg = ">=1.631.0"
wmill = ">=1.631.2"
wmill_pg = ">=1.631.2"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"

View File

@@ -1,7 +1,7 @@
openapi: '3.0.3'
info:
version: 1.631.0
version: 1.631.2
title: OpenFlow Spec
contact:
name: Ruben Fiszel

View File

@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.631.0'
ModuleVersion = '1.631.2'
# Supported PSEditions
# CompatiblePSEditions = @()

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.631.0"
version = "1.631.2"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.631.0"
version = "1.631.2"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"

View File

@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.631.0",
"version": "1.631.2",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./sqlUtils.ts", "./client.ts"]

View File

@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.631.0",
"version": "1.631.2",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"sideEffects": false,

View File

@@ -1 +1 @@
1.631.0
1.631.2