//! 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" ); 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 ); }