//! Custom profile link CRUD and reorder workflow tests. use crate::harness::TestHarness; use serde_json::Value; #[tokio::test] async fn custom_link_crud_lifecycle() { let mut h = TestHarness::new().await; let _user_id = h.signup("linkuser", "linkuser@test.com", "password123").await; // Create a link let resp = h.client.post_form( "/api/links", "url=https%3A%2F%2Fexample.com&title=My+Website&description=Personal+site", ).await; assert!(resp.status.is_success(), "Create link failed: {} {}", resp.status, resp.text); let link: Value = resp.json(); assert_eq!(link["url"].as_str().unwrap(), "https://example.com"); assert_eq!(link["title"].as_str().unwrap(), "My Website"); assert_eq!(link["description"].as_str().unwrap(), "Personal site"); let link_id = link["id"].as_str().expect("link should have id"); // Update only the title (partial update via JSON) let resp = h.client.put_json( &format!("/api/links/{}", link_id), r#"{"title": "Updated Title"}"#, ).await; assert!(resp.status.is_success(), "Update link failed: {} {}", resp.status, resp.text); let updated: Value = resp.json(); assert_eq!(updated["title"].as_str().unwrap(), "Updated Title"); assert_eq!(updated["url"].as_str().unwrap(), "https://example.com", "URL should remain unchanged"); // Delete the link let resp = h.client.delete(&format!("/api/links/{}", link_id)).await; assert_eq!(resp.status, 204, "Delete should return 204"); } #[tokio::test] async fn custom_link_reorder() { let mut h = TestHarness::new().await; let _user_id = h.signup("linkorder", "linkorder@test.com", "password123").await; // Create 3 links let resp = h.client.post_form("/api/links", "url=https%3A%2F%2Fa.com&title=Link+A").await; let link_a: Value = resp.json(); let id_a = link_a["id"].as_str().unwrap(); let resp = h.client.post_form("/api/links", "url=https%3A%2F%2Fb.com&title=Link+B").await; let link_b: Value = resp.json(); let id_b = link_b["id"].as_str().unwrap(); let resp = h.client.post_form("/api/links", "url=https%3A%2F%2Fc.com&title=Link+C").await; let link_c: Value = resp.json(); let id_c = link_c["id"].as_str().unwrap(); // Reorder: C, A, B let reorder_body = format!(r#"{{"link_ids": ["{}", "{}", "{}"]}}"#, id_c, id_a, id_b); let resp = h.client.put_json("/api/links/reorder", &reorder_body).await; assert_eq!(resp.status, 204, "Reorder should return 204, got {}: {}", resp.status, resp.text); // Verify order via direct SQL let rows: Vec<(String, i32)> = sqlx::query_as( "SELECT title, sort_order FROM custom_links WHERE user_id = (SELECT id FROM users WHERE username = 'linkorder') ORDER BY sort_order" ) .fetch_all(&h.db) .await .unwrap(); assert_eq!(rows.len(), 3); assert_eq!(rows[0].0, "Link C", "First should be Link C"); assert_eq!(rows[1].0, "Link A", "Second should be Link A"); assert_eq!(rows[2].0, "Link B", "Third should be Link B"); } #[tokio::test] async fn custom_link_validation_errors() { let mut h = TestHarness::new().await; let _user_id = h.signup("linkval", "linkval@test.com", "password123").await; // Bad URL scheme (ftp) let resp = h.client.post_form( "/api/links", "url=ftp%3A%2F%2Fexample.com&title=Bad+Link", ).await; assert_eq!(resp.status, 422, "Bad URL scheme should return 422, got {}: {}", resp.status, resp.text); // Empty title let resp = h.client.post_form( "/api/links", "url=https%3A%2F%2Fexample.com&title=", ).await; assert_eq!(resp.status, 422, "Empty title should return 422, got {}: {}", resp.status, resp.text); } #[tokio::test] async fn custom_link_auth_other_user() { let mut h = TestHarness::new().await; // User A creates a link let _user_a = h.signup("linkowner", "linkowner@test.com", "password123").await; let resp = h.client.post_form( "/api/links", "url=https%3A%2F%2Fexample.com&title=Owner+Link", ).await; assert!(resp.status.is_success()); let link: Value = resp.json(); let link_id = link["id"].as_str().unwrap(); // Switch to User B h.client.post_form("/logout", "").await; let _user_b = h.signup("linkthief", "linkthief@test.com", "password123").await; // User B tries to update User A's link let resp = h.client.put_json( &format!("/api/links/{}", link_id), r#"{"title": "Stolen"}"#, ).await; assert_eq!(resp.status, 404, "Updating another user's link should return 404, got {}", resp.status); // User B tries to delete User A's link let resp = h.client.delete(&format!("/api/links/{}", link_id)).await; assert_eq!(resp.status, 404, "Deleting another user's link should return 404, got {}", resp.status); }