use super::*;
use gbp_core::StreamType;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_node_experimental);
fn gid(b: u8) -> Vec<u8> {
vec![b; 16]
}
fn creator_group(user: &str, group_byte: u8) -> (MlsContext, GroupNode, GtpClient) {
let mls = MlsContext::create(user).expect("MlsContext::create");
let node = GroupNode::create(1, &gid(group_byte));
node.bootstrap_as_creator(mls.epoch());
let gtp = GtpClient::create();
(mls, node, gtp)
}
fn two_member_group() -> (MlsContext, GroupNode, GtpClient, MlsContext, GroupNode, GtpClient) {
let alice_mls = MlsContext::create("alice").unwrap();
let bob_mls = MlsContext::create("bob").unwrap();
let welcome = alice_mls.invite(&bob_mls.key_package().to_vec()).unwrap();
bob_mls.accept_welcome(&welcome.to_vec()).unwrap();
let group_id = alice_mls.group_id().to_vec();
let alice_node = GroupNode::create(1, &group_id);
let bob_node = GroupNode::create(2, &group_id);
alice_node.bootstrap_as_creator(alice_mls.epoch());
bob_node.bootstrap_as_joiner(bob_mls.epoch(), 0);
(
alice_mls, alice_node, GtpClient::create(),
bob_mls, bob_node, GtpClient::create(),
)
}
fn text_events(arr: &Array) -> Vec<JsValue> {
(0..arr.length())
.map(|i| arr.get(i))
.filter(|ev| {
let kind = Reflect::get(ev, &"kind".into())
.map(|v| v.as_string().unwrap_or_default())
.unwrap_or_default();
let st = Reflect::get(ev, &"streamType".into())
.map(|v| v.as_f64().unwrap_or(-1.0) as u8)
.unwrap_or(255);
kind == "payload_received" && st == StreamType::Text.as_u8()
})
.collect()
}
fn plaintext_of(ev: &JsValue) -> Vec<u8> {
let pt = Reflect::get(ev, &"plaintext".into()).unwrap();
Uint8Array::new(&pt).to_vec()
}
#[wasm_bindgen_test]
fn mls_create_epoch_zero() {
let ctx = MlsContext::create("alice").unwrap();
assert_eq!(ctx.epoch(), 0u64);
}
#[wasm_bindgen_test]
fn mls_key_package_nonempty() {
let ctx = MlsContext::create("alice").unwrap();
assert!(ctx.key_package().length() > 0);
}
#[wasm_bindgen_test]
fn mls_group_id_16_bytes() {
let ctx = MlsContext::create("alice").unwrap();
assert_eq!(ctx.group_id().length(), 16);
}
#[wasm_bindgen_test]
fn mls_invite_accept_syncs_epoch() {
let alice = MlsContext::create("alice").unwrap();
let bob = MlsContext::create("bob").unwrap();
assert_eq!(alice.epoch(), 0u64);
assert_eq!(bob.epoch(), 0u64);
let welcome = alice.invite(&bob.key_package().to_vec()).unwrap();
bob.accept_welcome(&welcome.to_vec()).unwrap();
assert_eq!(alice.epoch(), 1u64);
assert_eq!(bob.epoch(), 1u64);
assert_eq!(alice.group_id().to_vec(), bob.group_id().to_vec());
}
#[wasm_bindgen_test]
fn mls_two_distinct_users_have_different_key_packages() {
let alice = MlsContext::create("alice").unwrap();
let bob = MlsContext::create("bob").unwrap();
assert_ne!(alice.key_package().to_vec(), bob.key_package().to_vec());
}
#[wasm_bindgen_test]
fn group_node_create_bootstrap_as_creator() {
let mls = MlsContext::create("alice").unwrap();
let node = GroupNode::create(1, &gid(0x01));
node.bootstrap_as_creator(mls.epoch());
assert_eq!(node.current_epoch(), 0u64);
assert_eq!(node.member_id(), 1u32);
assert_eq!(node.last_transition_id(), 0u32);
}
#[wasm_bindgen_test]
fn group_node_bootstrap_as_joiner() {
let alice_mls = MlsContext::create("alice").unwrap();
let bob_mls = MlsContext::create("bob").unwrap();
let welcome = alice_mls.invite(&bob_mls.key_package().to_vec()).unwrap();
bob_mls.accept_welcome(&welcome.to_vec()).unwrap();
let gid = alice_mls.group_id().to_vec();
let bob_node = GroupNode::create(2, &gid);
bob_node.bootstrap_as_joiner(bob_mls.epoch(), 0);
assert_eq!(bob_node.current_epoch(), 1u64);
assert_eq!(bob_node.member_id(), 2u32);
}
#[wasm_bindgen_test]
fn group_node_check_timeouts_returns_array() {
let mls = MlsContext::create("alice").unwrap();
let node = GroupNode::create(1, &gid(0x02));
node.bootstrap_as_creator(mls.epoch());
let evs = node.check_timeouts();
assert!(evs.is_array());
}
#[wasm_bindgen_test]
fn gtp_send_returns_wire_bytes() {
let (mls, node, gtp) = creator_group("alice", 0x03);
let frame = gtp.send(&node, &mls, 0, 1, "hello");
assert!(!frame.is_null());
let wire = Reflect::get(&frame, &"wire".into()).unwrap();
assert!(Uint8Array::new(&wire).length() > 0);
}
#[wasm_bindgen_test]
fn gtp_single_member_roundtrip() {
let (mls, node, gtp) = creator_group("alice", 0x04);
let frame = gtp.send(&node, &mls, 0, 1, "hello wasm");
let wire = Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec();
let evs = node.on_wire(&mls, &wire);
let texts = text_events(&evs);
assert_eq!(texts.len(), 1);
let result = gtp.accept(&plaintext_of(&texts[0]), mls.epoch());
assert!(!result.is_null());
let text = Reflect::get(&result, &"text".into()).unwrap().as_string().unwrap();
assert_eq!(text, "hello wasm");
}
#[wasm_bindgen_test]
fn gtp_unicode_roundtrip() {
let (mls, node, gtp) = creator_group("alice", 0x05);
let msg = "Привет 🌍 こんにちは";
let frame = gtp.send(&node, &mls, 0, 1, msg);
let wire = Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec();
for ev in text_events(&node.on_wire(&mls, &wire)) {
let result = gtp.accept(&plaintext_of(&ev), mls.epoch());
let text = Reflect::get(&result, &"text".into()).unwrap().as_string().unwrap();
assert_eq!(text, msg);
}
}
#[wasm_bindgen_test]
fn gtp_duplicate_returns_status_duplicate() {
let (mls, node, gtp) = creator_group("alice", 0x06);
let wire1 = {
let frame = gtp.send(&node, &mls, 0, 42, "msg");
Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec()
};
for ev in text_events(&node.on_wire(&mls, &wire1)) {
let r = gtp.accept(&plaintext_of(&ev), mls.epoch());
let status = Reflect::get(&r, &"status".into()).unwrap().as_string().unwrap();
assert_eq!(status, "new");
}
let wire2 = {
let frame = gtp.send(&node, &mls, 0, 42, "msg");
Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec()
};
for ev in text_events(&node.on_wire(&mls, &wire2)) {
let r = gtp.accept(&plaintext_of(&ev), mls.epoch());
let status = Reflect::get(&r, &"status".into()).unwrap().as_string().unwrap();
assert_eq!(status, "duplicate");
}
}
#[wasm_bindgen_test]
fn gtp_sequential_message_ids() {
let (mls, node, gtp) = creator_group("alice", 0x07);
for i in 1u64..=5 {
let frame = gtp.send(&node, &mls, 0, i, &format!("msg {i}"));
let wire = Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec();
let evs = text_events(&node.on_wire(&mls, &wire));
assert_eq!(evs.len(), 1, "msg {i} should produce exactly one text event");
let r = gtp.accept(&plaintext_of(&evs[0]), mls.epoch());
let text = Reflect::get(&r, &"text".into()).unwrap().as_string().unwrap();
assert_eq!(text, format!("msg {i}"));
}
}
#[wasm_bindgen_test]
fn gtp_reset_clears_dedup_set() {
let (mls, node, gtp) = creator_group("alice", 0x08);
let wire = {
let frame = gtp.send(&node, &mls, 0, 1, "a");
Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec()
};
for ev in text_events(&node.on_wire(&mls, &wire)) {
let r = gtp.accept(&plaintext_of(&ev), mls.epoch());
assert_eq!(Reflect::get(&r, &"status".into()).unwrap().as_string().unwrap(), "new");
}
gtp.reset();
let wire2 = {
let frame = gtp.send(&node, &mls, 0, 1, "a");
Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec()
};
for ev in text_events(&node.on_wire(&mls, &wire2)) {
let r = gtp.accept(&plaintext_of(&ev), mls.epoch());
assert_eq!(Reflect::get(&r, &"status".into()).unwrap().as_string().unwrap(), "new");
}
}
#[wasm_bindgen_test]
fn two_member_gtp_alice_to_bob() {
let (alice_mls, alice_node, gtp_alice, bob_mls, bob_node, gtp_bob) = two_member_group();
let frame = gtp_alice.send(&alice_node, &alice_mls, 2, 1, "hello bob");
let wire = Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec();
let evs = text_events(&bob_node.on_wire(&bob_mls, &wire));
assert_eq!(evs.len(), 1);
let r = gtp_bob.accept(&plaintext_of(&evs[0]), bob_mls.epoch());
assert_eq!(
Reflect::get(&r, &"text".into()).unwrap().as_string().unwrap(),
"hello bob"
);
assert_eq!(
Reflect::get(&r, &"status".into()).unwrap().as_string().unwrap(),
"new"
);
}
#[wasm_bindgen_test]
fn two_member_gtp_bidirectional() {
let (alice_mls, alice_node, gtp_alice, bob_mls, bob_node, gtp_bob) = two_member_group();
let wire_ab = {
let frame = gtp_alice.send(&alice_node, &alice_mls, 2, 1, "ping");
Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec()
};
for ev in text_events(&bob_node.on_wire(&bob_mls, &wire_ab)) {
let r = gtp_bob.accept(&plaintext_of(&ev), bob_mls.epoch());
assert_eq!(Reflect::get(&r, &"text".into()).unwrap().as_string().unwrap(), "ping");
}
let wire_ba = {
let frame = gtp_bob.send(&bob_node, &bob_mls, 1, 1, "pong");
Uint8Array::new(&Reflect::get(&frame, &"wire".into()).unwrap()).to_vec()
};
for ev in text_events(&alice_node.on_wire(&alice_mls, &wire_ba)) {
let r = gtp_alice.accept(&plaintext_of(&ev), alice_mls.epoch());
assert_eq!(Reflect::get(&r, &"text".into()).unwrap().as_string().unwrap(), "pong");
}
}
#[wasm_bindgen_test]
fn two_member_epochs_match() {
let (alice_mls, _an, _ga, bob_mls, _bn, _gb) = two_member_group();
assert_eq!(alice_mls.epoch(), bob_mls.epoch());
assert_eq!(alice_mls.group_id().to_vec(), bob_mls.group_id().to_vec());
}