UNPKG

@towns-protocol/sdk

Version:

For more details, visit the following resources:

154 lines 7.34 kB
/** * @group main */ import { makeEvent, unpackStream } from '../../sign'; import { SyncedStreams } from '../../syncedStreams'; import { SyncState, stateConstraints } from '../../syncedStreamsLoop'; import { makeDonePromise, makeRandomUserContext, makeTestRpcClient, waitFor } from '../testUtils'; import { makeUserInboxStreamId, streamIdToBytes, userIdFromAddress } from '../../id'; import { make_UserInboxPayload_Ack, make_UserInboxPayload_Inception } from '../../types'; import { dlog, shortenHexString } from '@towns-protocol/dlog'; import EventEmitter from 'events'; import { SyncedStream } from '../../syncedStream'; import { StubPersistenceStore } from '../../persistenceStore'; import { nanoid } from 'nanoid'; import { StreamsView } from '../../views/streamsView'; const log = dlog('csb:test:syncedStreams'); describe('syncStreams', () => { beforeEach(async () => { log('beforeEach'); // bobsContext = await makeRandomUserContext() }); afterEach(async () => { log('afterEach'); }); test('waitForSyncingStateTransitions', () => { // the syncing, canceling, and not syncing state should not be able to transition to itself, otherwise waitForSyncingState will break expect(stateConstraints[SyncState.Syncing].has(SyncState.Syncing)).toBe(false); expect(stateConstraints[SyncState.Canceling].has(SyncState.Syncing)).toBe(false); expect(stateConstraints[SyncState.NotSyncing].has(SyncState.Syncing)).toBe(false); // the starting, and retrying state should both be able to transition to syncing, otherwise waitForSyncingState will break expect(stateConstraints[SyncState.Starting].has(SyncState.Syncing)).toBe(true); expect(stateConstraints[SyncState.Retrying].has(SyncState.Syncing)).toBe(true); // if this breaks, we just need to change the two conditions in waitForSyncingState }); test('starting->syncing->canceling->notSyncing', async () => { log('starting->syncing->canceling->notSyncing'); // globals setup const stubPersistenceStore = new StubPersistenceStore(); const done1 = makeDonePromise(); let userInboxDeviceSummaryUpdatedCount = 0; const mockClientEmitter = new EventEmitter(); mockClientEmitter.on('streamSyncActive', (isActive) => { if (isActive) { done1.done(); } }); mockClientEmitter.on('userInboxDeviceSummaryUpdated', () => { userInboxDeviceSummaryUpdatedCount++; }); // alice setup const rpcClient = await makeTestRpcClient(); const alicesContext = await makeRandomUserContext(); const alicesUserId = userIdFromAddress(alicesContext.creatorAddress); const alicesSyncedStreams = new SyncedStreams(alicesUserId, rpcClient, mockClientEmitter, undefined, shortenHexString(alicesUserId), { useModifySync: true, useSharedSyncer: true }); // some helper functions const createStream = async (streamId, events) => { const streamResponse = await rpcClient.createStream({ events, streamId, }); const response = await unpackStream(streamResponse.stream, undefined); return response; }; // user inbox stream setup const alicesUserInboxStreamIdStr = makeUserInboxStreamId(alicesUserId); const alicesUserInboxStreamId = streamIdToBytes(alicesUserInboxStreamIdStr); const userInboxStreamResponse = await createStream(alicesUserInboxStreamId, [ await makeEvent(alicesContext, make_UserInboxPayload_Inception({ streamId: alicesUserInboxStreamId, })), ]); const streamsViewDelegate = { isDMMessageEventBlocked: (_event) => { return false; }, }; const streamsView = new StreamsView(alicesUserId, streamsViewDelegate); const userInboxStream = new SyncedStream(alicesUserId, alicesUserInboxStreamIdStr, streamsView, mockClientEmitter, log, stubPersistenceStore); await userInboxStream.initializeFromResponse(userInboxStreamResponse); alicesSyncedStreams.startSyncStreams(); await done1.promise; alicesSyncedStreams.set(alicesUserInboxStreamIdStr, userInboxStream); alicesSyncedStreams.addStreamToSync(alicesUserInboxStreamIdStr, userInboxStream.view.syncCookie); // some helper functions const addEvent = async (payload) => { await rpcClient.addEvent({ streamId: alicesUserInboxStreamId, event: await makeEvent(alicesContext, payload, userInboxStreamResponse.streamAndCookie.miniblocks[0].hash), }); }; // assert assumptions expect(userInboxDeviceSummaryUpdatedCount).toBe(0); // post an ack (easiest way to put a string in a stream) await addEvent(make_UserInboxPayload_Ack({ deviceKey: 'numero uno', miniblockNum: 1n, })); // make sure it shows up await waitFor(() => { expect(userInboxDeviceSummaryUpdatedCount).toBe(1); }); const sendPing = async () => { if (!alicesSyncedStreams.pingInfo) { throw new Error('syncId not set'); } const n1 = nanoid(); const n2 = nanoid(); alicesSyncedStreams.pingInfo.nonces[n1] = { sequence: alicesSyncedStreams.pingInfo.currentSequence++, nonce: n1, pingAt: performance.now(), }; alicesSyncedStreams.pingInfo.nonces[n2] = { sequence: alicesSyncedStreams.pingInfo.currentSequence++, nonce: n2, pingAt: performance.now(), }; // ping the stream twice in a row const p1 = rpcClient.pingSync({ syncId: alicesSyncedStreams.getSyncId(), nonce: n1, }); const p2 = rpcClient.pingSync({ syncId: alicesSyncedStreams.getSyncId(), nonce: n2, }); await Promise.all([p1, p2]); await waitFor(() => expect(alicesSyncedStreams.pingInfo?.nonces[n2].receivedAt).toBeDefined()); await waitFor(() => expect(alicesSyncedStreams.pingInfo?.nonces[n1].receivedAt).toBeDefined()); }; for (let i = 0; i < 3; i++) { await sendPing(); } // get stream const stream = await rpcClient.getStream({ streamId: alicesUserInboxStreamId, }); expect(stream.stream).toBeDefined(); // drop the stream await rpcClient.info({ debug: ['drop_stream', alicesSyncedStreams.getSyncId(), alicesUserInboxStreamIdStr], }); // assert assumptions expect(userInboxDeviceSummaryUpdatedCount).toBe(1); // add second event await addEvent(make_UserInboxPayload_Ack({ deviceKey: 'numero dos', miniblockNum: 1n, })); // make sure it shows up await waitFor(() => expect(userInboxDeviceSummaryUpdatedCount).toBe(2)); await alicesSyncedStreams.stopSync(); }); }); //# sourceMappingURL=syncedStreams.test.js.map