patch tags

This commit is contained in:
2025-10-12 01:52:30 +02:00
parent 1b950a8f9a
commit f30e455c2d
20 changed files with 1306 additions and 161 deletions
+5
View File
@@ -37,6 +37,7 @@ pub struct StoredObject {
pub key: String,
pub bytes: Vec<u8>,
pub content_type: Option<String>,
pub content_disposition: Option<String>,
}
#[derive(Default)]
@@ -51,11 +52,13 @@ impl ObjectStorage for FakeStorage {
key: &str,
bytes: Vec<u8>,
content_type: Option<String>,
content_disposition: Option<String>,
) -> Result<()> {
let stored = StoredObject {
key: key.to_string(),
bytes,
content_type,
content_disposition,
};
let mut guard = self.objects.lock().await;
guard.insert(stored.key.clone(), stored);
@@ -119,6 +122,8 @@ impl TestApp {
jwt_issuer: "test-issuer".to_string(),
jwt_audience: "test-audience".to_string(),
jwt_expiry_minutes: 60,
download_token_audience: "test-download".to_string(),
download_token_expiry_minutes: 60,
refresh_token_expiry_days: 30,
refresh_cookie_secure: false,
refresh_cookie_domain: None,
+13
View File
@@ -22,6 +22,7 @@ struct DocumentInfo {
deleted_at: Option<String>,
issued_at: Option<String>,
tags: Vec<TagSummary>,
download_path: String,
}
#[derive(Deserialize)]
@@ -42,6 +43,7 @@ struct DocumentAssetInfo {
struct DocumentListItem {
id: Uuid,
current_version: i32,
download_path: String,
}
#[derive(Deserialize)]
@@ -154,6 +156,7 @@ async fn upload_and_list_document() -> Result<()> {
assert_eq!(detail.document.deleted_at, None);
assert!(detail.document.issued_at.is_none());
assert!(detail.document.tags.is_empty());
assert!(detail.document.download_path.starts_with("/download/"));
assert_eq!(detail.current_version.size_bytes, file_bytes.len() as i64);
assert!(detail.assets.is_empty());
@@ -173,6 +176,7 @@ async fn upload_and_list_document() -> Result<()> {
let item = list.pop().unwrap();
assert_eq!(item.id, detail.document.id);
assert_eq!(item.current_version, 1);
assert!(item.download_path.starts_with("/download/"));
let download = app
.get(
@@ -186,6 +190,15 @@ async fn upload_and_list_document() -> Result<()> {
assert!(download_info.url.contains(&detail.current_version.s3_key));
assert_eq!(download_info.filename, "doc.txt");
let redirect = app.get(&detail.document.download_path, None).await?;
assert_eq!(redirect.status(), StatusCode::TEMPORARY_REDIRECT);
let location = redirect
.headers()
.get("location")
.expect("redirect location header");
let location = location.to_str().expect("location header utf8");
assert!(location.contains(&detail.current_version.s3_key));
app.cleanup().await?;
Ok(())
}
+53 -1
View File
@@ -21,11 +21,16 @@ struct DocumentInfo {
#[derive(Deserialize)]
struct TagInfo {
label: String,
#[allow(dead_code)]
color: Option<String>,
}
#[derive(Deserialize)]
struct TagResponse {
id: Uuid,
label: String,
color: Option<String>,
usage_count: i64,
}
#[derive(Serialize)]
@@ -75,6 +80,53 @@ async fn tag_assignment_flow() -> Result<()> {
assert_eq!(create_tag.status(), StatusCode::OK);
let body = body_to_vec(create_tag.into_body()).await?;
let tag: TagResponse = serde_json::from_slice(&body)?;
assert_eq!(tag.label, "Important");
assert_eq!(tag.color.as_deref(), Some("#FF0000"));
assert_eq!(tag.usage_count, 0);
let update = app
.patch_json(
&format!("/api/tags/{}", tag.id),
&serde_json::json!({
"label": "Critical",
"color": "#00FF00"
}),
Some(&token),
)
.await?;
let updated_status = update.status();
let updated_body = body_to_vec(update.into_body()).await?;
if updated_status != StatusCode::OK {
panic!(
"update tag failed: {}",
String::from_utf8_lossy(&updated_body)
);
}
let updated: TagResponse = serde_json::from_slice(&updated_body)?;
assert_eq!(updated.label, "Critical");
assert_eq!(updated.color.as_deref(), Some("#00FF00"));
assert_eq!(updated.usage_count, 0);
let clear_color = app
.patch_json(
&format!("/api/tags/{}", tag.id),
&serde_json::json!({
"color": null
}),
Some(&token),
)
.await?;
let cleared_status = clear_color.status();
let cleared_body = body_to_vec(clear_color.into_body()).await?;
if cleared_status != StatusCode::OK {
panic!(
"clear color failed: {}",
String::from_utf8_lossy(&cleared_body)
);
}
let cleared: TagResponse = serde_json::from_slice(&cleared_body)?;
assert_eq!(cleared.color, None);
assert_eq!(cleared.usage_count, 0);
let assign = app
.post_json(
@@ -97,7 +149,7 @@ async fn tag_assignment_flow() -> Result<()> {
let refreshed_body = body_to_vec(refreshed.into_body()).await?;
let refreshed_detail: DocumentDetail = serde_json::from_slice(&refreshed_body)?;
assert_eq!(refreshed_detail.document.tags.len(), 1);
assert_eq!(refreshed_detail.document.tags[0].label, "Important");
assert_eq!(refreshed_detail.document.tags[0].label, "Critical");
let remove = app
.delete(