diff --git a/backend/.sqlx/query-009fd85c1aa55ea36454eafffbf62505461eb1cc8b9af85a2e2ab6e7dbe950c6.json b/backend/.sqlx/query-009fd85c1aa55ea36454eafffbf62505461eb1cc8b9af85a2e2ab6e7dbe950c6.json deleted file mode 100644 index c687589db1..0000000000 --- a/backend/.sqlx/query-009fd85c1aa55ea36454eafffbf62505461eb1cc8b9af85a2e2ab6e7dbe950c6.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, ws_error_handler_muted)\n VALUES ('test-workspace', $1, 'f/test/muted_failing_script', 'export function main() { throw new Error(\"fail\"); }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\\n//bun.lock\\n', true)\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "009fd85c1aa55ea36454eafffbf62505461eb1cc8b9af85a2e2ab6e7dbe950c6" -} diff --git a/backend/.sqlx/query-1043947475822c27f73b5357e6ec0d527d999f2865ff5de75186b49004a3565e.json b/backend/.sqlx/query-1043947475822c27f73b5357e6ec0d527d999f2865ff5de75186b49004a3565e.json deleted file mode 100644 index a0847ae2ca..0000000000 --- a/backend/.sqlx/query-1043947475822c27f73b5357e6ec0d527d999f2865ff5de75186b49004a3565e.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', $1, 'f/test/simple_script', $2, 'bun', 'script', 'test-user', '{}', 'Simple test script', 'A simple test script', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - }, - "nullable": [] - }, - "hash": "1043947475822c27f73b5357e6ec0d527d999f2865ff5de75186b49004a3565e" -} diff --git a/backend/.sqlx/query-12c3101e02f197ad731b66f539fe677ad25520fcc9c5a2378a293122956bed4c.json b/backend/.sqlx/query-12c3101e02f197ad731b66f539fe677ad25520fcc9c5a2378a293122956bed4c.json deleted file mode 100644 index 1a5b16143b..0000000000 --- a/backend/.sqlx/query-12c3101e02f197ad731b66f539fe677ad25520fcc9c5a2378a293122956bed4c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "12c3101e02f197ad731b66f539fe677ad25520fcc9c5a2378a293122956bed4c" -} diff --git a/backend/.sqlx/query-18d1a6ac94f2c87d5ea8c48a228f061135be4065dca33fbc429d8b0186c5ccb3.json b/backend/.sqlx/query-18d1a6ac94f2c87d5ea8c48a228f061135be4065dca33fbc429d8b0186c5ccb3.json deleted file mode 100644 index 8bd9bc4712..0000000000 --- a/backend/.sqlx/query-18d1a6ac94f2c87d5ea8c48a228f061135be4065dca33fbc429d8b0186c5ccb3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET error_handler = '{\"path\": \"script/f/test/error_handler\", \"extra_args\": {\"notify\": true}, \"muted_on_cancel\": true, \"muted_on_user_path\": false}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "18d1a6ac94f2c87d5ea8c48a228f061135be4065dca33fbc429d8b0186c5ccb3" -} diff --git a/backend/.sqlx/query-25bf3956b4365a4d4f96368bcc6e33817ae284ab26c195eee53d988d74263e86.json b/backend/.sqlx/query-25bf3956b4365a4d4f96368bcc6e33817ae284ab26c195eee53d988d74263e86.json deleted file mode 100644 index f7d03b7ebe..0000000000 --- a/backend/.sqlx/query-25bf3956b4365a4d4f96368bcc6e33817ae284ab26c195eee53d988d74263e86.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT (error_handler->>'muted_on_cancel')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "bool", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "25bf3956b4365a4d4f96368bcc6e33817ae284ab26c195eee53d988d74263e86" -} diff --git a/backend/.sqlx/query-28b8264a427e8c19be823d2b8e40736a7b7675780672b503d91737cd0c617657.json b/backend/.sqlx/query-28b8264a427e8c19be823d2b8e40736a7b7675780672b503d91737cd0c617657.json deleted file mode 100644 index 4a34503aaa..0000000000 --- a/backend/.sqlx/query-28b8264a427e8c19be823d2b8e40736a7b7675780672b503d91737cd0c617657.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT (error_handler->>'muted_on_user_path')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "bool", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "28b8264a427e8c19be823d2b8e40736a7b7675780672b503d91737cd0c617657" -} diff --git a/backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json b/backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json deleted file mode 100644 index 3eb58e590f..0000000000 --- a/backend/.sqlx/query-28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = '{\"path\": \"script/f/test/success_handler\", \"extra_args\": {\"key\": \"value\"}}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "28f3dfb70aed943ad3d64da608168c027c645afb73dfc2360245b68ba593cab4" -} diff --git a/backend/.sqlx/query-2c2ed5c91a4893966e94305df8b9f8ac904c8d0781a83fdea66c918320fb5601.json b/backend/.sqlx/query-2c2ed5c91a4893966e94305df8b9f8ac904c8d0781a83fdea66c918320fb5601.json deleted file mode 100644 index 4b692f11a2..0000000000 --- a/backend/.sqlx/query-2c2ed5c91a4893966e94305df8b9f8ac904c8d0781a83fdea66c918320fb5601.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = NULL\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "2c2ed5c91a4893966e94305df8b9f8ac904c8d0781a83fdea66c918320fb5601" -} diff --git a/backend/.sqlx/query-344c08104d62d9bcd3f9117abb574e73a6fb364e4ab3e3916958c2e493673b1d.json b/backend/.sqlx/query-344c08104d62d9bcd3f9117abb574e73a6fb364e4ab3e3916958c2e493673b1d.json deleted file mode 100644 index 37f090c2b4..0000000000 --- a/backend/.sqlx/query-344c08104d62d9bcd3f9117abb574e73a6fb364e4ab3e3916958c2e493673b1d.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 3333333333, 'f/test/error_handler', 'export function main() { return \"handled\"; }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "344c08104d62d9bcd3f9117abb574e73a6fb364e4ab3e3916958c2e493673b1d" -} diff --git a/backend/.sqlx/query-3a71ab02239d8dd4e2f440a35155459a7d5091c125a0998e79d89f8c560a3fd3.json b/backend/.sqlx/query-3a71ab02239d8dd4e2f440a35155459a7d5091c125a0998e79d89f8c560a3fd3.json deleted file mode 100644 index 1c370d3ccf..0000000000 --- a/backend/.sqlx/query-3a71ab02239d8dd4e2f440a35155459a7d5091c125a0998e79d89f8c560a3fd3.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO group_ (workspace_id, name, summary, extra_perms)\n VALUES ('test-workspace', 'success_handler', 'The group the success handler acts on behalf of', '{\"u/test-user\": true}')\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "3a71ab02239d8dd4e2f440a35155459a7d5091c125a0998e79d89f8c560a3fd3" -} diff --git a/backend/.sqlx/query-42250637493a623850ca76fe5a705fe4acef039f38a5e76da8d09f2f7e755779.json b/backend/.sqlx/query-42250637493a623850ca76fe5a705fe4acef039f38a5e76da8d09f2f7e755779.json deleted file mode 100644 index 402a406820..0000000000 --- a/backend/.sqlx/query-42250637493a623850ca76fe5a705fe4acef039f38a5e76da8d09f2f7e755779.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 1234567890, 'f/test/success_handler', $1, 'bun', 'script', 'test-user', '{}', 'Success handler script', 'Handles successful job completions', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "42250637493a623850ca76fe5a705fe4acef039f38a5e76da8d09f2f7e755779" -} diff --git a/backend/.sqlx/query-5f5bdd667f699a5a1c05e07d805a97afb06252541d06b4785063819c3893b674.json b/backend/.sqlx/query-5f5bdd667f699a5a1c05e07d805a97afb06252541d06b4785063819c3893b674.json deleted file mode 100644 index 812fed8f23..0000000000 --- a/backend/.sqlx/query-5f5bdd667f699a5a1c05e07d805a97afb06252541d06b4785063819c3893b674.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO group_ (workspace_id, name, summary, extra_perms)\n VALUES ('test-workspace', 'error_handler', 'The group the error handler acts on behalf of', '{\"u/test-user\": true}')\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "5f5bdd667f699a5a1c05e07d805a97afb06252541d06b4785063819c3893b674" -} diff --git a/backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json b/backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json deleted file mode 100644 index c0e9198953..0000000000 --- a/backend/.sqlx/query-65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = '{\"path\": \"script/f/test/success_handler\"}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "65016ea993fb40d4ab6b0a6f15c2082e8dfbaada01243bae07065c089bc42fa9" -} diff --git a/backend/.sqlx/query-69e1c04c246c42a3a1d7202b6fb349d0ac779431993e225945ddfb91489cb927.json b/backend/.sqlx/query-69e1c04c246c42a3a1d7202b6fb349d0ac779431993e225945ddfb91489cb927.json deleted file mode 100644 index 751b727d0f..0000000000 --- a/backend/.sqlx/query-69e1c04c246c42a3a1d7202b6fb349d0ac779431993e225945ddfb91489cb927.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 1111111111, 'f/test/error_handler', $1, 'bun', 'script', 'test-user', '{}', 'Error handler script', 'Handles failed job completions', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "69e1c04c246c42a3a1d7202b6fb349d0ac779431993e225945ddfb91489cb927" -} diff --git a/backend/.sqlx/query-79648f03eb50385183c34d57bda1ac984cf6502609df9ee51b7d684b585b8447.json b/backend/.sqlx/query-79648f03eb50385183c34d57bda1ac984cf6502609df9ee51b7d684b585b8447.json deleted file mode 100644 index fbf0c98de4..0000000000 --- a/backend/.sqlx/query-79648f03eb50385183c34d57bda1ac984cf6502609df9ee51b7d684b585b8447.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET error_handler = NULL\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "79648f03eb50385183c34d57bda1ac984cf6502609df9ee51b7d684b585b8447" -} diff --git a/backend/.sqlx/query-8511e096394edb1433f6572d50b66d8558866af53d829d83fa634f12a90733c0.json b/backend/.sqlx/query-8511e096394edb1433f6572d50b66d8558866af53d829d83fa634f12a90733c0.json deleted file mode 100644 index 3420b8c337..0000000000 --- a/backend/.sqlx/query-8511e096394edb1433f6572d50b66d8558866af53d829d83fa634f12a90733c0.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', $1, 'f/test/success_script', 'export function main() { return \"ok\"; }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8" - ] - }, - "nullable": [] - }, - "hash": "8511e096394edb1433f6572d50b66d8558866af53d829d83fa634f12a90733c0" -} diff --git a/backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json b/backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json deleted file mode 100644 index 9915fbf6c8..0000000000 --- a/backend/.sqlx/query-87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT success_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "87044e1466cd114cbef336620e96dc29f3f930149fedc240d44c614dfc794ff7" -} diff --git a/backend/.sqlx/query-b0c10d17ed684af4dc79db7a199d4438ea003b5762178f25ff12ef23c1a8f46a.json b/backend/.sqlx/query-b0c10d17ed684af4dc79db7a199d4438ea003b5762178f25ff12ef23c1a8f46a.json deleted file mode 100644 index a5a8c00494..0000000000 --- a/backend/.sqlx/query-b0c10d17ed684af4dc79db7a199d4438ea003b5762178f25ff12ef23c1a8f46a.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', $1, 'f/test/failing_script', $2, 'bun', 'script', 'test-user', '{}', 'Failing test script', 'A script that always fails', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Text" - ] - }, - "nullable": [] - }, - "hash": "b0c10d17ed684af4dc79db7a199d4438ea003b5762178f25ff12ef23c1a8f46a" -} diff --git a/backend/.sqlx/query-ba0c3dad5d2a77922821c9c6d58e06a65e6666e197f3e59b880470df35c27bec.json b/backend/.sqlx/query-ba0c3dad5d2a77922821c9c6d58e06a65e6666e197f3e59b880470df35c27bec.json deleted file mode 100644 index 7c4fe34428..0000000000 --- a/backend/.sqlx/query-ba0c3dad5d2a77922821c9c6d58e06a65e6666e197f3e59b880470df35c27bec.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO group_ (workspace_id, name, summary, extra_perms)\n VALUES ('test-workspace', 'error_handler', 'Error handler group', '{\"u/test-user\": true}')\n ON CONFLICT DO NOTHING\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "ba0c3dad5d2a77922821c9c6d58e06a65e6666e197f3e59b880470df35c27bec" -} diff --git a/backend/.sqlx/query-bb4cde84157126f29680cb36075848e7cde2ab7c6b7f0861becd4c0b8c330a8d.json b/backend/.sqlx/query-bb4cde84157126f29680cb36075848e7cde2ab7c6b7f0861becd4c0b8c330a8d.json deleted file mode 100644 index 4c4e0b71a4..0000000000 --- a/backend/.sqlx/query-bb4cde84157126f29680cb36075848e7cde2ab7c6b7f0861becd4c0b8c330a8d.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET error_handler = '{\"path\": \"script/f/test/error_handler\"}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "bb4cde84157126f29680cb36075848e7cde2ab7c6b7f0861becd4c0b8c330a8d" -} diff --git a/backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json b/backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json deleted file mode 100644 index c621e4b903..0000000000 --- a/backend/.sqlx/query-c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE workspace_settings\n SET success_handler = '{\"path\": \"flow/f/test/success_flow\"}'::jsonb\n WHERE workspace_id = 'test-workspace'\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "c2f271bcea1f11b2979152cc743559236fd875192b7b7f4824c63675fdc6a551" -} diff --git a/backend/.sqlx/query-cc719da4da98b6fe9612a4cc2f42dd19834a3e0d0be593ea6bc1239fe5453a74.json b/backend/.sqlx/query-cc719da4da98b6fe9612a4cc2f42dd19834a3e0d0be593ea6bc1239fe5453a74.json deleted file mode 100644 index 91cacf4123..0000000000 --- a/backend/.sqlx/query-cc719da4da98b6fe9612a4cc2f42dd19834a3e0d0be593ea6bc1239fe5453a74.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock)\n VALUES ('test-workspace', 5555555555, 'f/test/error_handler', 'export function main() { return \"handled\"; }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\\n//bun.lock\\n')\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "cc719da4da98b6fe9612a4cc2f42dd19834a3e0d0be593ea6bc1239fe5453a74" -} diff --git a/backend/.sqlx/query-d5f1a2bfbac206a33b991d58f9ff4b8e8e4a3840ea1c65d409576379dede41ed.json b/backend/.sqlx/query-d5f1a2bfbac206a33b991d58f9ff4b8e8e4a3840ea1c65d409576379dede41ed.json deleted file mode 100644 index c6eb9e16a3..0000000000 --- a/backend/.sqlx/query-d5f1a2bfbac206a33b991d58f9ff4b8e8e4a3840ea1c65d409576379dede41ed.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT error_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "d5f1a2bfbac206a33b991d58f9ff4b8e8e4a3840ea1c65d409576379dede41ed" -} diff --git a/backend/.sqlx/query-e4c9f3bca6bd636c3ee31fc643182ed5ca48a45da895fb992d820682f5169f7c.json b/backend/.sqlx/query-e4c9f3bca6bd636c3ee31fc643182ed5ca48a45da895fb992d820682f5169f7c.json deleted file mode 100644 index 63ce99a139..0000000000 --- a/backend/.sqlx/query-e4c9f3bca6bd636c3ee31fc643182ed5ca48a45da895fb992d820682f5169f7c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT id\n FROM v2_job\n WHERE workspace_id = 'test-workspace'\n AND permissioned_as_email = 'error_handler@windmill.dev'\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false - ] - }, - "hash": "e4c9f3bca6bd636c3ee31fc643182ed5ca48a45da895fb992d820682f5169f7c" -} diff --git a/backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json b/backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json deleted file mode 100644 index 91479f0583..0000000000 --- a/backend/.sqlx/query-e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "?column?", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "e9808cc152e1ce4717260ebb42f203ca16321a4550073258734a6208b83c00c2" -} diff --git a/backend/.sqlx/query-fdf0a16a72bc2eca2025d64750e2e6a7e7ff90b5ad4b42e6aab53992b447a20e.json b/backend/.sqlx/query-fdf0a16a72bc2eca2025d64750e2e6a7e7ff90b5ad4b42e6aab53992b447a20e.json deleted file mode 100644 index 69497769d7..0000000000 --- a/backend/.sqlx/query-fdf0a16a72bc2eca2025d64750e2e6a7e7ff90b5ad4b42e6aab53992b447a20e.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n id,\n runnable_path,\n permissioned_as_email,\n parent_job\n FROM v2_job\n WHERE workspace_id = 'test-workspace'\n AND permissioned_as_email = 'error_handler@windmill.dev'\n ORDER BY created_at DESC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "runnable_path", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "permissioned_as_email", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "parent_job", - "type_info": "Uuid" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - true, - false, - true - ] - }, - "hash": "fdf0a16a72bc2eca2025d64750e2e6a7e7ff90b5ad4b42e6aab53992b447a20e" -} diff --git a/backend/tests/error_handler.rs b/backend/tests/error_handler.rs deleted file mode 100644 index 2e10006d2f..0000000000 --- a/backend/tests/error_handler.rs +++ /dev/null @@ -1,480 +0,0 @@ -use sqlx::{Pool, Postgres}; - -use windmill_test_utils::*; - -/// Test that workspace error handler can be set and removed via database operations -#[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base"))] -async fn test_error_handler_settings(db: Pool) -> anyhow::Result<()> { - initialize_tracing().await; - - let _server = ApiServer::start(db.clone()).await?; - - // Initially error_handler should be NULL - let initial = sqlx::query_scalar!( - r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert!(initial.is_none()); - - // Set error handler with all options - sqlx::query!( - r#" - UPDATE workspace_settings - SET error_handler = '{"path": "script/f/test/error_handler", "extra_args": {"notify": true}, "muted_on_cancel": true, "muted_on_user_path": false}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - let after_set = sqlx::query_scalar!( - r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert_eq!(after_set, Some("script/f/test/error_handler".to_string())); - - // Verify extra_args - let extra_args = sqlx::query_scalar!( - r#"SELECT error_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert!(extra_args.is_some()); - - // Verify muted_on_cancel - let muted_on_cancel = sqlx::query_scalar!( - r#"SELECT (error_handler->>'muted_on_cancel')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert_eq!(muted_on_cancel, Some(true)); - - // Verify muted_on_user_path - let muted_on_user_path = sqlx::query_scalar!( - r#"SELECT (error_handler->>'muted_on_user_path')::boolean FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert_eq!(muted_on_user_path, Some(false)); - - // Remove error handler - sqlx::query!( - r#" - UPDATE workspace_settings - SET error_handler = NULL - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - let after_remove = sqlx::query_scalar!( - r#"SELECT error_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert!(after_remove.is_none()); - - Ok(()) -} - -/// Test that a failed job triggers the workspace error handler -#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))] -#[sqlx::test(fixtures("base"))] -async fn test_error_handler_triggered_on_failure(db: Pool) -> anyhow::Result<()> { - use windmill_common::jobs::JobPayload; - use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings}; - use windmill_common::scripts::{ScriptHash, ScriptLang}; - - initialize_tracing().await; - - let server = ApiServer::start(db.clone()).await?; - - // Create the error handler script - let error_handler_code = r#" -export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, error: any) { - console.log("Error handler called for job:", job_id); - return { handled: true, original_path: path }; -} -"#; - - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', 1111111111, 'f/test/error_handler', $1, 'bun', 'script', 'test-user', '{}', 'Error handler script', 'Handles failed job completions', E'{}\n//bun.lock\n') - "#, - error_handler_code - ) - .execute(&db) - .await?; - - // Create a script that will fail - let failing_script_code = "export function main() { throw new Error('intentional failure'); }"; - let failing_script_hash: i64 = 2222222222; - - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', $1, 'f/test/failing_script', $2, 'bun', 'script', 'test-user', '{}', 'Failing test script', 'A script that always fails', E'{}\n//bun.lock\n') - "#, - failing_script_hash, - failing_script_code - ) - .execute(&db) - .await?; - - // Set up the error handler in workspace_settings - sqlx::query!( - r#" - UPDATE workspace_settings - SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - // Create the error_handler group - sqlx::query!( - r#" - INSERT INTO group_ (workspace_id, name, summary, extra_perms) - VALUES ('test-workspace', 'error_handler', 'The group the error handler acts on behalf of', '{"u/test-user": true}') - ON CONFLICT DO NOTHING - "# - ) - .execute(&db) - .await?; - - // Run the failing script - let completed_job = RunJob::from(JobPayload::ScriptHash { - hash: ScriptHash(failing_script_hash), - path: "f/test/failing_script".to_string(), - cache_ttl: None, - cache_ignore_s3_path: None, - dedicated_worker: None, - language: ScriptLang::Bun, - priority: None, - apply_preprocessor: false, - concurrency_settings: ConcurrencySettings::default(), - debouncing_settings: DebouncingSettings::default(), - labels: None, - job_token_scopes: None, - }) - .run_until_complete(&db, false, server.addr.port()) - .await; - - // Verify the job actually failed - assert!(!completed_job.success, "Job should have failed"); - - let main_job_id = completed_job.id; - - // Wait for the error handler job to be created - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - - // Verify the error handler job was created - let error_handler_job = sqlx::query!( - r#" - SELECT - id, - runnable_path, - permissioned_as_email, - parent_job - FROM v2_job - WHERE workspace_id = 'test-workspace' - AND permissioned_as_email = 'error_handler@windmill.dev' - ORDER BY created_at DESC - LIMIT 1 - "# - ) - .fetch_optional(&db) - .await?; - - assert!( - error_handler_job.is_some(), - "Error handler job should have been created" - ); - - let handler_job = error_handler_job.unwrap(); - - assert_eq!( - handler_job.runnable_path.as_deref(), - Some("f/test/error_handler"), - "Error handler should run the configured script" - ); - assert_eq!( - handler_job.permissioned_as_email.as_str(), - "error_handler@windmill.dev", - "Error handler should run as error_handler user" - ); - assert_eq!( - handler_job.parent_job, - Some(main_job_id), - "Error handler should have the failed job as parent" - ); - - Ok(()) -} - -/// Test that error handler is NOT triggered when ws_error_handler_muted is set on the script -#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))] -#[sqlx::test(fixtures("base"))] -async fn test_error_handler_muted_on_script(db: Pool) -> anyhow::Result<()> { - use windmill_common::jobs::JobPayload; - use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings}; - use windmill_common::scripts::{ScriptHash, ScriptLang}; - - initialize_tracing().await; - - let server = ApiServer::start(db.clone()).await?; - - // Create the error handler script - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', 3333333333, 'f/test/error_handler', 'export function main() { return "handled"; }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\n//bun.lock\n') - "#, - ) - .execute(&db) - .await?; - - // Create a failing script with ws_error_handler_muted = true - let failing_script_hash: i64 = 4444444444; - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, ws_error_handler_muted) - VALUES ('test-workspace', $1, 'f/test/muted_failing_script', 'export function main() { throw new Error("fail"); }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\n//bun.lock\n', true) - "#, - failing_script_hash, - ) - .execute(&db) - .await?; - - // Set up the error handler - sqlx::query!( - r#" - UPDATE workspace_settings - SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - sqlx::query!( - r#" - INSERT INTO group_ (workspace_id, name, summary, extra_perms) - VALUES ('test-workspace', 'error_handler', 'Error handler group', '{"u/test-user": true}') - ON CONFLICT DO NOTHING - "# - ) - .execute(&db) - .await?; - - // Run the muted failing script - let completed_job = RunJob::from(JobPayload::ScriptHash { - hash: ScriptHash(failing_script_hash), - path: "f/test/muted_failing_script".to_string(), - cache_ttl: None, - cache_ignore_s3_path: None, - dedicated_worker: None, - language: ScriptLang::Bun, - priority: None, - apply_preprocessor: false, - concurrency_settings: ConcurrencySettings::default(), - debouncing_settings: DebouncingSettings::default(), - labels: None, - job_token_scopes: None, - }) - .run_until_complete(&db, false, server.addr.port()) - .await; - - assert!(!completed_job.success, "Job should have failed"); - - // Wait and check that NO error handler job was created - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - - let error_handler_job = sqlx::query_scalar!( - r#" - SELECT id - FROM v2_job - WHERE workspace_id = 'test-workspace' - AND permissioned_as_email = 'error_handler@windmill.dev' - "# - ) - .fetch_optional(&db) - .await?; - - assert!( - error_handler_job.is_none(), - "Error handler should NOT have been triggered for a muted script" - ); - - Ok(()) -} - -/// Test that error handler is NOT triggered on successful job completion -#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))] -#[sqlx::test(fixtures("base"))] -async fn test_error_handler_not_triggered_on_success(db: Pool) -> anyhow::Result<()> { - use windmill_common::jobs::JobPayload; - use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings}; - use windmill_common::scripts::{ScriptHash, ScriptLang}; - - initialize_tracing().await; - - let server = ApiServer::start(db.clone()).await?; - - // Create the error handler script - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', 5555555555, 'f/test/error_handler', 'export function main() { return "handled"; }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\n//bun.lock\n') - "#, - ) - .execute(&db) - .await?; - - // Create a successful script - let success_script_hash: i64 = 6666666666; - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', $1, 'f/test/success_script', 'export function main() { return "ok"; }', 'bun', 'script', 'test-user', '{}', '', '', E'{}\n//bun.lock\n') - "#, - success_script_hash, - ) - .execute(&db) - .await?; - - // Set up the error handler - sqlx::query!( - r#" - UPDATE workspace_settings - SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - sqlx::query!( - r#" - INSERT INTO group_ (workspace_id, name, summary, extra_perms) - VALUES ('test-workspace', 'error_handler', 'Error handler group', '{"u/test-user": true}') - ON CONFLICT DO NOTHING - "# - ) - .execute(&db) - .await?; - - // Run the successful script - let completed_job = RunJob::from(JobPayload::ScriptHash { - hash: ScriptHash(success_script_hash), - path: "f/test/success_script".to_string(), - cache_ttl: None, - cache_ignore_s3_path: None, - dedicated_worker: None, - language: ScriptLang::Bun, - priority: None, - apply_preprocessor: false, - concurrency_settings: ConcurrencySettings::default(), - debouncing_settings: DebouncingSettings::default(), - labels: None, - job_token_scopes: None, - }) - .run_until_complete(&db, false, server.addr.port()) - .await; - - assert!(completed_job.success, "Job should have succeeded"); - - // Wait and check that NO error handler job was created - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - - let error_handler_job = sqlx::query_scalar!( - r#" - SELECT id - FROM v2_job - WHERE workspace_id = 'test-workspace' - AND permissioned_as_email = 'error_handler@windmill.dev' - "# - ) - .fetch_optional(&db) - .await?; - - assert!( - error_handler_job.is_none(), - "Error handler should NOT have been triggered for a successful job" - ); - - Ok(()) -} - -/// A hub script run at the top level (a schedule, a trigger, the API) is a `script_hub` job, -/// and its failure must reach the workspace error handler like a workspace script's. -#[cfg(all(feature = "enterprise", feature = "private"))] -#[sqlx::test(fixtures("base"))] -async fn test_error_handler_triggered_on_hub_script_failure( - db: Pool, -) -> anyhow::Result<()> { - use windmill_common::jobs::JobPayload; - - initialize_tracing().await; - - let server = ApiServer::start(db.clone()).await?; - - let version = "990000002"; - let hub_dir = &*windmill_common::worker::HUB_CACHE_DIR; - tokio::fs::create_dir_all(hub_dir).await?; - tokio::fs::write( - format!("{hub_dir}/{version}"), - r#"{"content":"exit 1","lockfile":null,"language":"bash","schema":{},"summary":null}"#, - ) - .await?; - - // The workspace handler settings are cached per workspace id across the tests of this - // binary, so this reuses the handler path every other test here configures. - sqlx::query( - "INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', 1111111112, 'f/test/error_handler', 'echo handled', 'bash', 'script', 'test-user', '{}', '', '', '')", - ) - .execute(&db) - .await?; - sqlx::query( - r#"UPDATE workspace_settings SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb - WHERE workspace_id = 'test-workspace'"#, - ) - .execute(&db) - .await?; - - let completed_job = RunJob::from(JobPayload::ScriptHub { - path: format!("hub/{version}/test/fail"), - apply_preprocessor: false, - }) - .run_until_complete(&db, false, server.addr.port()) - .await; - assert!(!completed_job.success, "Hub script should have failed"); - - let mut handler_parent = None; - for _ in 0..50 { - handler_parent = sqlx::query_scalar::<_, Option>( - "SELECT parent_job FROM v2_job - WHERE workspace_id = 'test-workspace' AND permissioned_as_email = 'error_handler@windmill.dev'", - ) - .fetch_optional(&db) - .await?; - if handler_parent.is_some() { - break; - } - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - } - assert_eq!( - handler_parent, - Some(Some(completed_job.id)), - "Workspace error handler should handle the failed hub script" - ); - - Ok(()) -} diff --git a/backend/tests/success_handler.rs b/backend/tests/success_handler.rs deleted file mode 100644 index 14df1a4c25..0000000000 --- a/backend/tests/success_handler.rs +++ /dev/null @@ -1,293 +0,0 @@ -use sqlx::{Pool, Postgres}; - -use windmill_test_utils::*; - -/// Both job-running tests seed `f/test/success_handler` with a different hash, each in its own -/// database, while the deployed-script caches are process-global and keyed by workspace and -/// path. With them on, one test resolves the handler to the other's hash and the handler push -/// 404s whenever that hash's info is not cached yet. -#[cfg(all(feature = "enterprise", feature = "private"))] -fn disable_deployed_script_cache() { - windmill_common::DEPLOYED_SCRIPT_CACHE_DISABLED - .store(true, std::sync::atomic::Ordering::Relaxed); -} - -/// The handler job is pushed after the job it handles is reported complete. -#[cfg(all(feature = "enterprise", feature = "private"))] -async fn wait_for_success_handler_job( - db: &Pool, -) -> anyhow::Result, Option)>> { - for _ in 0..100 { - let job = sqlx::query_as::<_, (Option, Option)>( - "SELECT runnable_path, parent_job FROM v2_job - WHERE workspace_id = 'test-workspace' AND permissioned_as_email = 'success_handler@windmill.dev'", - ) - .fetch_optional(db) - .await?; - if job.is_some() { - return Ok(job); - } - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - } - Ok(None) -} - -/// Test that the workspace success handler cache works correctly with 60s TTL -#[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base"))] -async fn test_success_handler_cache(db: Pool) -> anyhow::Result<()> { - initialize_tracing().await; - - // First, create a success handler script - let _server = ApiServer::start(db.clone()).await?; - - // Set up a success handler in workspace_settings using new JSONB column - sqlx::query!( - r#" - UPDATE workspace_settings - SET success_handler = '{"path": "script/f/test/success_handler", "extra_args": {"key": "value"}}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - // Verify the success handler was set - let result = sqlx::query_scalar!( - r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - - assert_eq!(result, Some("script/f/test/success_handler".to_string())); - - // Verify extra args were set - let extra_args = sqlx::query_scalar!( - r#"SELECT success_handler->'extra_args' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - - assert!(extra_args.is_some()); - - Ok(()) -} - -/// Test that success handler can be set and removed via database operations -#[cfg(feature = "deno_core")] -#[sqlx::test(fixtures("base"))] -async fn test_success_handler_settings(db: Pool) -> anyhow::Result<()> { - initialize_tracing().await; - - let _server = ApiServer::start(db.clone()).await?; - - // Initially success_handler should be NULL - let initial = sqlx::query_scalar!( - r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert!(initial.is_none()); - - // Set success handler - sqlx::query!( - r#" - UPDATE workspace_settings - SET success_handler = '{"path": "flow/f/test/success_flow"}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - let after_set = sqlx::query_scalar!( - r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert_eq!(after_set, Some("flow/f/test/success_flow".to_string())); - - // Remove success handler - sqlx::query!( - r#" - UPDATE workspace_settings - SET success_handler = NULL - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - let after_remove = sqlx::query_scalar!( - r#"SELECT success_handler->>'path' FROM workspace_settings WHERE workspace_id = 'test-workspace'"# - ) - .fetch_one(&db) - .await?; - assert!(after_remove.is_none()); - - Ok(()) -} - -/// Test that a successful job completion triggers the success handler -#[cfg(all(feature = "deno_core", feature = "enterprise", feature = "private"))] -#[sqlx::test(fixtures("base"))] -async fn test_success_handler_triggered_on_success(db: Pool) -> anyhow::Result<()> { - use serde_json::json; - use windmill_common::jobs::JobPayload; - use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings}; - use windmill_common::scripts::{ScriptHash, ScriptLang}; - - initialize_tracing().await; - disable_deployed_script_cache(); - - let server = ApiServer::start(db.clone()).await?; - - // Create a simple success handler script - // Note: lock must be non-null for script to be considered "deployed" - let success_handler_code = r#" -export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, result: any) { - console.log("Success handler called for job:", job_id); - return { handled: true, original_path: path }; -} -"#; - - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', 1234567890, 'f/test/success_handler', $1, 'bun', 'script', 'test-user', '{}', 'Success handler script', 'Handles successful job completions', E'{}\n//bun.lock\n') - "#, - success_handler_code - ) - .execute(&db) - .await?; - - // Create a simple test script that we'll run (needs to be JobKind::Script, not Preview) - // Note: lock must be non-null for script to be considered "deployed" - let test_script_code = "export function main() { return 'success'; }"; - let test_script_hash: i64 = 9876543210; - - sqlx::query!( - r#" - INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', $1, 'f/test/simple_script', $2, 'bun', 'script', 'test-user', '{}', 'Simple test script', 'A simple test script', E'{}\n//bun.lock\n') - "#, - test_script_hash, - test_script_code - ) - .execute(&db) - .await?; - - // Set up the success handler in workspace_settings - sqlx::query!( - r#" - UPDATE workspace_settings - SET success_handler = '{"path": "script/f/test/success_handler"}'::jsonb - WHERE workspace_id = 'test-workspace' - "# - ) - .execute(&db) - .await?; - - // Create the success_handler group - sqlx::query!( - r#" - INSERT INTO group_ (workspace_id, name, summary, extra_perms) - VALUES ('test-workspace', 'success_handler', 'The group the success handler acts on behalf of', '{"u/test-user": true}') - ON CONFLICT DO NOTHING - "# - ) - .execute(&db) - .await?; - - // Run a script using ScriptHash (produces JobKind::Script, not Preview) - let completed_job = RunJob::from(JobPayload::ScriptHash { - hash: ScriptHash(test_script_hash), - path: "f/test/simple_script".to_string(), - cache_ttl: None, - cache_ignore_s3_path: None, - dedicated_worker: None, - language: ScriptLang::Bun, - priority: None, - apply_preprocessor: false, - concurrency_settings: ConcurrencySettings::default(), - debouncing_settings: DebouncingSettings::default(), - labels: None, - job_token_scopes: None, - }) - .run_until_complete(&db, false, server.addr.port()) - .await; - - let result = completed_job.json_result().unwrap(); - assert_eq!(result, json!("success")); - - let main_job_id = completed_job.id; - - let handler_job = wait_for_success_handler_job(&db).await?; - assert_eq!( - handler_job, - Some(( - Some("f/test/success_handler".to_string()), - Some(main_job_id) - )), - "Success handler should run the configured script with the main job as parent" - ); - // Note: root_job may be None when it equals parent_job (optimization in push function) - - Ok(()) -} - -/// A hub script run at the top level (a schedule, a trigger, the API) is a `script_hub` job, -/// and its success must reach the workspace success handler like a workspace script's. -#[cfg(all(feature = "enterprise", feature = "private"))] -#[sqlx::test(fixtures("base"))] -async fn test_success_handler_triggered_on_hub_script_success( - db: Pool, -) -> anyhow::Result<()> { - use windmill_common::jobs::JobPayload; - - initialize_tracing().await; - disable_deployed_script_cache(); - - let server = ApiServer::start(db.clone()).await?; - - let version = "990000003"; - let hub_dir = &*windmill_common::worker::HUB_CACHE_DIR; - tokio::fs::create_dir_all(hub_dir).await?; - tokio::fs::write( - format!("{hub_dir}/{version}"), - r#"{"content":"echo ok","lockfile":null,"language":"bash","schema":{},"summary":null}"#, - ) - .await?; - - // The workspace handler settings are cached per workspace id across the tests of this - // binary, so this reuses the handler path every other test here configures. - sqlx::query( - "INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) - VALUES ('test-workspace', 1234567891, 'f/test/success_handler', 'echo handled', 'bash', 'script', 'test-user', '{}', '', '', '')", - ) - .execute(&db) - .await?; - sqlx::query( - r#"UPDATE workspace_settings SET success_handler = '{"path": "script/f/test/success_handler"}'::jsonb - WHERE workspace_id = 'test-workspace'"#, - ) - .execute(&db) - .await?; - - let completed_job = RunJob::from(JobPayload::ScriptHub { - path: format!("hub/{version}/test/ok"), - apply_preprocessor: false, - }) - .run_until_complete(&db, false, server.addr.port()) - .await; - assert!(completed_job.success, "Hub script should have succeeded"); - - let handler_job = wait_for_success_handler_job(&db).await?; - assert_eq!( - handler_job.map(|(_, parent)| parent), - Some(Some(completed_job.id)), - "Workspace success handler should handle the successful hub script" - ); - - Ok(()) -} diff --git a/backend/tests/workspace_handlers.rs b/backend/tests/workspace_handlers.rs new file mode 100644 index 0000000000..0ad1e044bd --- /dev/null +++ b/backend/tests/workspace_handlers.rs @@ -0,0 +1,422 @@ +#![cfg(all(feature = "enterprise", feature = "private"))] + +use sqlx::{Pool, Postgres}; + +use windmill_test_utils::*; + +const ERROR_HANDLER_EMAIL: &str = "error_handler@windmill.dev"; +const SUCCESS_HANDLER_EMAIL: &str = "success_handler@windmill.dev"; + +/// Every test seeds `f/test/error_handler` or `f/test/success_handler` with a different hash, +/// each in its own database, while the deployed-script caches are process-global and keyed by +/// workspace and path. With them on, one test resolves the handler to another's hash and the +/// handler push 404s whenever that hash's info is not cached yet. +fn disable_deployed_script_cache() { + windmill_common::DEPLOYED_SCRIPT_CACHE_DISABLED + .store(true, std::sync::atomic::Ordering::Relaxed); +} + +/// Polls so the assertion does not depend on the handler being pushed before +/// `run_until_complete` returns, which only holds while the worker drains its completions on exit. +async fn wait_for_handler_job( + db: &Pool, + handler_email: &str, +) -> anyhow::Result, Option)>> { + for _ in 0..100 { + let job = sqlx::query_as::<_, (Option, Option)>( + "SELECT runnable_path, parent_job FROM v2_job + WHERE workspace_id = 'test-workspace' AND permissioned_as_email = $1", + ) + .bind(handler_email) + .fetch_optional(db) + .await?; + if job.is_some() { + return Ok(job); + } + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + } + Ok(None) +} + +#[cfg(feature = "deno_core")] +async fn insert_bun_script( + db: &Pool, + hash: i64, + path: &str, + content: &str, + ws_error_handler_muted: bool, +) -> anyhow::Result<()> { + // The lock must be non-null for the script to count as deployed. + sqlx::query( + r#"INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock, ws_error_handler_muted) + VALUES ('test-workspace', $1, $2, $3, 'bun', 'script', 'test-user', '{}', '', '', E'{}\n//bun.lock\n', $4)"#, + ) + .bind(hash) + .bind(path) + .bind(content) + .bind(ws_error_handler_muted) + .execute(db) + .await?; + Ok(()) +} + +/// Configures `f/test/error_handler` and `f/test/success_handler` as the workspace handlers. +/// Both settings are cached per workspace id across the tests of this binary, and any test's +/// top-level job can fill either cache, so every test configures both, with the same paths. +async fn configure_workspace_handlers(db: &Pool) -> anyhow::Result<()> { + sqlx::query( + r#"UPDATE workspace_settings + SET error_handler = '{"path": "script/f/test/error_handler"}'::jsonb, + success_handler = '{"path": "script/f/test/success_handler"}'::jsonb + WHERE workspace_id = 'test-workspace'"#, + ) + .execute(db) + .await?; + sqlx::query( + r#"INSERT INTO group_ (workspace_id, name, summary, extra_perms) + VALUES ('test-workspace', 'error_handler', '', '{"u/test-user": true}'), + ('test-workspace', 'success_handler', '', '{"u/test-user": true}') + ON CONFLICT DO NOTHING"#, + ) + .execute(db) + .await?; + Ok(()) +} + +/// Runs a deployed script by hash, which makes a `script` job rather than a preview. +#[cfg(feature = "deno_core")] +async fn run_script_hash(db: &Pool, port: u16, hash: i64, path: &str) -> CompletedJob { + use windmill_common::jobs::JobPayload; + use windmill_common::runnable_settings::{ConcurrencySettings, DebouncingSettings}; + use windmill_common::scripts::{ScriptHash, ScriptLang}; + + RunJob::from(JobPayload::ScriptHash { + hash: ScriptHash(hash), + path: path.to_string(), + cache_ttl: None, + cache_ignore_s3_path: None, + dedicated_worker: None, + language: ScriptLang::Bun, + priority: None, + apply_preprocessor: false, + concurrency_settings: ConcurrencySettings::default(), + debouncing_settings: DebouncingSettings::default(), + labels: None, + job_token_scopes: None, + }) + .run_until_complete(db, false, port) + .await +} + +/// A job's handler, if any, is pushed before the worker that ran it exits, so once a control +/// job has been handled, every handler job that the jobs run before it would produce exists. +#[cfg(feature = "deno_core")] +async fn assert_only_handled( + db: &Pool, + control_job: uuid::Uuid, + message: &str, +) -> anyhow::Result<()> { + assert!( + wait_for_handler_job(db, ERROR_HANDLER_EMAIL) + .await? + .is_some(), + "Error handler should have handled the control failure" + ); + let handled = sqlx::query_scalar::<_, Option>( + "SELECT parent_job FROM v2_job + WHERE workspace_id = 'test-workspace' AND permissioned_as_email = $1", + ) + .bind(ERROR_HANDLER_EMAIL) + .fetch_all(db) + .await?; + assert_eq!(handled, vec![Some(control_job)], "{message}"); + Ok(()) +} + +/// Test that a failed job triggers the workspace error handler +#[cfg(feature = "deno_core")] +#[sqlx::test(fixtures("base"))] +async fn test_error_handler_triggered_on_failure(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + disable_deployed_script_cache(); + + let server = ApiServer::start(db.clone()).await?; + + insert_bun_script( + &db, + 1111111111, + "f/test/error_handler", + r#" +export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, error: any) { + console.log("Error handler called for job:", job_id); + return { handled: true, original_path: path }; +} +"#, + false, + ) + .await?; + insert_bun_script( + &db, + 2222222222, + "f/test/failing_script", + "export function main() { throw new Error('intentional failure'); }", + false, + ) + .await?; + configure_workspace_handlers(&db).await?; + + let completed_job = + run_script_hash(&db, server.addr.port(), 2222222222, "f/test/failing_script").await; + assert!(!completed_job.success, "Job should have failed"); + + assert_eq!( + wait_for_handler_job(&db, ERROR_HANDLER_EMAIL).await?, + Some(( + Some("f/test/error_handler".to_string()), + Some(completed_job.id) + )), + "Error handler should run the configured script with the failed job as parent" + ); + + Ok(()) +} + +/// Test that error handler is NOT triggered when ws_error_handler_muted is set on the script +#[cfg(feature = "deno_core")] +#[sqlx::test(fixtures("base"))] +async fn test_error_handler_muted_on_script(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + disable_deployed_script_cache(); + + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + insert_bun_script( + &db, + 3333333333, + "f/test/error_handler", + r#"export function main() { return "handled"; }"#, + false, + ) + .await?; + let failing = r#"export function main() { throw new Error("fail"); }"#; + insert_bun_script( + &db, + 4444444444, + "f/test/muted_failing_script", + failing, + true, + ) + .await?; + insert_bun_script(&db, 4444444445, "f/test/failing_script", failing, false).await?; + configure_workspace_handlers(&db).await?; + + let muted_job = run_script_hash(&db, port, 4444444444, "f/test/muted_failing_script").await; + assert!(!muted_job.success, "Job should have failed"); + + let control_job = run_script_hash(&db, port, 4444444445, "f/test/failing_script").await; + assert!(!control_job.success, "Job should have failed"); + + assert_only_handled( + &db, + control_job.id, + "Error handler should NOT have been triggered for a muted script", + ) + .await +} + +/// Test that error handler is NOT triggered on successful job completion +#[cfg(feature = "deno_core")] +#[sqlx::test(fixtures("base"))] +async fn test_error_handler_not_triggered_on_success(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + disable_deployed_script_cache(); + + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + + insert_bun_script( + &db, + 5555555555, + "f/test/error_handler", + r#"export function main() { return "handled"; }"#, + false, + ) + .await?; + insert_bun_script( + &db, + 6666666666, + "f/test/success_script", + r#"export function main() { return "ok"; }"#, + false, + ) + .await?; + insert_bun_script( + &db, + 6666666667, + "f/test/failing_script", + r#"export function main() { throw new Error("fail"); }"#, + false, + ) + .await?; + configure_workspace_handlers(&db).await?; + + let success_job = run_script_hash(&db, port, 6666666666, "f/test/success_script").await; + assert!(success_job.success, "Job should have succeeded"); + + let control_job = run_script_hash(&db, port, 6666666667, "f/test/failing_script").await; + assert!(!control_job.success, "Job should have failed"); + + assert_only_handled( + &db, + control_job.id, + "Error handler should NOT have been triggered for a successful job", + ) + .await +} + +/// A hub script run at the top level (a schedule, a trigger, the API) is a `script_hub` job, +/// and its failure must reach the workspace error handler like a workspace script's. +#[sqlx::test(fixtures("base"))] +async fn test_error_handler_triggered_on_hub_script_failure( + db: Pool, +) -> anyhow::Result<()> { + use windmill_common::jobs::JobPayload; + + initialize_tracing().await; + disable_deployed_script_cache(); + + let server = ApiServer::start(db.clone()).await?; + + let version = "990000002"; + let hub_dir = &*windmill_common::worker::HUB_CACHE_DIR; + tokio::fs::create_dir_all(hub_dir).await?; + tokio::fs::write( + format!("{hub_dir}/{version}"), + r#"{"content":"exit 1","lockfile":null,"language":"bash","schema":{},"summary":null}"#, + ) + .await?; + + sqlx::query( + "INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) + VALUES ('test-workspace', 1111111112, 'f/test/error_handler', 'echo handled', 'bash', 'script', 'test-user', '{}', '', '', '')", + ) + .execute(&db) + .await?; + configure_workspace_handlers(&db).await?; + + let completed_job = RunJob::from(JobPayload::ScriptHub { + path: format!("hub/{version}/test/fail"), + apply_preprocessor: false, + }) + .run_until_complete(&db, false, server.addr.port()) + .await; + assert!(!completed_job.success, "Hub script should have failed"); + + let handler_job = wait_for_handler_job(&db, ERROR_HANDLER_EMAIL).await?; + assert_eq!( + handler_job.map(|(_, parent)| parent), + Some(Some(completed_job.id)), + "Workspace error handler should handle the failed hub script" + ); + + Ok(()) +} + +/// Test that a successful job completion triggers the success handler +#[cfg(feature = "deno_core")] +#[sqlx::test(fixtures("base"))] +async fn test_success_handler_triggered_on_success(db: Pool) -> anyhow::Result<()> { + use serde_json::json; + + initialize_tracing().await; + disable_deployed_script_cache(); + + let server = ApiServer::start(db.clone()).await?; + + insert_bun_script( + &db, + 1234567890, + "f/test/success_handler", + r#" +export async function main(path: string, email: string, job_id: string, is_flow: boolean, workspace_id: string, result: any) { + console.log("Success handler called for job:", job_id); + return { handled: true, original_path: path }; +} +"#, + false, + ) + .await?; + insert_bun_script( + &db, + 9876543210, + "f/test/simple_script", + "export function main() { return 'success'; }", + false, + ) + .await?; + configure_workspace_handlers(&db).await?; + + let completed_job = + run_script_hash(&db, server.addr.port(), 9876543210, "f/test/simple_script").await; + assert_eq!(completed_job.json_result().unwrap(), json!("success")); + + assert_eq!( + wait_for_handler_job(&db, SUCCESS_HANDLER_EMAIL).await?, + Some(( + Some("f/test/success_handler".to_string()), + Some(completed_job.id) + )), + "Success handler should run the configured script with the main job as parent" + ); + + Ok(()) +} + +/// A hub script run at the top level (a schedule, a trigger, the API) is a `script_hub` job, +/// and its success must reach the workspace success handler like a workspace script's. +#[sqlx::test(fixtures("base"))] +async fn test_success_handler_triggered_on_hub_script_success( + db: Pool, +) -> anyhow::Result<()> { + use windmill_common::jobs::JobPayload; + + initialize_tracing().await; + disable_deployed_script_cache(); + + let server = ApiServer::start(db.clone()).await?; + + let version = "990000003"; + let hub_dir = &*windmill_common::worker::HUB_CACHE_DIR; + tokio::fs::create_dir_all(hub_dir).await?; + tokio::fs::write( + format!("{hub_dir}/{version}"), + r#"{"content":"echo ok","lockfile":null,"language":"bash","schema":{},"summary":null}"#, + ) + .await?; + + sqlx::query( + "INSERT INTO script (workspace_id, hash, path, content, language, kind, created_by, schema, summary, description, lock) + VALUES ('test-workspace', 1234567891, 'f/test/success_handler', 'echo handled', 'bash', 'script', 'test-user', '{}', '', '', '')", + ) + .execute(&db) + .await?; + configure_workspace_handlers(&db).await?; + + let completed_job = RunJob::from(JobPayload::ScriptHub { + path: format!("hub/{version}/test/ok"), + apply_preprocessor: false, + }) + .run_until_complete(&db, false, server.addr.port()) + .await; + assert!(completed_job.success, "Hub script should have succeeded"); + + let handler_job = wait_for_handler_job(&db, SUCCESS_HANDLER_EMAIL).await?; + assert_eq!( + handler_job.map(|(_, parent)| parent), + Some(Some(completed_job.id)), + "Workspace success handler should handle the successful hub script" + ); + + Ok(()) +}