From d877209644a3d42d9ea98fcea6c5e5408fcd9bbe Mon Sep 17 00:00:00 2001 From: clockwork-labs-bot Date: Thu, 9 Apr 2026 12:35:14 -0400 Subject: [PATCH 1/6] Require confirmation for spacetime delete --- crates/cli/src/subcommands/delete.rs | 16 ++++- .../tests/smoketests/delete_database.rs | 58 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/subcommands/delete.rs b/crates/cli/src/subcommands/delete.rs index 2f91e3ab30f..a7b9154a13a 100644 --- a/crates/cli/src/subcommands/delete.rs +++ b/crates/cli/src/subcommands/delete.rs @@ -45,6 +45,20 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let force = args.get_flag("force"); let identity = database_identity(&config, &resolved.database, server).await?; + let delete_target = if resolved.database == identity.to_string() { + identity.to_string() + } else { + format!("{} ({identity})", resolved.database) + }; + + if !y_or_n( + force, + &format!("Are you sure you want to delete database {delete_target}? This action cannot be undone."), + )? { + println!("Aborting"); + return Ok(()); + } + let host_url = config.get_host_url(server)?; let request_path = format!("{host_url}/v1/database/{identity}"); let auth_header = get_auth_header(&mut config, false, server, !force).await?; @@ -58,7 +72,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E if !force { print_database_tree_info(&confirm.database_tree).await?; } - if y_or_n(force, "Do you want to proceed deleting above databases?")? { + if force || y_or_n(false, "Do you want to proceed deleting above databases?")? { send_request(&client, &request_path, &auth_header, Some(confirm.confirmation_token)) .await? .error_for_status()?; diff --git a/crates/smoketests/tests/smoketests/delete_database.rs b/crates/smoketests/tests/smoketests/delete_database.rs index c101304563f..e55152fb5d1 100644 --- a/crates/smoketests/tests/smoketests/delete_database.rs +++ b/crates/smoketests/tests/smoketests/delete_database.rs @@ -2,11 +2,41 @@ use spacetimedb_smoketests::Smoketest; use std::thread; use std::time::Duration; +fn assert_delete_prompt(output: &str, database: &str) { + assert!( + output.contains("Are you sure you want to delete database"), + "expected confirmation prompt in output:\n{output}" + ); + assert!( + output.contains(database), + "expected database name in confirmation prompt:\n{output}" + ); +} + +#[test] +fn test_delete_database_aborts_without_confirmation() { + let mut test = Smoketest::builder() + .precompiled_module("delete-database") + .autopublish(false) + .build(); + + let name = format!("test-db-{}", std::process::id()); + test.publish_module_named(&name, false).unwrap(); + + let output = test + .spacetime_with_stdin(&["delete", "--server", &test.server_url, &name], "n\n") + .unwrap(); + assert_delete_prompt(&output, &name); + assert!(output.contains("Aborting"), "expected abort message:\n{output}"); + + test.spacetime(&["logs", "--server", &test.server_url, &name]).unwrap(); +} + /// Test that deleting a database stops the module. /// The module is considered stopped if its scheduled reducer stops /// producing update events. #[test] -fn test_delete_database() { +fn test_delete_database_with_confirmation() { let mut test = Smoketest::builder() .precompiled_module("delete-database") .autopublish(false) @@ -23,8 +53,10 @@ fn test_delete_database() { thread::sleep(Duration::from_secs(2)); // Delete the database - test.spacetime(&["delete", "--server", &test.server_url, &name]) + let output = test + .spacetime_with_stdin(&["delete", "--server", &test.server_url, &name], "y\n") .unwrap(); + assert_delete_prompt(&output, &name); // Collect whatever updates we got let updates = sub.collect().unwrap(); @@ -37,3 +69,25 @@ fn test_delete_database() { updates.len() ); } + +#[test] +fn test_delete_database_yes_skips_confirmation() { + let mut test = Smoketest::builder() + .precompiled_module("delete-database") + .autopublish(false) + .build(); + + let name = format!("test-db-{}", std::process::id()); + test.publish_module_named(&name, false).unwrap(); + + let output = test + .spacetime(&["delete", "--server", &test.server_url, "--yes", &name]) + .unwrap(); + assert!( + output.contains("Skipping confirmation due to --yes"), + "expected --yes skip message:\n{output}" + ); + + let result = test.spacetime(&["logs", "--server", &test.server_url, &name]); + assert!(result.is_err(), "expected database to be deleted"); +} From 20e612423081dd376f1d10b7217e5c8af3ee5bbd Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Tue, 14 Apr 2026 09:48:08 -0700 Subject: [PATCH 2/6] [bot/delete-requires-confirmation]: review --- .../tests/smoketests/{delete_database.rs => cli/delete.rs} | 2 +- crates/smoketests/tests/smoketests/cli/mod.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) rename crates/smoketests/tests/smoketests/{delete_database.rs => cli/delete.rs} (97%) diff --git a/crates/smoketests/tests/smoketests/delete_database.rs b/crates/smoketests/tests/smoketests/cli/delete.rs similarity index 97% rename from crates/smoketests/tests/smoketests/delete_database.rs rename to crates/smoketests/tests/smoketests/cli/delete.rs index e55152fb5d1..7f009c9b7be 100644 --- a/crates/smoketests/tests/smoketests/delete_database.rs +++ b/crates/smoketests/tests/smoketests/cli/delete.rs @@ -24,7 +24,7 @@ fn test_delete_database_aborts_without_confirmation() { test.publish_module_named(&name, false).unwrap(); let output = test - .spacetime_with_stdin(&["delete", "--server", &test.server_url, &name], "n\n") + .spacetime(&["delete", "--server", &test.server_url, &name]); .unwrap(); assert_delete_prompt(&output, &name); assert!(output.contains("Aborting"), "expected abort message:\n{output}"); diff --git a/crates/smoketests/tests/smoketests/cli/mod.rs b/crates/smoketests/tests/smoketests/cli/mod.rs index d54c9749851..30a1b7e544d 100644 --- a/crates/smoketests/tests/smoketests/cli/mod.rs +++ b/crates/smoketests/tests/smoketests/cli/mod.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod delete; pub mod dev; pub mod generate; pub mod publish; From 1fb3bf4ad8a73f2bfc943e09f6346030efa74595 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Tue, 14 Apr 2026 09:48:23 -0700 Subject: [PATCH 3/6] [bot/delete-requires-confirmation]: review --- crates/smoketests/tests/smoketests/cli/delete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/cli/delete.rs b/crates/smoketests/tests/smoketests/cli/delete.rs index 7f009c9b7be..92ad8579c6b 100644 --- a/crates/smoketests/tests/smoketests/cli/delete.rs +++ b/crates/smoketests/tests/smoketests/cli/delete.rs @@ -24,7 +24,7 @@ fn test_delete_database_aborts_without_confirmation() { test.publish_module_named(&name, false).unwrap(); let output = test - .spacetime(&["delete", "--server", &test.server_url, &name]); + .spacetime(&["delete", "--server", &test.server_url, &name]) .unwrap(); assert_delete_prompt(&output, &name); assert!(output.contains("Aborting"), "expected abort message:\n{output}"); From f4f823472d3b5f4676a0d1b8e9c29fe84e84acdb Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Tue, 14 Apr 2026 09:51:31 -0700 Subject: [PATCH 4/6] [bot/delete-requires-confirmation]: fix tests --- crates/smoketests/tests/smoketests/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/smoketests/tests/smoketests/mod.rs b/crates/smoketests/tests/smoketests/mod.rs index f5053652dd3..3ac159af20d 100644 --- a/crates/smoketests/tests/smoketests/mod.rs +++ b/crates/smoketests/tests/smoketests/mod.rs @@ -11,7 +11,6 @@ mod connect_disconnect_from_cli; mod create_project; mod csharp_module; mod default_module_clippy; -mod delete_database; mod describe; mod detect_wasm_bindgen; mod dml; From 5e087c0e831bf6d70d19e4f7a4921f59934bd6fa Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Tue, 14 Apr 2026 09:55:53 -0700 Subject: [PATCH 5/6] [bot/delete-requires-confirmation]: move back --- .../smoketests/tests/smoketests/cli/delete.rs | 93 ------------------- crates/smoketests/tests/smoketests/cli/mod.rs | 1 - crates/smoketests/tests/smoketests/mod.rs | 1 + 3 files changed, 1 insertion(+), 94 deletions(-) delete mode 100644 crates/smoketests/tests/smoketests/cli/delete.rs diff --git a/crates/smoketests/tests/smoketests/cli/delete.rs b/crates/smoketests/tests/smoketests/cli/delete.rs deleted file mode 100644 index 92ad8579c6b..00000000000 --- a/crates/smoketests/tests/smoketests/cli/delete.rs +++ /dev/null @@ -1,93 +0,0 @@ -use spacetimedb_smoketests::Smoketest; -use std::thread; -use std::time::Duration; - -fn assert_delete_prompt(output: &str, database: &str) { - assert!( - output.contains("Are you sure you want to delete database"), - "expected confirmation prompt in output:\n{output}" - ); - assert!( - output.contains(database), - "expected database name in confirmation prompt:\n{output}" - ); -} - -#[test] -fn test_delete_database_aborts_without_confirmation() { - let mut test = Smoketest::builder() - .precompiled_module("delete-database") - .autopublish(false) - .build(); - - let name = format!("test-db-{}", std::process::id()); - test.publish_module_named(&name, false).unwrap(); - - let output = test - .spacetime(&["delete", "--server", &test.server_url, &name]) - .unwrap(); - assert_delete_prompt(&output, &name); - assert!(output.contains("Aborting"), "expected abort message:\n{output}"); - - test.spacetime(&["logs", "--server", &test.server_url, &name]).unwrap(); -} - -/// Test that deleting a database stops the module. -/// The module is considered stopped if its scheduled reducer stops -/// producing update events. -#[test] -fn test_delete_database_with_confirmation() { - let mut test = Smoketest::builder() - .precompiled_module("delete-database") - .autopublish(false) - .build(); - - let name = format!("test-db-{}", std::process::id()); - test.publish_module_named(&name, false).unwrap(); - - // Start subscription in background to collect updates - // We request many updates but will stop early when we delete the db - let sub = test.subscribe_background(&["SELECT * FROM counter"], 1000).unwrap(); - - // Let the scheduled reducer run for a bit - thread::sleep(Duration::from_secs(2)); - - // Delete the database - let output = test - .spacetime_with_stdin(&["delete", "--server", &test.server_url, &name], "y\n") - .unwrap(); - assert_delete_prompt(&output, &name); - - // Collect whatever updates we got - let updates = sub.collect().unwrap(); - - // At a rate of 100ms, we shouldn't have more than 20 updates in 2secs. - // But let's say 50, in case the delete gets delayed for some reason. - assert!( - updates.len() <= 50, - "Expected at most 50 updates, got {}. Database may not have stopped.", - updates.len() - ); -} - -#[test] -fn test_delete_database_yes_skips_confirmation() { - let mut test = Smoketest::builder() - .precompiled_module("delete-database") - .autopublish(false) - .build(); - - let name = format!("test-db-{}", std::process::id()); - test.publish_module_named(&name, false).unwrap(); - - let output = test - .spacetime(&["delete", "--server", &test.server_url, "--yes", &name]) - .unwrap(); - assert!( - output.contains("Skipping confirmation due to --yes"), - "expected --yes skip message:\n{output}" - ); - - let result = test.spacetime(&["logs", "--server", &test.server_url, &name]); - assert!(result.is_err(), "expected database to be deleted"); -} diff --git a/crates/smoketests/tests/smoketests/cli/mod.rs b/crates/smoketests/tests/smoketests/cli/mod.rs index 30a1b7e544d..d54c9749851 100644 --- a/crates/smoketests/tests/smoketests/cli/mod.rs +++ b/crates/smoketests/tests/smoketests/cli/mod.rs @@ -1,5 +1,4 @@ pub mod auth; -pub mod delete; pub mod dev; pub mod generate; pub mod publish; diff --git a/crates/smoketests/tests/smoketests/mod.rs b/crates/smoketests/tests/smoketests/mod.rs index 3ac159af20d..f5053652dd3 100644 --- a/crates/smoketests/tests/smoketests/mod.rs +++ b/crates/smoketests/tests/smoketests/mod.rs @@ -11,6 +11,7 @@ mod connect_disconnect_from_cli; mod create_project; mod csharp_module; mod default_module_clippy; +mod delete_database; mod describe; mod detect_wasm_bindgen; mod dml; From 794cfa6f099ee1a6fe7027a2fb2c1cfa9cff7f57 Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Tue, 14 Apr 2026 09:56:21 -0700 Subject: [PATCH 6/6] [bot/delete-requires-confirmation]: fix --- .../tests/smoketests/delete_database.rs | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 crates/smoketests/tests/smoketests/delete_database.rs diff --git a/crates/smoketests/tests/smoketests/delete_database.rs b/crates/smoketests/tests/smoketests/delete_database.rs new file mode 100644 index 00000000000..92ad8579c6b --- /dev/null +++ b/crates/smoketests/tests/smoketests/delete_database.rs @@ -0,0 +1,93 @@ +use spacetimedb_smoketests::Smoketest; +use std::thread; +use std::time::Duration; + +fn assert_delete_prompt(output: &str, database: &str) { + assert!( + output.contains("Are you sure you want to delete database"), + "expected confirmation prompt in output:\n{output}" + ); + assert!( + output.contains(database), + "expected database name in confirmation prompt:\n{output}" + ); +} + +#[test] +fn test_delete_database_aborts_without_confirmation() { + let mut test = Smoketest::builder() + .precompiled_module("delete-database") + .autopublish(false) + .build(); + + let name = format!("test-db-{}", std::process::id()); + test.publish_module_named(&name, false).unwrap(); + + let output = test + .spacetime(&["delete", "--server", &test.server_url, &name]) + .unwrap(); + assert_delete_prompt(&output, &name); + assert!(output.contains("Aborting"), "expected abort message:\n{output}"); + + test.spacetime(&["logs", "--server", &test.server_url, &name]).unwrap(); +} + +/// Test that deleting a database stops the module. +/// The module is considered stopped if its scheduled reducer stops +/// producing update events. +#[test] +fn test_delete_database_with_confirmation() { + let mut test = Smoketest::builder() + .precompiled_module("delete-database") + .autopublish(false) + .build(); + + let name = format!("test-db-{}", std::process::id()); + test.publish_module_named(&name, false).unwrap(); + + // Start subscription in background to collect updates + // We request many updates but will stop early when we delete the db + let sub = test.subscribe_background(&["SELECT * FROM counter"], 1000).unwrap(); + + // Let the scheduled reducer run for a bit + thread::sleep(Duration::from_secs(2)); + + // Delete the database + let output = test + .spacetime_with_stdin(&["delete", "--server", &test.server_url, &name], "y\n") + .unwrap(); + assert_delete_prompt(&output, &name); + + // Collect whatever updates we got + let updates = sub.collect().unwrap(); + + // At a rate of 100ms, we shouldn't have more than 20 updates in 2secs. + // But let's say 50, in case the delete gets delayed for some reason. + assert!( + updates.len() <= 50, + "Expected at most 50 updates, got {}. Database may not have stopped.", + updates.len() + ); +} + +#[test] +fn test_delete_database_yes_skips_confirmation() { + let mut test = Smoketest::builder() + .precompiled_module("delete-database") + .autopublish(false) + .build(); + + let name = format!("test-db-{}", std::process::id()); + test.publish_module_named(&name, false).unwrap(); + + let output = test + .spacetime(&["delete", "--server", &test.server_url, "--yes", &name]) + .unwrap(); + assert!( + output.contains("Skipping confirmation due to --yes"), + "expected --yes skip message:\n{output}" + ); + + let result = test.spacetime(&["logs", "--server", &test.server_url, &name]); + assert!(result.is_err(), "expected database to be deleted"); +}