#![allow(
unused_crate_dependencies,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic,
clippy::tests_outside_test_module,
reason = "integration-test idioms: every `#[test]` is the file's contents; `expect`/`panic!`/`[0]` are the clearest way to assert invariants"
)]
use actpub_activitystreams::{Object, WithContext, kind};
use pretty_assertions::assert_eq;
fn load_fixture(name: &str) -> serde_json::Value {
let path = format!("{}/tests/fixtures/{name}.json", env!("CARGO_MANIFEST_DIR"));
let body =
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read fixture {path}: {e}"));
serde_json::from_str(&body).unwrap_or_else(|e| panic!("parse fixture {path}: {e}"))
}
fn assert_canonical_roundtrip<T>(fixture: &serde_json::Value)
where
T: serde::Serialize + for<'de> serde::Deserialize<'de>,
{
let parsed: T = serde_json::from_value(fixture.clone())
.expect("fixture must deserialise into the typed model");
let canonical = serde_json::to_value(&parsed).expect("serialise typed model");
let reparsed: T = serde_json::from_value(canonical.clone()).expect("re-parse canonical form");
let recanonical = serde_json::to_value(&reparsed).expect("re-serialise typed model");
assert_eq!(
recanonical, canonical,
"second-pass serialisation must be byte-stable",
);
}
#[test]
fn mastodon_actor_roundtrips_byte_stable() {
let raw = load_fixture("mastodon-actor");
let actor: WithContext<Object> =
serde_json::from_value(raw.clone()).expect("Mastodon actor fixture must deserialise");
assert_eq!(actor.inner.preferred_username.as_deref(), Some("alice"));
assert!(actor.inner.inbox.is_some(), "inbox typed");
assert!(actor.inner.outbox.is_some(), "outbox typed");
assert!(actor.inner.followers.is_some(), "followers typed");
assert!(actor.inner.following.is_some(), "following typed");
assert!(actor.inner.public_key.is_some(), "publicKey typed");
assert!(actor.inner.endpoints.is_some(), "endpoints typed");
assert!(actor.inner.featured.is_some(), "toot:featured typed");
assert!(
actor.inner.featured_tags.is_some(),
"toot:featuredTags typed"
);
assert_eq!(actor.inner.manually_approves_followers, Some(false));
assert_eq!(actor.inner.discoverable, Some(true));
assert_eq!(actor.inner.indexable, Some(true));
assert_eq!(actor.inner.memorial, Some(false));
assert!(actor.inner.is_kind(kind::actor::PERSON));
assert!(actor.inner.is_actor());
let pk = actor.inner.public_key.as_ref().expect("public_key");
assert_eq!(
pk.id.as_str(),
"https://mastodon.social/users/alice#main-key"
);
assert_eq!(pk.owner.as_str(), "https://mastodon.social/users/alice");
assert!(pk.public_key_pem.starts_with("-----BEGIN PUBLIC KEY-----"));
let back = serde_json::to_value(&actor).expect("re-serialise");
assert_eq!(
back, raw,
"Mastodon actor fixture must round-trip byte-stable"
);
assert_canonical_roundtrip::<WithContext<Object>>(&raw);
}
#[test]
fn fep_521a_actor_roundtrips_byte_stable() {
let raw = load_fixture("fep-521a-actor");
let actor: WithContext<Object> =
serde_json::from_value(raw.clone()).expect("FEP-521a actor fixture must deserialise");
assert_eq!(actor.inner.assertion_method.len(), 1);
assert_eq!(actor.inner.authentication.len(), 1);
let am = &actor.inner.assertion_method[0];
let key = am.as_object().expect("inline Multikey form");
assert_eq!(key.controller.as_str(), "https://example.com/users/alice");
assert!(key.public_key_multibase.starts_with('z'));
let auth = &actor.inner.authentication[0];
assert!(auth.as_object().is_none(), "bare-URL form expected");
assert_canonical_roundtrip::<WithContext<Object>>(&raw);
}
#[test]
fn fep_8b32_signed_create_roundtrips_byte_stable() {
let raw = load_fixture("fep-8b32-create");
let create: WithContext<Object> =
serde_json::from_value(raw.clone()).expect("FEP-8b32 fixture must deserialise");
assert!(create.inner.is_kind("Create"));
assert_eq!(create.inner.proof.len(), 1);
let proof = create.inner.proof.first().expect("at least one proof");
assert_eq!(proof.cryptosuite, "eddsa-jcs-2022");
assert_eq!(proof.proof_purpose, "assertionMethod");
assert!(proof.proof_value.starts_with('z'));
assert_eq!(
proof.verification_method.as_str(),
"https://example.com/users/alice#ed25519-key"
);
assert_canonical_roundtrip::<WithContext<Object>>(&raw);
}