use jmap_calendars_types::{
Calendar, CalendarEvent, CalendarEventFilter, CalendarEventFilterCondition,
};
use jmap_metadata_types::MetadataTextMatch;
use jmap_types::Filter;
use serde_json::json;
#[test]
fn calendar_metadata_properties() {
let mut raw = json!({
"id": "cal1", "name": "Work", "description": null, "color": null,
"sortOrder": 0, "isSubscribed": true, "isVisible": true, "isDefault": false,
"includeInAvailability": "all", "defaultAlertsWithTime": null,
"defaultAlertsWithoutTime": null, "timeZone": null, "shareWith": null,
"myRights": {
"mayReadFreeBusy": true, "mayReadItems": true, "mayWriteAll": true,
"mayWriteOwn": true, "mayUpdatePrivate": true, "mayRSVP": true,
"mayShare": true, "mayDelete": true
},
"metadata": {"acme.example.com": {"department": "sales"}},
"privateMetadata": {"acme.example.com": {"pinned": true}}
});
let cal: Calendar = serde_json::from_value(raw.clone()).expect("decode");
assert_eq!(
cal.metadata
.as_ref()
.map(|m| &m["acme.example.com"]["department"]),
Some(&json!("sales"))
);
assert_eq!(
cal.private_metadata
.as_ref()
.map(|m| &m["acme.example.com"]["pinned"]),
Some(&json!(true))
);
assert!(cal.extra.is_empty());
let back = serde_json::to_value(&cal).expect("encode");
assert_eq!(back["metadata"], raw["metadata"]);
assert_eq!(back["privateMetadata"], raw["privateMetadata"]);
raw["metadata"] = serde_json::Value::Null;
let cal: Calendar = serde_json::from_value(raw.clone()).expect("decode null");
assert_eq!(cal.metadata, None);
assert!(
serde_json::to_value(&cal)
.expect("encode")
.get("metadata")
.is_none()
);
raw["metadata"] = json!(["x"]);
assert!(serde_json::from_value::<Calendar>(raw).is_err());
}
#[test]
fn calendar_event_metadata_properties() {
let mut raw = json!({
"id": "CE789", "calendarIds": {"cal1": true}, "title": "Quarterly Review Meeting",
"metadata": {"acme.example.com": {"approvalStatus": "pending"}},
"privateMetadata": {"acme.example.com": {"reminder": "bring slides"}}
});
let event: CalendarEvent = serde_json::from_value(raw.clone()).expect("decode");
assert_eq!(
event
.metadata
.as_ref()
.map(|m| &m["acme.example.com"]["approvalStatus"]),
Some(&json!("pending"))
);
assert_eq!(
event
.private_metadata
.as_ref()
.map(|m| &m["acme.example.com"]["reminder"]),
Some(&json!("bring slides"))
);
assert!(event.extra.is_empty());
let back = serde_json::to_value(&event).expect("encode");
assert_eq!(back["metadata"], raw["metadata"]);
assert_eq!(back["privateMetadata"], raw["privateMetadata"]);
raw["privateMetadata"] = json!(7);
assert!(serde_json::from_value::<CalendarEvent>(raw).is_err());
}
#[test]
fn metadata_condition_names_decode_and_typos_reject() {
let m = json!({"path": "acme.example.com/approvalStatus", "value": "pending"});
for (name, value) in [
("metadataExists", json!("acme.example.com")),
("privateMetadataExists", json!("acme.example.com")),
("metadataTextContains", m.clone()),
("privateMetadataTextContains", m.clone()),
("metadataTextEquals", m.clone()),
("privateMetadataTextEquals", m.clone()),
] {
let c: CalendarEventFilterCondition =
serde_json::from_value(json!({ name: value })).expect(name);
assert!(c.has_metadata_condition(), "{name}");
}
assert!(!CalendarEventFilterCondition::default().has_metadata_condition());
assert!(
serde_json::from_value::<CalendarEventFilterCondition>(json!({"metadataExist": "ns"}))
.is_err()
);
}
#[test]
fn metadata_condition_survives_operator_tree() {
let wire = json!({
"operator": "AND",
"conditions": [
{ "inCalendar": "cal1" },
{ "metadataTextEquals": { "path": "acme.example.com/approvalStatus", "value": "pending" } }
]
});
let filter: CalendarEventFilter = serde_json::from_value(wire.clone()).expect("decode");
let Filter::Operator(op) = &filter else {
panic!("expected an operator, got {filter:?}");
};
let Filter::Condition(leaf) = &op.conditions[1] else {
panic!("expected a condition");
};
assert_eq!(
leaf.metadata_text_equals,
Some(MetadataTextMatch::new(
"acme.example.com/approvalStatus",
"pending"
))
);
assert_eq!(serde_json::to_value(&filter).expect("encode"), wire);
}