iris-chat 0.1.59

Iris Chat command line client and shared encrypted chat core
Documentation
#[test]
fn expired_history_preserves_newer_unread_badges() {
    for old_delivery in [DeliveryState::Seen, DeliveryState::Received] {
        let owner = Keys::generate();
        let device = Keys::generate();
        let chat_id = Keys::generate().public_key().to_hex();
        let mut core = logged_in_test_core("expired-read-history", &owner, &device);
        let mut old = test_chat_message(&chat_id, "old", "read history", 100, false);
        old.expires_at_secs = Some(200);
        old.delivery = old_delivery;
        let mut unread = test_chat_message(&chat_id, "new", "still unread", 150, false);
        unread.delivery = DeliveryState::Received;
        core.threads.insert(
            chat_id.clone(),
            ThreadRecord {
                chat_id: chat_id.clone(),
                unread_count: 1,
                updated_at_secs: 150,
                messages: vec![old, unread],
                draft: String::new(),
            },
        );
        core.persist_best_effort_inner();

        assert_eq!(core.prune_expired_messages(200), 1);
        let thread = &core.threads[&chat_id];
        assert_eq!(thread.messages.len(), 1);
        assert_eq!(thread.messages[0].id, "new");
        assert_eq!(thread.unread_count, 1);

        core.persist_best_effort_inner();
        let persisted = core.load_persisted().unwrap().unwrap();
        assert_eq!(persisted.threads[0].unread_count, 1);
        assert_eq!(persisted.threads[0].messages.len(), 1);
    }
}

#[test]
fn expired_runtime_messages_are_not_restored_by_direct_or_group_delivery() {
    let owner = Keys::generate();
    let device = Keys::generate();
    let sender = Keys::generate();
    let mut core = logged_in_test_core("expired-runtime-replay", &owner, &device);
    core.preferences.send_read_receipts = false;
    let now = unix_now().get();

    for expires_at in [now.saturating_sub(1), now] {
        let (payload, _) = runtime_rumor_json(
            sender.public_key(),
            CHAT_MESSAGE_KIND,
            "expired secret",
            now.saturating_sub(60),
            vec![vec!["expiration".to_string(), expires_at.to_string()]],
        );
        core.apply_decrypted_runtime_message(
            sender.public_key(),
            None,
            payload,
            Some("f".repeat(64)),
        );

        let (payload, _) = runtime_rumor_json(
            sender.public_key(),
            CHAT_MESSAGE_KIND,
            "expired group secret",
            now.saturating_sub(60),
            vec![
                vec!["l".to_string(), "expired-group".to_string()],
                vec!["expiration".to_string(), expires_at.to_string()],
            ],
        );
        core.apply_group_decrypted_event(GroupIncomingEvent::Message(
            nostr_double_ratchet::GroupReceivedMessage {
                group_id: "expired-group".to_string(),
                sender_owner: ndr_owner_pubkey(sender.public_key()),
                sender_device: None,
                body: payload.into_bytes(),
                revision: 1,
            },
        ));

        core.apply_runtime_text_message(
            owner.public_key(),
            Some(sender.public_key().to_hex()),
            "expired own-device replay".to_string(),
            now.saturating_sub(60),
            Some(expires_at),
            Some("own-replay".to_string()),
            None,
        );
    }

    assert!(core.threads.is_empty());
    core.persist_best_effort_inner();
    assert_eq!(stored_message_count(&core), 0);
}