mod common; use anyhow::Result; use axum::http::StatusCode; use common::{acquire_db_lock, body_to_vec, TestApp}; use serde::Deserialize; use serde_json::Value; use uuid::Uuid; #[derive(Deserialize)] struct DocumentDetail { document: DocumentInfo, current_version: DocumentVersion, assets: Vec, } #[derive(Deserialize)] struct DocumentInfo { id: Uuid, original_name: String, current_version: i32, deleted_at: Option, tags: Vec, } #[derive(Deserialize)] struct DocumentVersion { id: Uuid, s3_key: String, size_bytes: i64, } #[derive(Deserialize)] struct DocumentAssetInfo { id: Uuid, asset_type: String, } #[derive(Deserialize)] struct DocumentListItem { id: Uuid, current_version: i32, } #[derive(Deserialize)] struct DocumentDownload { url: String, filename: String, } #[derive(Deserialize)] struct BulkReanalyze { queued: usize, } #[derive(Deserialize)] struct AnalyzeJobPayload { document_id: Uuid, document_version_id: Uuid, #[serde(default)] force: bool, } #[tokio::test] async fn upload_and_list_document() -> Result<()> { let _lock = acquire_db_lock().await; let app = TestApp::new().await?; let password = "passw0rd"; app.insert_user("dana", password, "admin").await?; let token = app.login_token("dana", password).await?; let file_bytes = b"example document body".to_vec(); let upload = app .upload_document( "/api/documents", "doc.txt", "text/plain", &file_bytes, None, &token, ) .await?; assert_eq!(upload.status(), StatusCode::CREATED); let body = body_to_vec(upload.into_body()).await?; let detail: DocumentDetail = serde_json::from_slice(&body)?; assert_eq!(detail.document.original_name, "doc.txt"); assert_eq!(detail.document.current_version, 1); assert_eq!(detail.document.deleted_at, None); assert!(detail.document.tags.is_empty()); assert_eq!(detail.current_version.size_bytes, file_bytes.len() as i64); assert!(detail.assets.is_empty()); let stored = app .storage() .get(&detail.current_version.s3_key) .await .expect("object stored"); assert_eq!(stored.bytes, file_bytes); assert_eq!(app.storage().object_count().await, 1); let response = app.get("/api/documents", Some(&token)).await?; assert_eq!(response.status(), StatusCode::OK); let body = body_to_vec(response.into_body()).await?; let mut list: Vec = serde_json::from_slice(&body)?; assert_eq!(list.len(), 1); let item = list.pop().unwrap(); assert_eq!(item.id, detail.document.id); assert_eq!(item.current_version, 1); let download = app .get( &format!("/api/documents/{}/download", detail.document.id), Some(&token), ) .await?; assert_eq!(download.status(), StatusCode::OK); let body = body_to_vec(download.into_body()).await?; let download_info: DocumentDownload = serde_json::from_slice(&body)?; assert!(download_info.url.contains(&detail.current_version.s3_key)); assert_eq!(download_info.filename, "doc.txt"); app.cleanup().await?; Ok(()) } #[tokio::test] async fn duplicate_and_restore_document() -> Result<()> { let _lock = acquire_db_lock().await; let app = TestApp::new().await?; let password = "pass1234"; app.insert_user("sam", password, "admin").await?; let token = app.login_token("sam", password).await?; let payload = b"same bytes".to_vec(); let first = app .upload_document( "/api/documents", "dup.bin", "application/octet-stream", &payload, None, &token, ) .await?; assert_eq!(first.status(), StatusCode::CREATED); let first_body = body_to_vec(first.into_body()).await?; let first_detail: DocumentDetail = serde_json::from_slice(&first_body)?; let second = app .upload_document( "/api/documents", "dup.bin", "application/octet-stream", &payload, None, &token, ) .await?; assert_eq!(second.status(), StatusCode::OK); let second_body = body_to_vec(second.into_body()).await?; let second_detail: DocumentDetail = serde_json::from_slice(&second_body)?; assert_eq!(first_detail.document.id, second_detail.document.id); assert_eq!(second_detail.document.deleted_at, None); assert!(second_detail.assets.is_empty()); assert_eq!(app.storage().object_count().await, 1); let delete = app .delete( &format!("/api/documents/{}", first_detail.document.id), Some(&token), ) .await?; assert_eq!(delete.status(), StatusCode::NO_CONTENT); let third = app .upload_document( "/api/documents", "dup.bin", "application/octet-stream", &payload, None, &token, ) .await?; assert_eq!(third.status(), StatusCode::OK); let third_body = body_to_vec(third.into_body()).await?; let third_detail: DocumentDetail = serde_json::from_slice(&third_body)?; assert_eq!(third_detail.document.id, first_detail.document.id); assert_eq!(third_detail.document.deleted_at, None); assert_eq!(app.storage().object_count().await, 1); app.cleanup().await?; Ok(()) } #[tokio::test] async fn bulk_reanalyze_documents() -> Result<()> { let _lock = acquire_db_lock().await; let app = TestApp::new().await?; let password = "bulkpass"; app.insert_user("alex", password, "admin").await?; let token = app.login_token("alex", password).await?; app.clear_jobs().await?; let first_bytes = b"first doc"; let first = app .upload_document( "/api/documents", "first.txt", "text/plain", first_bytes, None, &token, ) .await?; assert_eq!(first.status(), StatusCode::CREATED); let first_body = body_to_vec(first.into_body()).await?; let first_detail: DocumentDetail = serde_json::from_slice(&first_body)?; let second_bytes = b"second doc"; let second = app .upload_document( "/api/documents", "second.txt", "text/plain", second_bytes, None, &token, ) .await?; assert_eq!(second.status(), StatusCode::CREATED); let second_body = body_to_vec(second.into_body()).await?; let second_detail: DocumentDetail = serde_json::from_slice(&second_body)?; app.clear_jobs().await?; let response = app .post_json( "/api/documents/reanalyze", &serde_json::json!({}), Some(&token), ) .await?; assert_eq!(response.status(), StatusCode::ACCEPTED); let body = body_to_vec(response.into_body()).await?; let bulk: BulkReanalyze = serde_json::from_slice(&body)?; assert_eq!(bulk.queued, 2); let jobs = app.jobs_by_type("analyze-document").await?; assert_eq!(jobs.len(), 2); let mut payload_docs = Vec::new(); for job in jobs { let payload: AnalyzeJobPayload = serde_json::from_value(job.payload)?; assert!(payload.force); payload_docs.push((payload.document_id, payload.document_version_id)); } let mut expected = vec![ (first_detail.document.id, first_detail.current_version.id), (second_detail.document.id, second_detail.current_version.id), ]; payload_docs.sort(); expected.sort(); assert_eq!(payload_docs, expected); app.cleanup().await?; Ok(()) }