Files
papercrate/backend/tests/documents_flow.rs
T
2025-10-10 09:39:58 +02:00

276 lines
7.6 KiB
Rust

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<DocumentAssetInfo>,
}
#[derive(Deserialize)]
struct DocumentInfo {
id: Uuid,
original_name: String,
current_version: i32,
deleted_at: Option<String>,
tags: Vec<Value>,
}
#[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<DocumentListItem> = 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(())
}