use chrono::{DateTime, Utc};
use crate::admin::AdminModel;
use crate::http::FormData;
use rustio_macros::RustioAdmin;
#[derive(Debug, RustioAdmin)]
#[allow(dead_code)]
pub struct StampedFixture {
pub id: i64,
pub title: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[test]
fn auto_timestamp_fields_are_not_editable() {
let fields = StampedFixture::FIELDS;
let created = fields
.iter()
.find(|f| f.name == "created_at")
.expect("created_at field present in FIELDS");
let updated = fields
.iter()
.find(|f| f.name == "updated_at")
.expect("updated_at field present in FIELDS");
assert!(!created.editable, "created_at must be non-editable so the form filters it out");
assert!(!updated.editable, "updated_at must be non-editable so the form filters it out");
let title = fields
.iter()
.find(|f| f.name == "title")
.expect("title field present");
assert!(title.editable, "regular fields must remain editable");
}
#[test]
fn from_form_accepts_submission_without_auto_timestamps() {
let before = Utc::now();
let form = FormData::from_urlencoded("title=hello");
let model = StampedFixture::from_form(&form).expect("from_form succeeds with no timestamps");
let after = Utc::now();
assert_eq!(model.title, "hello");
assert!(
model.created_at >= before && model.created_at <= after,
"created_at should be defaulted to Utc::now()",
);
assert!(
model.updated_at >= before && model.updated_at <= after,
"updated_at should be defaulted to Utc::now()",
);
}
#[derive(Debug, RustioAdmin)]
#[allow(dead_code)]
pub struct HardeningFixture {
pub id: i64,
pub title: String,
pub author_id: i64,
pub edition: Option<i64>,
pub published_at: DateTime<Utc>,
}
#[test]
fn invalid_number_input() {
let form = FormData::from_urlencoded(
"title=hi&author_id=abc&edition=&published_at=2026-01-01T12:00",
);
let errs = HardeningFixture::from_form(&form)
.expect_err("from_form must reject invalid i64");
assert!(
errs.iter().any(|e| e.contains("Author Id") && e.contains("number")),
"expected an Author Id number error; got: {errs:?}"
);
}
#[test]
fn invalid_optional_number_input() {
let form = FormData::from_urlencoded(
"title=hi&author_id=1&edition=abc&published_at=2026-01-01T12:00",
);
let errs = HardeningFixture::from_form(&form)
.expect_err("from_form must reject garbage in Option<i64>");
assert!(
errs.iter().any(|e| e.contains("Edition") && e.contains("number")),
"expected an Edition number error; got: {errs:?}"
);
let form2 = FormData::from_urlencoded(
"title=hi&author_id=1&edition=&published_at=2026-01-01T12:00",
);
let model = HardeningFixture::from_form(&form2)
.expect("empty Option<i64> input should NOT error");
assert_eq!(model.edition, None);
let form3 = FormData::from_urlencoded("title=hi&author_id=1&published_at=2026-01-01T12:00");
let model = HardeningFixture::from_form(&form3)
.expect("missing Option<i64> field should NOT error");
assert_eq!(model.edition, None);
}
#[test]
fn invalid_datetime_input() {
let form = FormData::from_urlencoded(
"title=hi&author_id=1&edition=&published_at=tomorrow",
);
let errs = HardeningFixture::from_form(&form)
.expect_err("from_form must reject malformed datetime");
assert!(
errs.iter().any(|e| e.contains("Published At") && e.contains("not a valid date")),
"expected a Published At date error; got: {errs:?}"
);
}
#[test]
fn whitespace_only_string_treated_as_empty() {
let form = FormData::from_urlencoded(
"title=%20%20%20&author_id=1&edition=&published_at=2026-01-01T12:00",
);
let errs = HardeningFixture::from_form(&form)
.expect_err("whitespace-only required String must trigger required error");
assert!(
errs.iter().any(|e| e.contains("Title") && e.contains("required")),
"expected a Title required error; got: {errs:?}"
);
let form = FormData::from_urlencoded(
"title=%20%20hi%20%20&author_id=1&edition=&published_at=2026-01-01T12:00",
);
let model = HardeningFixture::from_form(&form).expect("trimmed String parses");
assert_eq!(model.title, "hi", "leading/trailing whitespace must be stripped");
}