//! Item reorder workflow tests: move up, move down, boundary behavior. use serde_json::Value; use crate::harness::TestHarness; /// Helper: create a creator with a project and return the project ID. async fn setup_project(h: &mut TestHarness) -> String { h.create_creator("reorder").await; let resp = h .client .post_form("/api/projects", "slug=reorder-proj&title=Reorder+Project") .await; let project: Value = resp.json(); project["id"].as_str().unwrap().to_string() } /// Helper: create an item and return its ID. async fn create_item(h: &mut TestHarness, project_id: &str, title: &str) -> String { let resp = h .client .post_form( &format!("/api/projects/{}/items", project_id), &format!("title={}", urlencoding::encode(title)), ) .await; let item: Value = resp.json(); item["id"].as_str().unwrap().to_string() } /// Helper: get item IDs in current display order via DB. async fn get_item_order(h: &TestHarness, project_id: &str) -> Vec { let rows: Vec<(makenotwork::db::ItemId,)> = sqlx::query_as( "SELECT id FROM items WHERE project_id = $1::uuid ORDER BY sort_order, created_at DESC", ) .bind(project_id) .fetch_all(&h.db) .await .unwrap(); rows.into_iter().map(|(id,)| id.to_string()).collect() } #[tokio::test] async fn move_item_down_swaps_with_next() { let mut h = TestHarness::new().await; let project_id = setup_project(&mut h).await; // Create 3 items — all start with sort_order=0, so order is by created_at DESC (C, B, A) let a = create_item(&mut h, &project_id, "Item A").await; let b = create_item(&mut h, &project_id, "Item B").await; let c = create_item(&mut h, &project_id, "Item C").await; // Initial order: C, B, A (newest first due to created_at DESC with all sort_order=0) let order = get_item_order(&h, &project_id).await; assert_eq!(order, vec![c.clone(), b.clone(), a.clone()]); // Move C (first item) down — should swap with B let resp = h .client .put_form(&format!("/api/items/{}/move", c), "direction=down") .await; assert_eq!(resp.status, 204); // Order should now be: B, C, A let order = get_item_order(&h, &project_id).await; assert_eq!(order, vec![b.clone(), c.clone(), a.clone()]); } #[tokio::test] async fn move_item_up_swaps_with_previous() { let mut h = TestHarness::new().await; let project_id = setup_project(&mut h).await; let a = create_item(&mut h, &project_id, "Item A").await; let b = create_item(&mut h, &project_id, "Item B").await; let c = create_item(&mut h, &project_id, "Item C").await; // Initial order: C, B, A // Move B (middle) up — should swap with C let resp = h .client .put_form(&format!("/api/items/{}/move", b), "direction=up") .await; assert_eq!(resp.status, 204); // Order should now be: B, C, A let order = get_item_order(&h, &project_id).await; assert_eq!(order, vec![b.clone(), c.clone(), a.clone()]); } #[tokio::test] async fn move_first_item_up_is_noop() { let mut h = TestHarness::new().await; let project_id = setup_project(&mut h).await; let a = create_item(&mut h, &project_id, "Item A").await; let b = create_item(&mut h, &project_id, "Item B").await; // Initial order: B, A // Move B (first) up — should be a no-op let resp = h .client .put_form(&format!("/api/items/{}/move", b), "direction=up") .await; assert_eq!(resp.status, 204); // Order unchanged: B, A let order = get_item_order(&h, &project_id).await; assert_eq!(order, vec![b, a]); } #[tokio::test] async fn move_last_item_down_is_noop() { let mut h = TestHarness::new().await; let project_id = setup_project(&mut h).await; let a = create_item(&mut h, &project_id, "Item A").await; let b = create_item(&mut h, &project_id, "Item B").await; // Initial order: B, A // Move A (last) down — should be a no-op let resp = h .client .put_form(&format!("/api/items/{}/move", a), "direction=down") .await; assert_eq!(resp.status, 204); // Order unchanged: B, A let order = get_item_order(&h, &project_id).await; assert_eq!(order, vec![b, a]); } #[tokio::test] async fn move_normalizes_then_swaps() { let mut h = TestHarness::new().await; let project_id = setup_project(&mut h).await; let a = create_item(&mut h, &project_id, "Item A").await; let b = create_item(&mut h, &project_id, "Item B").await; let c = create_item(&mut h, &project_id, "Item C").await; // All items start with sort_order=0 (cold start) // Move A (last) up — should normalize [0,1,2] then swap A with B let resp = h .client .put_form(&format!("/api/items/{}/move", a), "direction=up") .await; assert_eq!(resp.status, 204); // Order should be: C, A, B let order = get_item_order(&h, &project_id).await; assert_eq!(order, vec![c, a, b]); } #[tokio::test] async fn move_requires_ownership() { let mut h = TestHarness::new().await; let project_id = setup_project(&mut h).await; let item_id = create_item(&mut h, &project_id, "Item").await; // Log in as a different user h.signup("other", "other@test.com", "password123").await; h.client.post_form("/logout", "").await; h.login("other", "password123").await; // Try to move the first user's item — should be rejected let resp = h .client .put_form(&format!("/api/items/{}/move", item_id), "direction=down") .await; assert_eq!(resp.status, 403); }