use std::fs;
use hayson_engine::database::Database;
use serde_json::json;
use tempfile::TempDir;
const BYTES: &str = "Ynl0ZXM="; const OTHER: &str = "b3RoZXI=";
fn docs() -> (TempDir, Database) {
let dir = TempDir::new().unwrap();
let mut database = Database::open(dir.path()).unwrap();
database
.create_table(
"docs",
json!({
"schemaVersion": 1,
"table": "docs",
"primaryKey": "id",
"fields": { "id": { "type": "string" }, "cover": { "type": "image" } },
}),
)
.unwrap();
(dir, database)
}
fn put(database: &mut Database, id: &str, content: &str, path: &str) -> Result<(), String> {
database
.table_mut("docs")
.unwrap()
.put(
&json!({ "id": id, "cover": { "content": content, "virtualPath": path } }),
false,
)
.map(|_| ())
.map_err(|error| error.to_string())
}
#[test]
fn reads_back_the_bytes_and_refuses_a_path_outside_the_image_directory() {
let (_dir, mut database) = docs();
put(&mut database, "d1", BYTES, "/a.png").unwrap();
let table = database.table("docs").unwrap();
let name = table.list_images().unwrap()[0].physical_path.clone();
assert_eq!(table.read_image(&name).unwrap(), Some(b"bytes".to_vec()));
assert_eq!(table.read_image("0123456789abcdef.bin").unwrap(), None);
assert!(table.read_image("../hayson.json").is_err());
assert!(table.read_image("/etc/passwd").is_err());
}
#[test]
fn lists_who_references_an_image_and_counts_orphans() {
let (_dir, mut database) = docs();
put(&mut database, "d1", BYTES, "/a.png").unwrap();
put(&mut database, "d2", BYTES, "/b.png").unwrap();
put(&mut database, "d3", OTHER, "/c.png").unwrap();
database.table_mut("docs").unwrap().delete("d3", false).unwrap();
let table = database.table("docs").unwrap();
let images = table.list_images().unwrap();
assert_eq!(images.len(), 2);
let shared = images.iter().find(|image| image.referenced_by.len() == 2).unwrap();
assert_eq!(shared.bytes, 5);
let orphan = images.iter().find(|image| image.referenced_by.is_empty()).unwrap();
let usage = table.image_usage().unwrap();
assert_eq!((usage.count, usage.bytes), (2, 10));
assert_eq!((usage.orphan_count, usage.orphan_bytes), (1, orphan.bytes));
}
#[test]
fn deletes_an_orphan_but_not_a_referenced_image() {
let (_dir, mut database) = docs();
put(&mut database, "d1", BYTES, "/a.png").unwrap();
put(&mut database, "d2", OTHER, "/b.png").unwrap();
database.table_mut("docs").unwrap().delete("d2", false).unwrap();
let table = database.table("docs").unwrap();
let images = table.list_images().unwrap();
let referenced = images.iter().find(|i| !i.referenced_by.is_empty()).unwrap();
let orphan = images.iter().find(|i| i.referenced_by.is_empty()).unwrap();
assert!(table.delete_image(&referenced.physical_path).is_err());
assert!(table.delete_image(&orphan.physical_path).unwrap());
assert!(!table.delete_image(&orphan.physical_path).unwrap());
assert_eq!(table.list_images().unwrap().len(), 1);
}
#[test]
fn a_missing_file_is_listed_for_its_referencing_record() {
let (_dir, mut database) = docs();
put(&mut database, "d1", BYTES, "/a.png").unwrap();
let table = database.table("docs").unwrap();
let name = table.list_images().unwrap()[0].physical_path.clone();
fs::remove_file(table.image_dir().join(&name)).unwrap();
let images = table.list_images().unwrap();
assert_eq!(images.len(), 1);
assert!(images[0].is_missing());
assert_eq!(table.image_usage().unwrap().count, 0);
}
#[test]
fn different_bytes_cannot_take_over_an_existing_content_hash_file() {
let (_dir, mut database) = docs();
put(&mut database, "d1", BYTES, "/a.png").unwrap();
let table = database.table("docs").unwrap();
let name = table.list_images().unwrap()[0].physical_path.clone();
fs::write(table.image_dir().join(&name), b"tampered").unwrap();
let refused = put(&mut database, "d2", BYTES, "/b.png").unwrap_err();
assert!(refused.contains("differ"), "{refused}");
let table = database.table("docs").unwrap();
assert!(!table.has("d2"));
assert_eq!(table.read_image(&name).unwrap(), Some(b"tampered".to_vec()));
}
#[test]
fn an_image_over_the_opened_limit_is_refused_and_one_at_the_limit_is_kept() {
let dir = TempDir::new().unwrap();
{
let mut database = Database::open(dir.path()).unwrap();
database
.create_table(
"docs",
json!({
"schemaVersion": 1,
"table": "docs",
"primaryKey": "id",
"fields": { "id": { "type": "string" }, "cover": { "type": "image" } },
}),
)
.unwrap();
database.close().unwrap();
}
let mut database = Database::open_with(
dir.path(),
hayson_engine::database::OpenOptions {
max_image_bytes: Some(5),
..Default::default()
},
)
.unwrap();
put(&mut database, "d1", BYTES, "/a.png").unwrap(); let refused = put(&mut database, "d2", "Ynl0ZXMh", "/b.png").unwrap_err(); assert!(refused.contains("6 bytes, over the 5 byte limit"), "{refused}");
assert!(!database.table("docs").unwrap().has("d2"));
assert_eq!(database.table("docs").unwrap().list_images().unwrap().len(), 1);
}