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; // GET /api/v1/media/{id}/metadata/external (viewer) #[tokio::test] async fn get_external_metadata_requires_auth() { let (app, ..) = setup_app_with_auth().await; let response = app .oneshot(get( "/api/v1/media/00000000-0000-0000-0000-000000000000/external-metadata", )) .await .unwrap(); assert_eq!(response.status(), StatusCode::UNAUTHORIZED); } #[tokio::test] async fn get_external_metadata_viewer_ok() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(get_authed( "/api/v1/media/00000000-0000-0000-0000-000000000000/external-metadata", &viewer_token, )) .await .unwrap(); // Media does not exist; 200 with empty array or 404 are both valid assert!( response.status() == StatusCode::OK || response.status() == StatusCode::NOT_FOUND ); } // POST /api/v1/media/{id}/enrich (editor) #[tokio::test] async fn trigger_enrichment_requires_editor() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/media/00000000-0000-0000-0000-000000000000/enrich", "{}", &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn trigger_enrichment_editor_accepted() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/media/00000000-0000-0000-0000-000000000000/enrich", "{}", &editor_token, )) .await .unwrap(); // Route is accessible to editors; media not found returns 404, job queued // returns 200 assert!( response.status() == StatusCode::OK || response.status() == StatusCode::NOT_FOUND ); } // POST /api/v1/jobs/enrich (editor, batch) #[tokio::test] async fn batch_enrich_requires_editor() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/jobs/enrich", r#"{"media_ids":["00000000-0000-0000-0000-000000000000"]}"#, &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn batch_enrich_empty_ids_rejected() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/jobs/enrich", r#"{"media_ids":[]}"#, &editor_token, )) .await .unwrap(); // Validation requires 1-1000 ids assert!( response.status() == StatusCode::BAD_REQUEST || response.status() == StatusCode::UNPROCESSABLE_ENTITY ); } #[tokio::test] async fn batch_enrich_editor_accepted() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/jobs/enrich", r#"{"media_ids":["00000000-0000-0000-0000-000000000000"]}"#, &editor_token, )) .await .unwrap(); // Job is queued and a job_id is returned assert_eq!(response.status(), StatusCode::OK); } // No-auth coverage (exercises setup_app and get) #[tokio::test] async fn get_external_metadata_auth_disabled() { let app = setup_app().await; let response = app .oneshot(get( "/api/v1/media/00000000-0000-0000-0000-000000000000/external-metadata", )) .await .unwrap(); assert!( response.status() == StatusCode::OK || response.status() == StatusCode::NOT_FOUND ); } // RBAC enforcement for editor-level HTTP methods #[tokio::test] async fn batch_enrich_response_has_job_id() { let (app, _, editor_token, _) = setup_app_with_auth().await; let response = app .oneshot(post_json_authed( "/api/v1/jobs/enrich", r#"{"media_ids":["00000000-0000-0000-0000-000000000000"]}"#, &editor_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let body = response_body(response).await; // Route queues a job and returns a job identifier assert!( body["job_id"].is_string() || body["id"].is_string(), "expected job identifier in response: {body}" ); } #[tokio::test] async fn delete_tag_requires_editor() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(delete_authed( "/api/v1/tags/00000000-0000-0000-0000-000000000000", &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn update_media_requires_editor() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(patch_json_authed( "/api/v1/media/00000000-0000-0000-0000-000000000000", r#"{"title":"new title"}"#, &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); } #[tokio::test] async fn update_sync_device_requires_editor() { let (app, _, _, viewer_token) = setup_app_with_auth().await; let response = app .oneshot(put_json_authed( "/api/v1/sync/devices/00000000-0000-0000-0000-000000000000", r#"{"name":"my device"}"#, &viewer_token, )) .await .unwrap(); assert_eq!(response.status(), StatusCode::FORBIDDEN); }