Menu
Public repository / 89d76abbb01a83de2e71d39c6321fc0d7ef581be
AkurAI-Framework / crates/collections/tests/files.rs
Latest change d88533200d3e5293cc2264fb91be84f0c0c5bcaa - feat(cli): wire relations, file uploads, and expand into the auto-API by Ólafur Búi Ólafsson
//! Tests for the `File` field kind: a file field stores a `{blob,...}` JSON
//! descriptor object, accepts null/omitted JSON input, and rejects a
//! non-object/non-null value with a clear validation error.
//!
//! Each test gets its own file under `CARGO_TARGET_TMPDIR` (never `/tmp`).
use std::path::PathBuf;
use std::sync::atomic::{AtomicU32, Ordering};
use akurai_collections::{meta, Collection, Field, FieldKind, Store};
use akurai_json::Value;
static COUNTER: AtomicU32 = AtomicU32::new(0);
fn db_path() -> PathBuf {
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let mut p = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
p.push(format!("files-test-{n}-{}.db", std::process::id()));
let _ = std::fs::remove_file(&p);
p
}
/// A `documents` collection with a title and an optional `attachment` file field.
fn documents() -> Collection {
Collection::new(
"documents",
vec![
Field::new("title", FieldKind::Text).required(),
Field::new("attachment", FieldKind::File),
],
)
}
fn obj(pairs: Vec<(&str, Value)>) -> Value {
Value::Object(pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
}
fn file_descriptor() -> Value {
obj(vec![
(
"blob",
Value::Str("0123456789abcdef0123456789abcdef".into()),
),
("filename", Value::Str("note.txt".into())),
("content_type", Value::Str("text/plain".into())),
("size", Value::Int(13)),
])
}
#[test]
fn file_field_is_reported_in_meta() {
let m = meta(&[documents()]);
let json = m.to_json();
assert!(json.contains("\"type\":\"file\""), "got: {json}");
assert_eq!(FieldKind::File.as_str(), "file");
}
#[test]
fn create_stores_a_file_descriptor_object() {
let coll = documents();
let mut store = Store::open(db_path()).unwrap();
let created = store
.create(
&coll,
obj(vec![
("title", Value::Str("Spec".into())),
("attachment", file_descriptor()),
]),
)
.unwrap();
let att = created.get("attachment").expect("attachment present");
assert_eq!(
att.get("blob").and_then(Value::as_str),
Some("0123456789abcdef0123456789abcdef")
);
assert_eq!(
att.get("filename").and_then(Value::as_str),
Some("note.txt")
);
assert_eq!(att.get("size").and_then(Value::as_i64), Some(13));
}
#[test]
fn file_field_omitted_is_fine() {
let coll = documents();
let mut store = Store::open(db_path()).unwrap();
// No `attachment` key at all — accepted, and the stored record simply omits it.
let created = store
.create(&coll, obj(vec![("title", Value::Str("No file".into()))]))
.unwrap();
assert!(created.get("attachment").is_none());
}
#[test]
fn file_field_null_is_fine() {
let coll = documents();
let mut store = Store::open(db_path()).unwrap();
let created = store
.create(
&coll,
obj(vec![
("title", Value::Str("Null file".into())),
("attachment", Value::Null),
]),
)
.unwrap();
assert!(created.get("attachment").is_none());
}
#[test]
fn non_object_file_value_is_a_validation_error() {
let coll = documents();
let mut store = Store::open(db_path()).unwrap();
let err = store
.create(
&coll,
obj(vec![
("title", Value::Str("Bad".into())),
("attachment", Value::Str("just a string".into())),
]),
)
.unwrap_err();
let msg = err.to_string();
assert!(msg.contains("attachment"), "got: {msg}");
assert!(msg.contains("file"), "got: {msg}");
}
#[test]
fn update_replaces_the_file_descriptor() {
let coll = documents();
let mut store = Store::open(db_path()).unwrap();
let created = store
.create(
&coll,
obj(vec![
("title", Value::Str("Doc".into())),
("attachment", file_descriptor()),
]),
)
.unwrap();
let id = created.get("id").and_then(Value::as_i64).unwrap() as u64;
let replacement = obj(vec![
(
"blob",
Value::Str("ffffffffffffffffffffffffffffffff".into()),
),
("filename", Value::Str("new.bin".into())),
(
"content_type",
Value::Str("application/octet-stream".into()),
),
("size", Value::Int(4)),
]);
let updated = store
.update(&coll, id, obj(vec![("attachment", replacement)]))
.unwrap()
.unwrap();
assert_eq!(
updated
.get("attachment")
.and_then(|a| a.get("filename"))
.and_then(Value::as_str),
Some("new.bin")
);
// Clearing it with null drops the field.
let cleared = store
.update(&coll, id, obj(vec![("attachment", Value::Null)]))
.unwrap()
.unwrap();
assert!(cleared.get("attachment").is_none());
}