From 05f42536661eb5fdc37b1510718426ade7f4b111 Mon Sep 17 00:00:00 2001 From: cameronvoell Date: Fri, 13 Dec 2024 12:02:53 -0800 Subject: [PATCH] add fork tests to client section --- example/src/tests/clientTests.ts | 208 ++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 2 deletions(-) diff --git a/example/src/tests/clientTests.ts b/example/src/tests/clientTests.ts index b05405f9..b7364d78 100644 --- a/example/src/tests/clientTests.ts +++ b/example/src/tests/clientTests.ts @@ -1,8 +1,9 @@ import { Wallet } from 'ethers' import RNFS from 'react-native-fs' -import { Test, assert, createClients } from './test-utils' -import { Client } from '../../../src/index' +import { Test, assert, createClients, delayToPropogate } from './test-utils' +import { Client, Group } from '../../../src/index' +import { DefaultContentTypes } from 'xmtp-react-native-sdk/lib/types/DefaultContentType' export const clientTests: Test[] = [] let counter = 1 @@ -13,6 +14,209 @@ function test(name: string, perform: () => Promise) { }) } +test('groups cannot fork', async () => { + const [alix, bo, caro] = await createClients(3) + // Create group with 3 users + const { id: groupId } = await alix.conversations.newGroup([ + bo.address, + caro.address, + ]) + + const getGroupForClient = async (client: Client) => { + // Always sync the client before getting the group + await client.conversations.sync() + const group = await client.conversations.findGroup(groupId) + assert(group !== undefined, `Group not found for ${client.address}`) + return group as Group + } + + const syncClientAndGroup = async (client: Client) => { + const group = await getGroupForClient(client) + await group.sync() + } + + const addMemberToGroup = async (fromClient: Client, addresses: string[]) => { + await syncClientAndGroup(fromClient) + const group = await getGroupForClient(fromClient) + await group.addMembers(addresses) + await delayToPropogate(500) + } + + const removeMemberFromGroup = async ( + fromClient: Client, + addresses: string[] + ) => { + await syncClientAndGroup(fromClient) + const group = await getGroupForClient(fromClient) + await group.removeMembers(addresses) + await delayToPropogate(500) + } + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async (senderClient: Client, receiver: Client) => { + // for (const senderClient of senders) { + const messageContent = Math.random().toString(36) + await syncClientAndGroup(senderClient) + const senderGroup = await getGroupForClient(senderClient) + await senderGroup.send(messageContent) + + await delayToPropogate(500) + await senderGroup.sync() + + await syncClientAndGroup(receiver) + + const receiverGroupToCheck = await getGroupForClient(receiver) + await receiverGroupToCheck.sync() + + const messages = await receiverGroupToCheck.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + // console.log(lastMessage); + console.log( + `${receiverGroupToCheck.client.address} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroupToCheck.client.address} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + // } + } + + console.log('Testing that messages sent by alix are received by bo') + await testMessageSending(alix, bo) + console.log('Alix & Bo are not forked at the beginning') + + // Test adding members one by one + // console.log('Testing adding members one by one...') + const newClients = await createClients(2) + + // Add back several members + console.log('Adding new members to the group...') + for (const client of newClients) { + console.log(`Adding member ${client.address}...`) + await addMemberToGroup(alix, [client.address]) + } + await delayToPropogate() + + await alix.conversations.sync() + await syncClientAndGroup(alix) + + // NB => if we don't use Promise.all but a loop, we don't get a fork + const REMOVE_MEMBERS_IN_PARALLEL = true + if (REMOVE_MEMBERS_IN_PARALLEL) { + console.log('Removing members in parallel') + + await Promise.all( + newClients.map((client) => { + console.log(`Removing member ${client.address}...`) + return removeMemberFromGroup(alix, [client.address]) + }) + ) + } else { + console.log('Removing members one by one') + + for (const client of newClients) { + console.log(`Removing member ${client.address}...`) + await removeMemberFromGroup(alix, [client.address]) + } + } + + await delayToPropogate(1000) + + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i+1}/${tryCount}`) + try { + await syncClientAndGroup(alix) + await syncClientAndGroup(bo) + await delayToPropogate(500) + await testMessageSending(alix, bo) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + + return true +}) + +test('groups cannot fork short version', async () => { + const [alix, bo, new_one, new_two] = await createClients(4) + // Create group with 2 users + const alixGroup = await alix.conversations.newGroup([ + bo.address, + new_one.address, + new_two.address, + ]) + + // sync clients + await alix.conversations.sync() + await bo.conversations.sync() + const boGroup: Group = (await bo.conversations.findGroup(alixGroup.id))! + + // Remove two members in parallel + // NB => if we don't use Promise.all but a loop, we don't get a fork + console.log('*************libxmtp*********************: Removing members in parallel') + await Promise.all([ + alixGroup.removeMembers([new_one.address]), + alixGroup.removeMembers([new_two.address]) + ]) + + // Helper to send a message from a bunch of senders and make sure it is received by all receivers + const testMessageSending = async (senderGroup: Group, receiverGroup: Group) => { + const messageContent = Math.random().toString(36) + await senderGroup.sync() + await alixGroup.send(messageContent) + + await delayToPropogate(500) + await alixGroup.sync() + await receiverGroup.sync() + + const messages = await receiverGroup.messages({ + direction: 'DESCENDING', + }) + const lastMessage = messages[0] + console.log( + `${receiverGroup.client.address} sees ${messages.length} messages in group` + ) + assert( + lastMessage !== undefined && + lastMessage.nativeContent.text === messageContent, + `${receiverGroup.client.address} should have received the message, FORK? ${lastMessage?.nativeContent.text} !== ${messageContent}` + ) + } + // When forked, it stays forked even if we try 5 times + // but sometimes it is not forked and works 5/5 times + let forkCount = 0 + const tryCount = 5 + for (let i = 0; i < tryCount; i++) { + console.log(`Checking fork status ${i+1}/${tryCount}`) + try { + await alixGroup.sync() + await boGroup.sync() + await delayToPropogate(500) + await testMessageSending(alixGroup, boGroup) + console.log('Not forked!') + } catch (e: any) { + console.log('Forked!') + console.log(e) + forkCount++ + } + } + assert(forkCount === 0, `Forked ${forkCount}/${tryCount} times`) + + return true +}) + test('can make a client', async () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const keyBytes = new Uint8Array([