iris-chat 0.1.66

Iris Chat command line client and shared encrypted chat core
Documentation
use super::*;

#[test]
fn private_invite_join_opens_chat_without_a_first_message() {
    let owner = Keys::generate();
    let device = Keys::generate();
    let peer_owner = Keys::generate();
    let peer_device = Keys::generate();
    let mut inviter = isolated_invite_ui_core("join-auto-open-inviter", &owner, &device);
    publish_personal_invite_for_test(&mut inviter);
    inviter.handle_action(AppAction::PushScreen {
        screen: Screen::NewChat,
    });
    assert!(matches!(inviter.screen_stack.last(), Some(Screen::NewChat)));
    let url = inviter
        .state
        .public_invite
        .as_ref()
        .unwrap()
        .legacy_url
        .clone();
    let mut joiner = isolated_invite_ui_core("join-auto-open-peer", &peer_owner, &peer_device);
    joiner.handle_action(AppAction::AcceptInvite { invite_input: url });
    prove_invite_owner(&mut joiner, &owner, &device, 10);
    let response = pending_events_with_kind(&joiner, INVITE_RESPONSE_KIND).remove(0);
    inviter.handle_relay_event(response);
    assert!(
        matches!(inviter.screen_stack.last(), Some(Screen::NewChat)),
        "Unverified owner claim must not open a chat"
    );
    prove_invite_owner(&mut inviter, &peer_owner, &peer_device, 10);
    assert!(
        matches!(inviter.screen_stack.last(), Some(Screen::Chat { chat_id }) if chat_id == &peer_owner.public_key().to_hex()),
        "An authenticated invite join should open the chat without any text message"
    );
    assert!(inviter.threads[&peer_owner.public_key().to_hex()]
        .messages
        .is_empty());
    assert!(
        !inviter.private_chat_invites.is_empty(),
        "Reusable invite survives authenticated joins"
    );
}

#[test]
fn private_invite_join_respects_unchecked_away_and_different_invite() {
    for mode in ["unchecked", "away", "different_invite"] {
        let owner = Keys::generate();
        let device = Keys::generate();
        let peer_owner = Keys::generate();
        let peer_device = Keys::generate();
        let mut inviter = isolated_invite_ui_core("invite-no-focus-steal", &owner, &device);
        publish_personal_invite_for_test(&mut inviter);
        inviter.handle_action(AppAction::PushScreen {
            screen: Screen::NewChat,
        });
        assert!(
            inviter
                .state
                .public_invite
                .as_ref()
                .unwrap()
                .open_chat_on_join
        );
        let url = inviter
            .state
            .public_invite
            .as_ref()
            .unwrap()
            .legacy_url
            .clone();
        let mut joiner =
            isolated_invite_ui_core("invite-no-focus-steal-peer", &peer_owner, &peer_device);
        joiner.handle_action(AppAction::AcceptInvite {
            invite_input: url.clone(),
        });
        prove_invite_owner(&mut joiner, &owner, &device, 10);
        match mode {
            "unchecked" => {
                inviter.handle_action(AppAction::SetInviteOpenOnJoin { enabled: false });
                assert!(
                    !inviter
                        .state
                        .public_invite
                        .as_ref()
                        .unwrap()
                        .open_chat_on_join
                );
            }
            "away" => inviter.handle_action(AppAction::PushScreen {
                screen: Screen::Settings,
            }),
            "different_invite" => {
                // Invite timestamps are seconds; give the newly displayed invite a distinct time.
                std::thread::sleep(Duration::from_millis(1100));
                publish_personal_invite_for_test(&mut inviter);
                assert_ne!(
                    inviter.state.public_invite.as_ref().unwrap().legacy_url,
                    url
                );
            }
            _ => unreachable!(),
        }
        let before = inviter.screen_stack.clone();
        inviter
            .handle_relay_event(pending_events_with_kind(&joiner, INVITE_RESPONSE_KIND).remove(0));
        prove_invite_owner(&mut inviter, &peer_owner, &peer_device, 10);
        assert!(
            inviter
                .protocol_engine
                .as_ref()
                .unwrap()
                .active_session_count_for_owner(peer_owner.public_key())
                > 0
        );
        assert!(inviter.threads[&peer_owner.public_key().to_hex()]
            .messages
            .is_empty());
        assert_eq!(
            inviter.screen_stack, before,
            "{mode}: handshake must not steal navigation"
        );
    }
}

pub(super) fn isolated_invite_ui_core(label: &str, owner: &Keys, device: &Keys) -> AppCore {
    let mut core = logged_in_test_core(label, owner, device);
    core.preferences.nearby_enabled = false;
    core.preferences.nostr_relay_urls.clear();
    let socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
    let std::net::SocketAddr::V4(address) = socket.local_addr().unwrap() else {
        unreachable!()
    };
    drop(socket);
    core.reconcile_device_sync_at_rendezvous_for_test(address);
    core
}