Schema changes: - Rename items.size to items.uncompressed_size for clarity - Add compressed_size (INTEGER NULL) - tracks compressed file size on disk - Add closed (BOOLEAN NOT NULL DEFAULT 1) - tracks whether item is fully written - Existing items default to closed=true via migration Lifecycle: - Items created with closed=false, set to true on successful save/import - Compressed size captured via fs::metadata() after compression writer closes - Truncated uploads (413) get compressed_size set, closed=true, uncompressed_size=None - Update command now backfills both uncompressed_size and compressed_size Also includes bug fixes and dedup from prior review: - Fix stream_raw_content_response using uncompressed_size for raw byte Content-Length - ApiResponse::ok()/empty() constructors, TryFrom<ItemWithMeta> for ItemInfo - tag_names() method on ItemWithMeta, meta_filter() on Settings - Fix .unwrap() panics in compression engine Read/Write impls - Fix TOCTOU race in stream_raw_content_response (now uses compressed_size) - Fix swallowed write errors in meta plugins (digest, magic_file, exec) - Fix term::stderr().unwrap() panic in item_service - Deduplicate ItemService::new() calls across 20 API handlers - ImportMeta supports #[serde(alias = "size")] for backward compat All 75 tests, 67 doc tests pass. Clippy clean.
97 lines
2.8 KiB
Rust
97 lines
2.8 KiB
Rust
#[cfg(test)]
|
|
mod export_tar_tests {
|
|
use crate::db::{Item, Meta, Tag};
|
|
use crate::export_tar::{common_tags, export_name};
|
|
use crate::services::types::ItemWithMeta;
|
|
use chrono::Utc;
|
|
|
|
fn make_item_with_tags(id: i64, tags: Vec<&str>) -> ItemWithMeta {
|
|
ItemWithMeta {
|
|
item: Item {
|
|
id: Some(id),
|
|
ts: Utc::now(),
|
|
uncompressed_size: Some(100),
|
|
compressed_size: Some(80),
|
|
closed: true,
|
|
compression: "raw".to_string(),
|
|
},
|
|
tags: tags
|
|
.into_iter()
|
|
.map(|t| Tag {
|
|
id: 0,
|
|
name: t.to_string(),
|
|
})
|
|
.collect(),
|
|
meta: Vec::new(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_common_tags_empty() {
|
|
let items: Vec<ItemWithMeta> = Vec::new();
|
|
assert!(common_tags(&items).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_common_tags_single_item() {
|
|
let items = vec![make_item_with_tags(1, vec!["foo", "bar"])];
|
|
let tags = common_tags(&items);
|
|
assert_eq!(tags, vec!["bar", "foo"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_common_tags_intersection() {
|
|
let items = vec![
|
|
make_item_with_tags(1, vec!["foo", "bar", "baz"]),
|
|
make_item_with_tags(2, vec!["foo", "bar", "qux"]),
|
|
make_item_with_tags(3, vec!["foo", "baz"]),
|
|
];
|
|
let tags = common_tags(&items);
|
|
assert_eq!(tags, vec!["foo"]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_common_tags_no_intersection() {
|
|
let items = vec![
|
|
make_item_with_tags(1, vec!["foo"]),
|
|
make_item_with_tags(2, vec!["bar"]),
|
|
];
|
|
let tags = common_tags(&items);
|
|
assert!(tags.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_export_name_with_arg() {
|
|
let items = vec![make_item_with_tags(1, vec!["foo"])];
|
|
let name = export_name(&Some("mybackup".to_string()), &items);
|
|
assert_eq!(name, "mybackup");
|
|
}
|
|
|
|
#[test]
|
|
fn test_export_name_default_with_tags() {
|
|
let items = vec![
|
|
make_item_with_tags(1, vec!["foo", "bar"]),
|
|
make_item_with_tags(2, vec!["foo", "baz"]),
|
|
];
|
|
let name = export_name(&None, &items);
|
|
assert_eq!(name, "export_foo");
|
|
}
|
|
|
|
#[test]
|
|
fn test_export_name_default_no_common_tags() {
|
|
let items = vec![
|
|
make_item_with_tags(1, vec!["foo"]),
|
|
make_item_with_tags(2, vec!["bar"]),
|
|
];
|
|
let name = export_name(&None, &items);
|
|
assert_eq!(name, "export");
|
|
}
|
|
|
|
#[test]
|
|
fn test_export_name_default_empty() {
|
|
let items: Vec<ItemWithMeta> = Vec::new();
|
|
let name = export_name(&None, &items);
|
|
assert_eq!(name, "export");
|
|
}
|
|
}
|