mod common;
use common::payload::{payload, round_trip_by_name, Fill, DISPATCHED, EXCLUDED};
use common::rust_source::parse_items;
use serde_json::json;
use std::collections::BTreeSet;
#[test]
fn every_spec_type_decodes_with_every_optional_field_present() {
let spec = common::load();
let mut failures = Vec::new();
for name in spec.types.keys() {
if EXCLUDED.iter().any(|(excluded, _)| excluded == name) {
continue;
}
let full = match payload(&spec, name, Fill::All, 0) {
Ok(full) => full,
Err(why) => {
failures.push(format!(" {name}: could not build a payload — {why}"));
continue;
}
};
if let Err(why) = round_trip_by_name(name, &full) {
failures.push(format!(" {name}: {why}"));
}
}
assert!(
failures.is_empty(),
"{} of {} spec types rejected a payload containing every field the spec \
lists for them:\n{}",
failures.len(),
spec.types.len(),
failures.join("\n")
);
}
#[test]
fn the_maximal_payloads_carry_far_more_than_the_required_ones() {
let spec = common::load();
let (mut required, mut all) = (0_usize, 0_usize);
for name in spec.types.keys() {
for (fill, total) in [(Fill::Required, &mut required), (Fill::All, &mut all)] {
if let Ok(value) = payload(&spec, name, fill, 0) {
*total += value.as_object().map_or(0, serde_json::Map::len);
}
}
}
assert!(
all > required * 2,
"maximal payloads carry {all} fields against the required-only {required}; \
they are supposed to add every optional field on top, so this sweep is \
no longer testing much more than the round-trip one"
);
}
#[test]
fn no_spec_optional_field_is_required_in_rust() {
let spec = common::load();
let items = parse_items();
let mut brittle = Vec::new();
for (name, fields) in &spec.types {
let Some(item) = items.get(name) else {
continue; };
if item.is_enum && item.fields.is_empty() {
continue;
}
for (field_name, spec_field) in fields {
if !spec_field.optional() {
continue;
}
let Some(field) = item.fields.get(field_name) else {
continue; };
let optional_in_rust = field.ty.starts_with("Option<")
|| field.ty.contains("Option <")
|| field.has_default
|| field.flattened;
if !optional_in_rust {
brittle.push(format!(
" {name}.{field_name}: spec says optional, Rust has `{}` \
with no #[serde(default)]",
field.ty
));
}
}
}
assert!(
brittle.is_empty(),
"{} spec-optional field(s) cannot be absent from the wire. Any server \
that omits one fails to decode the whole object — make it an `Option` \
or give it `#[serde(default)]`:\n{}",
brittle.len(),
brittle.join("\n")
);
}
#[test]
fn the_optionality_scan_reaches_the_spec_types() {
let spec = common::load();
let items = parse_items();
let seen: BTreeSet<&String> = spec
.types
.keys()
.filter(|name| items.contains_key(*name))
.collect();
assert!(
seen.len() > 300,
"the scan found only {} of the {} spec types in the source tree; it \
would report success while checking almost nothing",
seen.len(),
spec.types.len()
);
let checked: usize = spec
.types
.iter()
.filter_map(|(name, fields)| items.get(name).map(|item| (fields, item)))
.map(|(fields, item)| {
fields
.iter()
.filter(|(f, sf)| sf.optional() && item.fields.contains_key(*f))
.count()
})
.sum();
assert!(
checked > 900,
"only {checked} optional fields were reachable; expected the great \
majority of the spec's optional fields"
);
}
#[test]
fn a_chat_member_administrator_decodes_without_any_optional_rights() {
let admin: rustigram_types::chat_member::ChatMember = serde_json::from_value(json!({
"status": "administrator",
"user": { "id": 1, "is_bot": false, "first_name": "A" },
"can_be_edited": false,
"is_anonymous": false,
"can_manage_chat": true,
"can_delete_messages": false,
"can_manage_video_chats": false,
"can_restrict_members": false,
"can_promote_members": false,
"can_change_info": false,
"can_invite_users": false
}))
.expect("an administrator with no optional rights decodes");
let rustigram_types::chat_member::ChatMember::Administrator(admin) = admin else {
panic!("the status discriminant selected the wrong variant");
};
assert!(
!admin.can_post_messages,
"an absent right must default to not granted, never to granted"
);
}
#[test]
fn a_chat_full_info_decodes_without_accent_color_id() {
let chat: rustigram_types::chat::ChatFullInfo = serde_json::from_value(json!({
"id": -100,
"type": "supergroup",
"max_reaction_count": 11
}))
.expect("a ChatFullInfo omitting accent_color_id decodes");
assert_eq!(
chat.accent_color_id, 0,
"an absent accent colour must fall back to the default, not to garbage"
);
}
#[test]
fn the_sweep_covers_the_whole_dispatch_table() {
let spec = common::load();
let uncovered: Vec<&String> = spec
.types
.keys()
.filter(|name| {
!DISPATCHED.contains(&name.as_str())
&& !EXCLUDED.iter().any(|(e, _)| *e == name.as_str())
})
.collect();
assert!(
uncovered.is_empty(),
"{} spec type(s) are outside the dispatch table, so this sweep silently \
skips them: {uncovered:?}",
uncovered.len()
);
}