mod common; use axum::http::StatusCode; use common::{ delete_authed, get, get_authed, patch_json_authed, post_json_authed, put_json_authed, response_body, setup_app, setup_app_with_auth, }; use tower::ServiceExt; #[tokio::test] async fn backlinks_for_nonexistent_media() { let app = setup_app().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .oneshot(get(&format!("/api/v1/media/{fake_id}/backlinks"))) .await .unwrap(); // Should return OK with empty list, or NOT_FOUND assert!( resp.status() == StatusCode::OK || resp.status() == StatusCode::NOT_FOUND ); } #[tokio::test] async fn outgoing_links_for_nonexistent_media() { let app = setup_app().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .oneshot(get(&format!("/api/v1/media/{fake_id}/outgoing-links"))) .await .unwrap(); assert!( resp.status() == StatusCode::OK || resp.status() == StatusCode::NOT_FOUND ); } #[tokio::test] async fn notes_graph_empty() { let (app, _, _, viewer) = setup_app_with_auth().await; let resp = app .clone() .oneshot(get_authed("/api/v1/notes/graph", &viewer)) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = response_body(resp).await; // Fresh database: graph must be empty. if let Some(arr) = body.as_array() { assert!(arr.is_empty(), "graph should be empty, got {arr:?}"); } else if let Some(obj) = body.as_object() { // Accept an object if the schema uses {nodes:[], edges:[]} style. let nodes_empty = obj .get("nodes") .and_then(|v| v.as_array()) .is_none_or(std::vec::Vec::is_empty); let edges_empty = obj .get("edges") .and_then(|v| v.as_array()) .is_none_or(std::vec::Vec::is_empty); assert!( nodes_empty && edges_empty, "graph should be empty, got {obj:?}" ); } else { panic!("expected array or object, got {body}"); } } #[tokio::test] async fn unresolved_count_zero() { let app = setup_app().await; let resp = app .oneshot(get("/api/v1/notes/unresolved-count")) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = response_body(resp).await; // Fresh database has no unresolved links. let count = body["count"] .as_u64() .expect("response should have a numeric 'count' field"); assert_eq!(count, 0, "expected zero unresolved links in fresh database"); } #[tokio::test] async fn reindex_links_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(post_json_authed( &format!("/api/v1/media/{fake_id}/reindex-links"), "{}", &viewer, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn update_media_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(patch_json_authed( &format!("/api/v1/media/{fake_id}"), r#"{"title":"new title"}"#, &viewer, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn delete_media_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(delete_authed(&format!("/api/v1/media/{fake_id}"), &viewer)) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn update_sync_device_requires_editor() { let (app, _, _, viewer) = setup_app_with_auth().await; let fake_id = uuid::Uuid::now_v7(); let resp = app .clone() .oneshot(put_json_authed( &format!("/api/v1/sync/devices/{fake_id}"), r#"{"name":"renamed"}"#, &viewer, )) .await .unwrap(); assert_eq!(resp.status(), StatusCode::FORBIDDEN); }