mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 16:02:36 +00:00
A move writes old path -> new path (per workspace and kind, per owner for a draft-only move) in its transaction; a draft save or discard addressed to a path the caller has no draft at resolves through it and keeps the moved draft's path keys. Creating an item at a path drops the records leaving it. Every writer (edit routes, sessions, chat, CLI, the tab-close flush) follows without passing an id, so the id plumbing is gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
75 lines
3.0 KiB
Rust
75 lines
3.0 KiB
Rust
//! Moving a draft must carry both of its path keys.
|
|
//!
|
|
//! A draft value holds a typed path and a mirror the editors keep beside it while
|
|
//! it differs from the row's path (`path`/`draft_path`; which is which depends on
|
|
//! the kind). The loaders prefer the mirror, so a move that rewrote only the typed
|
|
//! key left the mirror naming the old location: reopening the item restored the
|
|
//! old path, and the next autosave wrote it back — undoing the move silently.
|
|
//!
|
|
//! This pins `move_draft`, including that a draft with no mirror never gains one.
|
|
//! A deploy's carry follows the keys only where they still name the old path
|
|
//! (`drafts_save_follows_move` pins that).
|
|
|
|
use serde_json::Value;
|
|
use sqlx::{Pool, Postgres};
|
|
|
|
use windmill_test_utils::*;
|
|
|
|
async fn move_to(port: u16, from: &str, to: &str) -> anyhow::Result<String> {
|
|
let resp = reqwest::Client::new()
|
|
.post(format!(
|
|
"http://localhost:{port}/api/w/test-workspace/drafts/move/script/{from}"
|
|
))
|
|
.header("Authorization", "Bearer SECRET_TOKEN")
|
|
.json(&serde_json::json!({ "new_path": to }))
|
|
.send()
|
|
.await?;
|
|
// Asserted here so a rejected move fails as itself, rather than as a JSON
|
|
// parse error in the read-back below.
|
|
let status = resp.status();
|
|
let body = resp.text().await?;
|
|
assert!(status.is_success(), "move {from} -> {to} failed: {body}");
|
|
Ok(body)
|
|
}
|
|
|
|
/// The stored draft value at `path`, read back through the API so this test needs
|
|
/// no `sqlx::query!` (which would want an offline cache entry of its own).
|
|
async fn value_at(port: u16, path: &str) -> anyhow::Result<Value> {
|
|
let body: Value = reqwest::Client::new()
|
|
.get(format!(
|
|
"http://localhost:{port}/api/w/test-workspace/drafts/get_own/script/{path}"
|
|
))
|
|
.header("Authorization", "Bearer SECRET_TOKEN")
|
|
.send()
|
|
.await?
|
|
.json()
|
|
.await?;
|
|
Ok(body
|
|
.get("value")
|
|
.cloned()
|
|
.unwrap_or_else(|| panic!("no draft at {path}: {body}")))
|
|
}
|
|
|
|
#[sqlx::test(fixtures("base", "drafts_move_mirror"))]
|
|
async fn test_move_carries_both_path_keys(db: Pool<Postgres>) -> anyhow::Result<()> {
|
|
initialize_tracing().await;
|
|
let server = ApiServer::start(db.clone()).await?;
|
|
let port = server.addr.port();
|
|
|
|
move_to(port, "u/test-user/draft_mirror", "u/test-user/renamed").await?;
|
|
move_to(port, "u/test-user/draft_plain", "u/test-user/plain2").await?;
|
|
|
|
// The mirror follows: left at `u/test-user/friendly` it would win at load and
|
|
// walk the item back there.
|
|
let moved = value_at(port, "u/test-user/renamed").await?;
|
|
assert_eq!(moved["path"], "u/test-user/renamed");
|
|
assert_eq!(moved["draft_path"], "u/test-user/renamed");
|
|
|
|
// A draft that never had a mirror must not be given one.
|
|
let plain = value_at(port, "u/test-user/plain2").await?;
|
|
assert_eq!(plain["path"], "u/test-user/plain2");
|
|
assert_eq!(plain.get("draft_path"), None, "mirror injected: {plain}");
|
|
|
|
Ok(())
|
|
}
|