revert document types
This commit is contained in:
@@ -538,7 +538,6 @@ impl TestApp {
|
||||
correspondents_json: None,
|
||||
issued_at: None,
|
||||
skip_existing: false,
|
||||
document_type_id: None,
|
||||
};
|
||||
self.upload_document_with_extras(
|
||||
path,
|
||||
@@ -618,13 +617,6 @@ impl TestApp {
|
||||
body.extend(b"\r\n");
|
||||
}
|
||||
|
||||
if let Some(document_type_id) = extras.document_type_id {
|
||||
body.extend(format!("--{boundary}\r\n").as_bytes());
|
||||
body.extend(b"Content-Disposition: form-data; name=\"document_type_id\"\r\n\r\n");
|
||||
body.extend(document_type_id.to_string().as_bytes());
|
||||
body.extend(b"\r\n");
|
||||
}
|
||||
|
||||
if extras.skip_existing {
|
||||
body.extend(format!("--{boundary}\r\n").as_bytes());
|
||||
body.extend(b"Content-Disposition: form-data; name=\"skip_existing\"\r\n\r\ntrue\r\n");
|
||||
@@ -674,7 +666,6 @@ pub struct UploadExtras<'a> {
|
||||
pub correspondents_json: Option<&'a str>,
|
||||
pub issued_at: Option<&'a str>,
|
||||
pub skip_existing: bool,
|
||||
pub document_type_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl<'a> UploadExtras<'a> {
|
||||
@@ -686,7 +677,6 @@ impl<'a> UploadExtras<'a> {
|
||||
correspondents_json: None,
|
||||
issued_at: None,
|
||||
skip_existing: false,
|
||||
document_type_id: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
mod common;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::http::StatusCode;
|
||||
use common::{acquire_db_lock, body_to_vec, TestApp};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct DocumentTypeInfo {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct ApiErrorResponse {
|
||||
error: String,
|
||||
#[serde(default)]
|
||||
code: Option<String>,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn document_type_crud_flow() -> Result<()> {
|
||||
let _lock = acquire_db_lock().await;
|
||||
let app = TestApp::new().await?;
|
||||
|
||||
let password = "doctypecrud";
|
||||
let username = "doctype_admin";
|
||||
app.insert_user(username, password, "admin").await?;
|
||||
let token = app.login_token(username, password).await?;
|
||||
|
||||
let create_invoices = app
|
||||
.post_json(
|
||||
"/api/document-types",
|
||||
&json!({"name": "Invoices"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(create_invoices.status(), StatusCode::CREATED);
|
||||
let invoices_body = body_to_vec(create_invoices.into_body()).await?;
|
||||
let invoices: DocumentTypeInfo = serde_json::from_slice(&invoices_body)?;
|
||||
let invoices_id = invoices.id;
|
||||
|
||||
let create_receipts = app
|
||||
.post_json(
|
||||
"/api/document-types",
|
||||
&json!({"name": "Receipts"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(create_receipts.status(), StatusCode::CREATED);
|
||||
let receipts_body = body_to_vec(create_receipts.into_body()).await?;
|
||||
let receipts: DocumentTypeInfo = serde_json::from_slice(&receipts_body)?;
|
||||
|
||||
let list_resp = app.get("/api/document-types", Some(&token)).await?;
|
||||
assert!(list_resp.status().is_success());
|
||||
let list_body = body_to_vec(list_resp.into_body()).await?;
|
||||
let mut all_types: Vec<DocumentTypeInfo> = serde_json::from_slice(&list_body)?;
|
||||
all_types.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
assert_eq!(all_types.len(), 2);
|
||||
assert_eq!(all_types[0].name, "Invoices");
|
||||
assert_eq!(all_types[1].name, "Receipts");
|
||||
|
||||
let update_resp = app
|
||||
.patch_json(
|
||||
&format!("/api/document-types/{}", invoices_id),
|
||||
&json!({"name": "Bills"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(update_resp.status(), StatusCode::OK);
|
||||
let update_body = body_to_vec(update_resp.into_body()).await?;
|
||||
let updated: DocumentTypeInfo = serde_json::from_slice(&update_body)?;
|
||||
assert_eq!(updated.id, invoices_id);
|
||||
assert_eq!(updated.name, "Bills");
|
||||
|
||||
let conflict_resp = app
|
||||
.patch_json(
|
||||
&format!("/api/document-types/{}", receipts.id),
|
||||
&json!({"name": "Bills"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(conflict_resp.status(), StatusCode::CONFLICT);
|
||||
let conflict_body = body_to_vec(conflict_resp.into_body()).await?;
|
||||
let conflict_json: ApiErrorResponse = serde_json::from_slice(&conflict_body)?;
|
||||
assert_eq!(
|
||||
conflict_json.error,
|
||||
"a document type with that name already exists"
|
||||
);
|
||||
assert_eq!(
|
||||
conflict_json.code.as_deref(),
|
||||
Some("duplicate_document_type")
|
||||
);
|
||||
|
||||
let delete_receipts = app
|
||||
.delete(
|
||||
&format!("/api/document-types/{}", receipts.id),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(delete_receipts.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
let list_after_resp = app.get("/api/document-types", Some(&token)).await?;
|
||||
assert!(list_after_resp.status().is_success());
|
||||
let list_after_body = body_to_vec(list_after_resp.into_body()).await?;
|
||||
let list_after: Vec<DocumentTypeInfo> = serde_json::from_slice(&list_after_body)?;
|
||||
assert_eq!(list_after.len(), 1);
|
||||
assert_eq!(list_after[0].id, invoices_id);
|
||||
assert_eq!(list_after[0].name, "Bills");
|
||||
|
||||
let delete_bills = app
|
||||
.delete(
|
||||
&format!("/api/document-types/{}", invoices_id),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(delete_bills.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
let final_list_resp = app.get("/api/document-types", Some(&token)).await?;
|
||||
let final_list_body = body_to_vec(final_list_resp.into_body()).await?;
|
||||
let final_list: Vec<DocumentTypeInfo> = serde_json::from_slice(&final_list_body)?;
|
||||
assert!(final_list.is_empty());
|
||||
|
||||
app.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -30,21 +30,11 @@ struct DocumentInfo {
|
||||
deleted_at: Option<String>,
|
||||
issued_at: Option<String>,
|
||||
metadata: Value,
|
||||
#[serde(default)]
|
||||
document_type_id: Option<Uuid>,
|
||||
#[serde(default)]
|
||||
document_type: Option<DocumentTypeInfo>,
|
||||
tags: Vec<TagSummary>,
|
||||
#[serde(default)]
|
||||
current_version: Option<DocumentVersionPayload>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DocumentTypeInfo {
|
||||
id: Uuid,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct DocumentVersionPayload {
|
||||
id: Uuid,
|
||||
@@ -72,8 +62,6 @@ struct DocumentAssetInfo {
|
||||
struct DocumentListItem {
|
||||
id: Uuid,
|
||||
#[serde(default)]
|
||||
document_type_id: Option<Uuid>,
|
||||
#[serde(default)]
|
||||
current_version: Option<DocumentVersionPayload>,
|
||||
}
|
||||
|
||||
@@ -186,7 +174,6 @@ async fn upload_and_list_document() -> Result<()> {
|
||||
assert_eq!(detail.document.title, "doc");
|
||||
assert_eq!(detail.document.deleted_at, None);
|
||||
assert!(detail.document.issued_at.is_none());
|
||||
assert!(detail.document.document_type_id.is_none());
|
||||
assert!(detail.document.tags.is_empty());
|
||||
let current_version = detail
|
||||
.document
|
||||
@@ -420,7 +407,6 @@ async fn upload_skips_existing_when_requested() -> Result<()> {
|
||||
correspondents_json: None,
|
||||
issued_at: None,
|
||||
skip_existing: false,
|
||||
document_type_id: None,
|
||||
};
|
||||
|
||||
let first_upload = app
|
||||
@@ -471,7 +457,6 @@ async fn upload_skips_existing_when_requested() -> Result<()> {
|
||||
correspondents_json: None,
|
||||
issued_at: None,
|
||||
skip_existing: true,
|
||||
document_type_id: None,
|
||||
};
|
||||
|
||||
let skip_resp = app
|
||||
@@ -1453,298 +1438,6 @@ async fn list_documents_by_status_filter() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn document_upload_and_patch_document_type() -> Result<()> {
|
||||
let _lock = acquire_db_lock().await;
|
||||
let app = TestApp::new().await?;
|
||||
|
||||
let password = "doctypeupload";
|
||||
let username = "doctype_user";
|
||||
app.insert_user(username, password, "admin").await?;
|
||||
let token = app.login_token(username, password).await?;
|
||||
|
||||
let invoices_resp = app
|
||||
.post_json(
|
||||
"/api/document-types",
|
||||
&json!({"name": "Invoices"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(invoices_resp.status(), StatusCode::CREATED);
|
||||
let invoices_body = body_to_vec(invoices_resp.into_body()).await?;
|
||||
let invoices: DocumentTypeInfo = serde_json::from_slice(&invoices_body)?;
|
||||
|
||||
let receipts_resp = app
|
||||
.post_json(
|
||||
"/api/document-types",
|
||||
&json!({"name": "Receipts"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(receipts_resp.status(), StatusCode::CREATED);
|
||||
let receipts_body = body_to_vec(receipts_resp.into_body()).await?;
|
||||
let receipts: DocumentTypeInfo = serde_json::from_slice(&receipts_body)?;
|
||||
|
||||
let upload_extras = UploadExtras {
|
||||
title: Some("Invoice #1"),
|
||||
metadata_json: None,
|
||||
tag_ids_json: None,
|
||||
correspondents_json: None,
|
||||
issued_at: None,
|
||||
skip_existing: false,
|
||||
document_type_id: Some(invoices.id),
|
||||
};
|
||||
|
||||
let upload_resp = app
|
||||
.upload_document_with_extras(
|
||||
"/api/documents",
|
||||
"invoice.pdf",
|
||||
"application/pdf",
|
||||
b"invoice-bytes",
|
||||
None,
|
||||
upload_extras,
|
||||
&token,
|
||||
)
|
||||
.await?;
|
||||
assert!(upload_resp.status().is_success());
|
||||
let upload_body = body_to_vec(upload_resp.into_body()).await?;
|
||||
let mut detail: DocumentDetail = serde_json::from_slice(&upload_body)?;
|
||||
assert_eq!(detail.document.document_type_id, Some(invoices.id));
|
||||
let doc_type_info = detail
|
||||
.document
|
||||
.document_type
|
||||
.as_ref()
|
||||
.expect("document type present");
|
||||
assert_eq!(doc_type_info.name, "Invoices");
|
||||
|
||||
let document_id = detail.document.id;
|
||||
|
||||
let update_resp = app
|
||||
.patch_json(
|
||||
&format!("/api/documents/{}", document_id),
|
||||
&json!({"document_type_id": receipts.id}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(update_resp.status().is_success());
|
||||
let update_body = body_to_vec(update_resp.into_body()).await?;
|
||||
detail = serde_json::from_slice(&update_body)?;
|
||||
assert_eq!(detail.document.document_type_id, Some(receipts.id));
|
||||
assert_eq!(
|
||||
detail
|
||||
.document
|
||||
.document_type
|
||||
.as_ref()
|
||||
.map(|info| info.name.as_str()),
|
||||
Some("Receipts")
|
||||
);
|
||||
|
||||
let clear_resp = app
|
||||
.patch_json(
|
||||
&format!("/api/documents/{}", document_id),
|
||||
&json!({"document_type_id": null}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(clear_resp.status().is_success());
|
||||
let clear_body = body_to_vec(clear_resp.into_body()).await?;
|
||||
detail = serde_json::from_slice(&clear_body)?;
|
||||
assert!(detail.document.document_type_id.is_none());
|
||||
assert!(detail.document.document_type.is_none());
|
||||
|
||||
let invalid_id = Uuid::new_v4();
|
||||
let invalid_resp = app
|
||||
.patch_json(
|
||||
&format!("/api/documents/{}", document_id),
|
||||
&json!({"document_type_id": invalid_id}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(invalid_resp.status(), StatusCode::BAD_REQUEST);
|
||||
let invalid_body = body_to_vec(invalid_resp.into_body()).await?;
|
||||
let invalid_error: ApiErrorResponse = serde_json::from_slice(&invalid_body)?;
|
||||
assert_eq!(
|
||||
invalid_error.error,
|
||||
"document_type_id does not exist for this tenant"
|
||||
);
|
||||
|
||||
let final_detail_resp = app
|
||||
.get(&format!("/api/documents/{}", document_id), Some(&token))
|
||||
.await?;
|
||||
let final_detail_body = body_to_vec(final_detail_resp.into_body()).await?;
|
||||
let final_detail: DocumentDetail = serde_json::from_slice(&final_detail_body)?;
|
||||
assert!(final_detail.document.document_type_id.is_none());
|
||||
assert!(final_detail.document.document_type.is_none());
|
||||
|
||||
app.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_documents_filtered_by_document_type() -> Result<()> {
|
||||
let _lock = acquire_db_lock().await;
|
||||
let app = TestApp::new().await?;
|
||||
|
||||
let password = "doctypefilter";
|
||||
app.insert_user("doctypeuser", password, "admin").await?;
|
||||
let token = app.login_token("doctypeuser", password).await?;
|
||||
|
||||
let invoices_resp = app
|
||||
.post_json(
|
||||
"/api/document-types",
|
||||
&json!({"name": "Invoices"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(invoices_resp.status(), StatusCode::CREATED);
|
||||
let invoices_body = body_to_vec(invoices_resp.into_body()).await?;
|
||||
let invoices: DocumentTypeInfo = serde_json::from_slice(&invoices_body)?;
|
||||
|
||||
let receipts_resp = app
|
||||
.post_json(
|
||||
"/api/document-types",
|
||||
&json!({"name": "Receipts"}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(receipts_resp.status(), StatusCode::CREATED);
|
||||
let receipts_body = body_to_vec(receipts_resp.into_body()).await?;
|
||||
let receipts: DocumentTypeInfo = serde_json::from_slice(&receipts_body)?;
|
||||
assert_eq!(invoices.name, "Invoices");
|
||||
assert_eq!(receipts.name, "Receipts");
|
||||
|
||||
let doc_a = app
|
||||
.upload_document(
|
||||
"/api/documents",
|
||||
"invoice-a.pdf",
|
||||
"application/pdf",
|
||||
b"invoice-a",
|
||||
None,
|
||||
&token,
|
||||
)
|
||||
.await?;
|
||||
let doc_a_body = body_to_vec(doc_a.into_body()).await?;
|
||||
let detail_a: DocumentDetail = serde_json::from_slice(&doc_a_body)?;
|
||||
|
||||
let doc_b = app
|
||||
.upload_document(
|
||||
"/api/documents",
|
||||
"receipt-b.pdf",
|
||||
"application/pdf",
|
||||
b"receipt-b",
|
||||
None,
|
||||
&token,
|
||||
)
|
||||
.await?;
|
||||
let doc_b_body = body_to_vec(doc_b.into_body()).await?;
|
||||
let detail_b: DocumentDetail = serde_json::from_slice(&doc_b_body)?;
|
||||
|
||||
let doc_c = app
|
||||
.upload_document(
|
||||
"/api/documents",
|
||||
"notes.txt",
|
||||
"text/plain",
|
||||
b"notes",
|
||||
None,
|
||||
&token,
|
||||
)
|
||||
.await?;
|
||||
let doc_c_body = body_to_vec(doc_c.into_body()).await?;
|
||||
let detail_c: DocumentDetail = serde_json::from_slice(&doc_c_body)?;
|
||||
|
||||
let assign_a = app
|
||||
.patch_json(
|
||||
&format!("/api/documents/{}", detail_a.document.id),
|
||||
&json!({"document_type_id": invoices.id}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(assign_a.status(), StatusCode::OK);
|
||||
|
||||
let assign_b = app
|
||||
.patch_json(
|
||||
&format!("/api/documents/{}", detail_b.document.id),
|
||||
&json!({"document_type_id": receipts.id}),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(assign_b.status(), StatusCode::OK);
|
||||
|
||||
let invoices_resp = app
|
||||
.get(
|
||||
&format!("/api/documents?document_types={}", invoices.id),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(invoices_resp.status().is_success());
|
||||
let invoices_list_body = body_to_vec(invoices_resp.into_body()).await?;
|
||||
let invoices_docs: Vec<DocumentListItem> = serde_json::from_slice(&invoices_list_body)?;
|
||||
assert_eq!(invoices_docs.len(), 1);
|
||||
assert_eq!(invoices_docs[0].id, detail_a.document.id);
|
||||
assert_eq!(invoices_docs[0].document_type_id, Some(invoices.id));
|
||||
|
||||
let receipts_resp = app
|
||||
.get(
|
||||
&format!("/api/documents?document_types={}", receipts.id),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(receipts_resp.status().is_success());
|
||||
let receipts_list_body = body_to_vec(receipts_resp.into_body()).await?;
|
||||
let receipts_docs: Vec<DocumentListItem> = serde_json::from_slice(&receipts_list_body)?;
|
||||
assert_eq!(receipts_docs.len(), 1);
|
||||
assert_eq!(receipts_docs[0].id, detail_b.document.id);
|
||||
assert_eq!(receipts_docs[0].document_type_id, Some(receipts.id));
|
||||
|
||||
let combined_resp = app
|
||||
.get(
|
||||
&format!(
|
||||
"/api/documents?document_types={},{}",
|
||||
invoices.id, receipts.id
|
||||
),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(combined_resp.status().is_success());
|
||||
let combined_body = body_to_vec(combined_resp.into_body()).await?;
|
||||
let combined_docs: Vec<DocumentListItem> = serde_json::from_slice(&combined_body)?;
|
||||
let combined_ids: Vec<Uuid> = combined_docs.iter().map(|doc| doc.id).collect();
|
||||
assert!(combined_ids.contains(&detail_a.document.id));
|
||||
assert!(combined_ids.contains(&detail_b.document.id));
|
||||
assert!(!combined_ids.contains(&detail_c.document.id));
|
||||
|
||||
let refreshed_a = app
|
||||
.get(
|
||||
&format!("/api/documents/{}", detail_a.document.id),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(refreshed_a.status().is_success());
|
||||
let refreshed_a_body = body_to_vec(refreshed_a.into_body()).await?;
|
||||
let refreshed_a_detail: DocumentDetail = serde_json::from_slice(&refreshed_a_body)?;
|
||||
assert_eq!(
|
||||
refreshed_a_detail.document.document_type_id,
|
||||
Some(invoices.id)
|
||||
);
|
||||
|
||||
let refreshed_b = app
|
||||
.get(
|
||||
&format!("/api/documents/{}", detail_b.document.id),
|
||||
Some(&token),
|
||||
)
|
||||
.await?;
|
||||
assert!(refreshed_b.status().is_success());
|
||||
let refreshed_b_body = body_to_vec(refreshed_b.into_body()).await?;
|
||||
let refreshed_b_detail: DocumentDetail = serde_json::from_slice(&refreshed_b_body)?;
|
||||
assert_eq!(
|
||||
refreshed_b_detail.document.document_type_id,
|
||||
Some(receipts.id)
|
||||
);
|
||||
|
||||
app.cleanup().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn restore_document_to_original_and_custom_folder() -> Result<()> {
|
||||
let _lock = acquire_db_lock().await;
|
||||
|
||||
Reference in New Issue
Block a user