use super::*;
#[test]
fn parses_types_and_modifiers() {
let field = Field::parse("title:string^").unwrap();
assert_eq!(
(field.name.as_str(), field.ty, field.optional, field.unique),
("title", FieldType::String, false, true)
);
let field = Field::parse("summary:text?").unwrap();
assert_eq!((field.optional, field.unique), (true, false));
let field = Field::parse("code:string?^").unwrap();
assert_eq!((field.optional, field.unique), (true, true));
let field = Field::parse("author:references").unwrap();
assert_eq!(field.name, "author_id");
assert_eq!(field.target.unwrap().plural, "authors");
let field = Field::parse("settings:json?").unwrap();
assert_eq!(
(field.ty, field.optional, field.column_type().as_str()),
(FieldType::Json, true, "Option<ocre::serde_json::Value>")
);
}
#[test]
fn rejects_bad_fields() {
let error = |spec: &str| Field::parse(spec).unwrap_err().message;
assert_eq!(error("title"), "field `title` has no type");
assert_eq!(error("title:varchar"), "unknown field type `varchar` for `title`");
assert_eq!(error("Title:string"), "invalid field name `Title`");
assert_eq!(error("type:string"), "field name `type` is reserved");
assert_eq!(error("order:integer"), "field name `order` is reserved");
assert_eq!(error("id:integer"), "field name `id` is reserved");
assert_eq!(error("done:boolean?"), "boolean field `done` cannot be optional");
assert_eq!(error("2x:references"), "invalid field name `2x`");
assert!(parse_fields(&["a:string".into(), "a:text".into()]).is_err(), "duplicate");
assert_eq!(error("avatar:attachment^"), "attachment `avatar` cannot be unique");
assert_eq!(error("edit:attachment"), "attachment name `edit` clashes with a scaffold route");
assert_eq!(error("settings:json^"), "json field `settings` cannot be unique");
let clash = parse_fields(&["avatar:attachment".into(), "avatar_size:integer".into()]).unwrap_err();
assert_eq!(clash.message, "field `avatar_size` is listed twice");
}
#[test]
fn sql_columns() {
let column = |spec: &str| Field::parse(spec).unwrap().sql_columns().join(", ");
assert_eq!(column("title:string"), "title TEXT NOT NULL");
assert_eq!(column("summary:text?"), "summary TEXT");
assert_eq!(column("pages:integer"), "pages INTEGER NOT NULL");
assert_eq!(column("rating:float?"), "rating REAL");
assert_eq!(column("done:boolean"), "done INTEGER NOT NULL DEFAULT 0");
assert_eq!(column("on:date"), "on TEXT NOT NULL");
assert_eq!(column("at:datetime?"), "at TEXT");
assert_eq!(column("author:references"), "author_id INTEGER NOT NULL REFERENCES authors(id) ON DELETE CASCADE");
assert_eq!(column("editor:references?"), "editor_id INTEGER REFERENCES editors(id) ON DELETE SET NULL");
assert_eq!(
column("avatar:attachment"),
"avatar_key TEXT NOT NULL, avatar_filename TEXT NOT NULL, avatar_content_type TEXT NOT NULL, avatar_size INTEGER NOT NULL"
);
assert_eq!(column("doc:attachment?"), "doc_key TEXT, doc_filename TEXT, doc_content_type TEXT, doc_size INTEGER");
}
#[test]
fn rust_types_labels_and_kinds() {
let field = |spec: &str| Field::parse(spec).unwrap();
assert_eq!(field("rating:float?").column_type(), "Option<f64>");
assert_eq!(field("done:boolean").column_type(), "bool");
assert_eq!(field("author:references").column_type(), "i64");
assert_eq!(field("author:references").label(), "Author");
assert_eq!(field("owner_id:integer").label(), "Owner id");
assert!(field("on:date").ty.is_textual() && !field("on:date").ty.is_numeric());
assert!(field("author:references").ty.is_numeric());
let doc = field("doc:attachment?");
assert_eq!((doc.column_type().as_str(), doc.rules_const().as_str()), ("Option<Upload>", "DOC"));
assert_eq!(field("title:string").columns(), [("title".to_owned(), "String".to_owned())]);
assert_eq!(doc.columns()[3], ("doc_size".to_owned(), "Option<i64>".to_owned()));
}
#[test]
fn checks_per_type() {
let checks = |spec: &str| Field::parse(spec).unwrap().checks("x");
assert_eq!(checks("title:string"), ["v.required(\"title\", x);"]);
assert!(checks("title:string?").is_empty());
assert_eq!(checks("on:date"), ["v.date(\"on\", x);"]);
assert_eq!(checks("at:datetime?"), ["v.datetime(\"at\", x);"]);
assert_eq!(checks("pages:integer"), ["v.safe_integer(\"pages\", x);"]);
assert!(checks("rating:float").is_empty() && checks("done:boolean").is_empty());
assert_eq!(checks("avatar:attachment"), ["v.file(\"avatar\", x, &AVATAR);"]);
}
#[test]
fn enums_list_their_values() {
let field = Field::parse("review_state:enum:draft,in_review?").unwrap();
let enumeration = field.enumeration.clone().unwrap();
assert_eq!(
(enumeration.type_name.as_str(), enumeration.values.as_slice()),
("ReviewState", &["draft".to_owned(), "in_review".to_owned()][..])
);
assert_eq!(Enumeration::variant("in_review"), "InReview");
assert_eq!(
(field.rust_type(), field.column_type().as_str(), field.optional),
("ReviewState", "Option<ReviewState>", true)
);
assert_eq!(field.sql_columns(), ["review_state TEXT CHECK (review_state IN ('draft', 'in_review'))"]);
assert!(field.checks("x").is_empty());
let error = |spec: &str| Field::parse(spec).unwrap_err().message;
assert_eq!(error("status:enum"), "enum `status` has no values");
assert_eq!(error("status:enum:a,a"), "invalid values `a,a` for enum `status`");
assert_eq!(error("status:enum:a,B-c"), "invalid values `a,B-c` for enum `status`");
assert_eq!(error("status:enum:a,b^"), "enum `status` cannot be unique");
assert!(error("title:string:x").starts_with("type `string` of `title` takes no `:x`"));
}
#[test]
fn lock_version_and_rich_text_rules() {
assert_eq!(Field::parse("lock_version:integer").unwrap().ty, FieldType::LockVersion);
for spec in ["lock_version:integer?", "lock_version:integer^", "lock_version:string"] {
assert_eq!(Field::parse(spec).unwrap_err().message, "`lock_version` must be `lock_version:integer`", "{spec}");
}
assert_eq!(Field::parse("body:rich_text").unwrap().ty, FieldType::RichText);
assert_eq!(Field::parse("body:rich_text^").unwrap_err().message, "rich_text field `body` cannot be unique");
}
#[test]
fn polymorphic_fields_expand_to_a_type_and_an_id() {
let specs = |specs: &[&str]| parse_fields(&specs.iter().map(|s| (*s).to_owned()).collect::<Vec<_>>());
let fields = specs(&["commentable:polymorphic:post,blog_post?"]).unwrap();
assert_eq!(
(fields[0].name.as_str(), fields[0].ty, fields[0].optional),
("commentable_type", FieldType::Enum, true)
);
assert_eq!(fields[0].enumeration.as_ref().unwrap().values, ["post", "blog_post"]);
assert_eq!((fields[1].name.as_str(), fields[1].ty), ("commentable_id", FieldType::Integer));
assert_eq!(fields[1].polymorphic.as_ref().unwrap()[1].plural, "blog_posts");
assert_eq!(
(fields[1].association(), fields[0].association(), fields[1].label()),
("commentable", "commentable", "Commentable".to_owned())
);
let error = |spec: &str| specs(&[spec]).unwrap_err().message;
assert_eq!(error("owner:polymorphic:post^"), "polymorphic `owner` cannot be unique");
assert_eq!(error("owner:polymorphic"), "polymorphic `owner` has no models");
assert_eq!(error("owner:polymorphic:1post"), "invalid model name `1post`");
assert_eq!(error("owner:polymorphic:post,post"), "invalid values `post,post` for enum `owner_type`");
assert_eq!(
specs(&["owner:polymorphic:post", "owner_id:integer"]).unwrap_err().message,
"field `owner_id` is listed twice"
);
}
#[test]
fn many_attachments_are_set_apart_from_the_fields() {
let specs = |specs: &[&str]| parse_model_fields(&specs.iter().map(|s| (*s).to_owned()).collect::<Vec<_>>());
let (fields, many) = specs(&["title:string", "photos:attachments", "scans:attachments"]).unwrap();
assert_eq!((fields.len(), many), (1, vec!["photos".to_owned(), "scans".to_owned()]));
let error = |spec: &[&str]| specs(spec).unwrap_err().message;
assert_eq!(error(&["photos:attachments?"]), "`photos:attachments` takes no `?` or `^`");
assert_eq!(error(&["photo:attachments"]), "invalid attachments name `photo`");
assert_eq!(error(&["Photos:attachments"]), "invalid attachments name `Photos`");
assert_eq!(error(&["photos:attachments", "photos:attachments"]), "field `photos` is listed twice");
assert_eq!(error(&["title:attachments", "x:bogus"]), "invalid attachments name `title`");
assert!(error(&["photos:attachments", "x:bogus"]).starts_with("unknown field type `bogus`"));
}