From dbcb5db1558b2db4eae837bbc4a5437a2d7cb51c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 8 Feb 2026 22:20:34 +0000 Subject: [PATCH] test: add global endpoint coverage for users, groups, schedules, and more Co-Authored-By: Claude Opus 4.6 --- backend/tests/flows.rs | 11 +++ backend/tests/folders.rs | 10 +++ backend/tests/groups.rs | 145 ++++++++++++++++++++++++++++++ backend/tests/resources.rs | 10 +++ backend/tests/schedules.rs | 15 ++++ backend/tests/scripts.rs | 4 + backend/tests/users.rs | 172 ++++++++++++++++++++++++++++++++++++ backend/tests/workspaces.rs | 9 ++ 8 files changed, 376 insertions(+) diff --git a/backend/tests/flows.rs b/backend/tests/flows.rs index 69ed656f7e..d63f9ffeab 100644 --- a/backend/tests/flows.rs +++ b/backend/tests/flows.rs @@ -206,6 +206,17 @@ async fn test_flow_endpoints(db: Pool) -> anyhow::Result<()> { .unwrap(); assert_eq!(resp.status(), 200, "history_update: {}", resp.text().await?); + // --- get_triggers_count --- + let resp = authed(client().get(flow_url( + port, + "get_triggers_count", + "u/test-user/test_flow", + ))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + // --- toggle_workspace_error_handler (EE-gated, expect 400 in OSS) --- let resp = authed(client().post(flow_url( port, diff --git a/backend/tests/folders.rs b/backend/tests/folders.rs index ee27a0e5c5..075eea69eb 100644 --- a/backend/tests/folders.rs +++ b/backend/tests/folders.rs @@ -140,6 +140,16 @@ async fn test_folder_endpoints(db: Pool) -> anyhow::Result<()> { .unwrap(); assert_eq!(resp.status(), 200); + // --- is_owner --- + let resp = authed(client().get(format!( + "{base}/is_owner/f/test_folder" + ))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + resp.json::().await?; + // --- delete --- let resp = authed(client().delete(folder_url(port, "delete", "another_folder"))) .send() diff --git a/backend/tests/groups.rs b/backend/tests/groups.rs index 0fb37fdec4..453a15916e 100644 --- a/backend/tests/groups.rs +++ b/backend/tests/groups.rs @@ -151,5 +151,150 @@ async fn test_group_endpoints(db: Pool) -> anyhow::Result<()> { let names = resp.json::>().await?; assert!(!names.contains(&"another_group".to_string())); + // ===== Global (instance group) endpoints ===== + let global_base = format!("http://localhost:{port}/api/groups"); + + // --- create instance group --- + let resp = authed(client().post(format!("{global_base}/create"))) + .json(&json!({"name": "test_igroup", "summary": "Test instance group"})) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "create igroup: {}", + resp.text().await? + ); + + // --- list instance groups --- + let resp = authed(client().get(format!("{global_base}/list"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let list = resp.json::>().await?; + assert!(list.iter().any(|g| g["name"] == "test_igroup")); + + // --- list_with_workspaces --- + let resp = authed(client().get(format!("{global_base}/list_with_workspaces"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + resp.json::>().await?; + + // --- get instance group --- + let resp = authed(client().get(format!("{global_base}/get/test_igroup"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let body = resp.json::().await?; + assert_eq!(body["name"], "test_igroup"); + assert_eq!(body["summary"], "Test instance group"); + + // --- update instance group --- + let resp = authed(client().post(format!("{global_base}/update/test_igroup"))) + .json(&json!({"new_summary": "Updated instance group"})) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "update igroup: {}", + resp.text().await? + ); + + // verify update + let resp = authed(client().get(format!("{global_base}/get/test_igroup"))) + .send() + .await + .unwrap(); + let body = resp.json::().await?; + assert_eq!(body["summary"], "Updated instance group"); + + // --- adduser to instance group --- + let resp = authed(client().post(format!("{global_base}/adduser/test_igroup"))) + .json(&json!({"email": "test@windmill.dev"})) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "adduser igroup: {}", + resp.text().await? + ); + + // verify membership + let resp = authed(client().get(format!("{global_base}/get/test_igroup"))) + .send() + .await + .unwrap(); + let body = resp.json::().await?; + let emails = body["emails"].as_array().unwrap(); + assert!( + emails + .iter() + .any(|e| e.as_str() == Some("test@windmill.dev")), + "expected test@windmill.dev in emails, got: {:?}", + emails + ); + + // --- removeuser from instance group --- + let resp = authed(client().post(format!( + "{global_base}/removeuser/test_igroup" + ))) + .json(&json!({"email": "test@windmill.dev"})) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + + // --- export (EE-gated) --- + let resp = authed(client().get(format!("{global_base}/export"))) + .send() + .await + .unwrap(); + assert!( + resp.status() == 200 || resp.status() == 400, + "export igroups: unexpected status {}", + resp.status() + ); + + // --- overwrite (EE-gated) --- + let resp = authed(client().post(format!("{global_base}/overwrite"))) + .json(&json!([])) + .send() + .await + .unwrap(); + assert!( + resp.status() == 200 || resp.status() == 400, + "overwrite igroups: unexpected status {}", + resp.status() + ); + + // --- delete instance group --- + let resp = authed(client().delete(format!("{global_base}/delete/test_igroup"))) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "delete igroup: {}", + resp.text().await? + ); + + // verify deleted + let resp = authed(client().get(format!("{global_base}/list"))) + .send() + .await + .unwrap(); + let list = resp.json::>().await?; + assert!(!list.iter().any(|g| g["name"] == "test_igroup")); + Ok(()) } diff --git a/backend/tests/resources.rs b/backend/tests/resources.rs index d44d21b4ed..a81daf029e 100644 --- a/backend/tests/resources.rs +++ b/backend/tests/resources.rs @@ -296,6 +296,16 @@ async fn test_resource_endpoints(db: Pool) -> anyhow::Result<()> { .unwrap(); assert_eq!(resp.status(), 404); + // --- file_resource_type_to_file_ext_map --- + let resp = authed(client().get(format!( + "{base}/file_resource_type_to_file_ext_map" + ))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + resp.json::().await?; + // --- resource types --- // type/exists diff --git a/backend/tests/schedules.rs b/backend/tests/schedules.rs index b437b2ce44..ee66264c5f 100644 --- a/backend/tests/schedules.rs +++ b/backend/tests/schedules.rs @@ -218,5 +218,20 @@ async fn test_schedule_endpoints(db: Pool) -> anyhow::Result<()> { let resp = authed_get(port, "exists", "u/test-user/another_schedule").await; assert_eq!(resp.json::().await?, false); + // ===== Global endpoints ===== + + // --- preview --- + let resp = authed(client().post(format!( + "http://localhost:{port}/api/schedules/preview" + ))) + .json(&json!({ + "schedule": "0 0 */6 * * *", + "timezone": "UTC" + })) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200, "preview: {}", resp.text().await?); + Ok(()) } diff --git a/backend/tests/scripts.rs b/backend/tests/scripts.rs index 8166c66329..3825980350 100644 --- a/backend/tests/scripts.rs +++ b/backend/tests/scripts.rs @@ -257,6 +257,10 @@ async fn test_script_endpoints(db: Pool) -> anyhow::Result<()> { resp.status() ); + // --- get_triggers_count --- + let resp = authed_get(port, "get_triggers_count", "u/test-user/test_script").await; + assert_eq!(resp.status(), 200); + // --- tokened_raw (global unauthed, token in URL) --- let resp = client() .get(format!( diff --git a/backend/tests/users.rs b/backend/tests/users.rs index cb7d164004..79307d9f1e 100644 --- a/backend/tests/users.rs +++ b/backend/tests/users.rs @@ -113,6 +113,178 @@ async fn test_user_endpoints(db: Pool) -> anyhow::Result<()> { assert_eq!(resp.status(), 200); resp.json::>().await?; + // --- username_info --- + let resp = authed(client().get(format!( + "{global_base}/username_info/test@windmill.dev" + ))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let body = resp.json::().await?; + assert_eq!(body["username"], "test-user"); + + // --- global usage --- + let resp = authed(client().get(format!("{global_base}/usage"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + + // --- tutorial_progress (get, then set, then get again) --- + let resp = authed(client().get(format!("{global_base}/tutorial_progress"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + resp.json::().await?; + + let resp = authed(client().post(format!("{global_base}/tutorial_progress"))) + .json(&json!({"progress": 42, "skipped_all": false})) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "set tutorial_progress: {}", + resp.text().await? + ); + + let resp = authed(client().get(format!("{global_base}/tutorial_progress"))) + .send() + .await + .unwrap(); + let body = resp.json::().await?; + assert_eq!(body["progress"], 42); + + // --- global update user --- + let resp = authed(client().post(format!( + "{global_base}/update/test2@windmill.dev" + ))) + .json(&json!({"name": "Updated Test User 2"})) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "global update user: {}", + resp.text().await? + ); + + // --- setpassword (EE-gated in OSS) --- + let resp = authed(client().post(format!("{global_base}/setpassword"))) + .json(&json!({"password": "new-test-password-123"})) + .send() + .await + .unwrap(); + assert!( + resp.status() == 200 || resp.status() == 500, + "setpassword: unexpected status {}", + resp.status() + ); + + // --- all_runnables --- + let resp = authed(client().get(format!("{global_base}/all_runnables"))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + resp.json::>().await?; + + // --- onboarding (EE-gated in OSS) --- + let resp = authed(client().post(format!("{global_base}/onboarding"))) + .json(&json!({"touch_point": "test", "use_case": "testing"})) + .send() + .await + .unwrap(); + assert!( + resp.status() == 200 || resp.status() == 500, + "onboarding: unexpected status {}", + resp.status() + ); + + // --- decline_invite (no pending invite, but endpoint should handle gracefully) --- + let resp = authed(client().post(format!("{global_base}/decline_invite"))) + .json(&json!({"workspace_id": "nonexistent-ws"})) + .send() + .await + .unwrap(); + assert!( + resp.status() == 200 || resp.status() == 404, + "decline_invite: unexpected status {}", + resp.status() + ); + + // --- auth: is_first_time_setup (unauthed) --- + let resp = client() + .get(format!("http://localhost:{port}/api/auth/is_first_time_setup")) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + let is_first = resp.json::().await?; + assert_eq!(is_first, false); + + // --- auth: is_smtp_configured (unauthed) --- + let resp = client() + .get(format!("http://localhost:{port}/api/auth/is_smtp_configured")) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + resp.json::().await?; + + // --- create user (global, EE-gated in OSS) --- + let resp = authed(client().post(format!("{global_base}/create"))) + .json(&json!({ + "email": "newglobaluser@windmill.dev", + "password": "test-password-123", + "super_admin": false, + "name": "New Global User" + })) + .send() + .await + .unwrap(); + let create_status = resp.status(); + assert!( + create_status == 201 || create_status == 500, + "create user: unexpected status {}", + create_status + ); + + if create_status == 201 { + // --- rename user (only if create succeeded / EE) --- + let resp = authed(client().post(format!( + "{global_base}/rename/newglobaluser@windmill.dev" + ))) + .json(&json!({"new_username": "renamed-user"})) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "rename user: {}", + resp.text().await? + ); + + // --- global delete user --- + let resp = authed(client().delete(format!( + "{global_base}/delete/newglobaluser@windmill.dev" + ))) + .send() + .await + .unwrap(); + assert_eq!( + resp.status(), + 200, + "global delete user: {}", + resp.text().await? + ); + } + // ===== Workspace-scoped endpoints ===== // --- whoami --- diff --git a/backend/tests/workspaces.rs b/backend/tests/workspaces.rs index 0b70c1955a..c5ce424514 100644 --- a/backend/tests/workspaces.rs +++ b/backend/tests/workspaces.rs @@ -587,5 +587,14 @@ async fn test_workspace_endpoints(db: Pool) -> anyhow::Result<()> { .unwrap(); assert_eq!(resp.json::().await?, false); + // --- create_workspace_require_superadmin --- + let resp = authed(client().get(format!( + "{global_base}/create_workspace_require_superadmin" + ))) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 200); + Ok(()) }