@river-build/sdk
Version:
For more details, visit the following resources:
190 lines • 9.54 kB
JavaScript
/**
* @group main
*/
import { isEncryptedData, makeTestClient, makeUniqueSpaceStreamId, waitFor } from '../testUtils';
import { dlog } from '@river-build/dlog';
import { AES_GCM_DERIVED_ALGORITHM } from '@river-build/encryption';
import { makeUniqueChannelStreamId, makeUniqueMediaStreamId } from '../../id';
import { MediaInfoSchema, MembershipOp } from '@river-build/proto';
import { deriveKeyAndIV } from '../../crypto_utils';
import { nanoid } from 'nanoid';
import { create } from '@bufbuild/protobuf';
const log = dlog('csb:test');
describe('spaceTests', () => {
let bobsClient;
let alicesClient;
beforeEach(async () => {
bobsClient = await makeTestClient();
await bobsClient.initializeUser();
bobsClient.startSync();
alicesClient = await makeTestClient();
await alicesClient.initializeUser();
alicesClient.startSync();
});
afterEach(async () => {
await bobsClient.stop();
await alicesClient.stop();
});
test('bobKicksAlice', async () => {
log('bobKicksAlice');
const spaceId = makeUniqueSpaceStreamId();
await expect(bobsClient.createSpace(spaceId)).resolves.not.toThrow();
const channelId = makeUniqueChannelStreamId(spaceId);
await expect(bobsClient.createChannel(spaceId, 'name', 'topic', channelId)).resolves.not.toThrow();
await expect(alicesClient.joinStream(spaceId)).resolves.not.toThrow();
await expect(alicesClient.joinStream(channelId)).resolves.not.toThrow();
const userStreamView = alicesClient.stream(alicesClient.userStreamId).view;
await waitFor(() => {
expect(userStreamView.userContent.isMember(spaceId, MembershipOp.SO_JOIN)).toBe(true);
expect(userStreamView.userContent.isMember(channelId, MembershipOp.SO_JOIN)).toBe(true);
});
// Bob can kick Alice
await expect(bobsClient.removeUser(spaceId, alicesClient.userId)).resolves.not.toThrow();
// Alice is no longer a member of the space or channel
await waitFor(() => {
expect(userStreamView.userContent.isMember(spaceId, MembershipOp.SO_JOIN)).toBe(false);
expect(userStreamView.userContent.isMember(channelId, MembershipOp.SO_JOIN)).toBe(false);
});
});
test('channelMetadata', async () => {
log('channelMetadata');
const spaceId = makeUniqueSpaceStreamId();
await expect(bobsClient.createSpace(spaceId)).resolves.not.toThrow();
const spaceStream = await bobsClient.waitForStream(spaceId);
// assert assumptions
expect(spaceStream).toBeDefined();
// create a new channel
const channelId = makeUniqueChannelStreamId(spaceId);
await expect(bobsClient.createChannel(spaceId, 'name', 'topic', channelId)).resolves.not.toThrow();
// our space channels metatdata should reflect the new channel
await waitFor(() => {
expect(spaceStream.view.spaceContent.spaceChannelsMetadata.get(channelId)).toBeDefined();
expect(spaceStream.view.spaceContent.spaceChannelsMetadata.get(channelId)
?.updatedAtEventNum).toBeGreaterThan(0);
});
// save off existing updated at
const prevUpdatedAt = spaceStream.view.spaceContent.spaceChannelsMetadata.get(channelId).updatedAtEventNum;
// make a snapshot
spaceStream.view.saveSnapshots = true;
await bobsClient.debugForceMakeMiniblock(spaceId, { forceSnapshot: true });
// the new snapshot should have the new data
await waitFor(() => {
const snapshot = spaceStream.view.snapshot();
expect(snapshot?.content.case === 'spaceContent' &&
snapshot.content.value.channels.length === 1 &&
snapshot.content.value.channels[0].updatedAtEventNum === prevUpdatedAt).toBe(true);
});
// update the channel metadata
await bobsClient.updateChannel(spaceId, channelId, '', '');
// see the metadat update
await waitFor(() => {
expect(spaceStream.view.spaceContent.spaceChannelsMetadata.get(channelId)).toBeDefined();
expect(spaceStream.view.spaceContent.spaceChannelsMetadata.get(channelId)
?.updatedAtEventNum).toBeGreaterThan(prevUpdatedAt);
});
// make a miniblock
spaceStream.view.saveSnapshots = true;
await bobsClient.debugForceMakeMiniblock(spaceId, { forceSnapshot: true });
// see new snapshot should have the new data
await waitFor(() => {
const snapshot = spaceStream.view.snapshot();
expect(snapshot?.content.case === 'spaceContent' &&
snapshot.content.value.channels.length === 1 &&
snapshot.content.value.channels[0].updatedAtEventNum > prevUpdatedAt).toBe(true);
});
});
function spaceImageUpdated(spaceId, counter) {
counter.count++;
}
test('spaceImage', async () => {
const spaceImageUpdatedCounter = {
count: 0,
};
const spaceId = makeUniqueSpaceStreamId();
await expect(bobsClient.createSpace(spaceId)).resolves.not.toThrow();
const spaceStream = await bobsClient.waitForStream(spaceId);
spaceStream.view.saveSnapshots = true;
spaceStream.on('spaceImageUpdated', spaceImageUpdated.bind(null, spaceId, spaceImageUpdatedCounter));
try {
// make a space image event
const mediaStreamId = makeUniqueMediaStreamId();
const image = create(MediaInfoSchema, {
mimetype: 'image/png',
filename: 'bob-1.png',
});
const { key, iv } = await deriveKeyAndIV(nanoid(128)); // if in browser please use window.crypto.subtle.generateKey
const chunkedMediaInfo = {
info: image,
streamId: mediaStreamId,
encryption: {
case: 'aesgcm',
value: { secretKey: key, iv },
},
thumbnail: undefined,
};
await bobsClient.setSpaceImage(spaceId, chunkedMediaInfo);
// make a snapshot
await bobsClient.debugForceMakeMiniblock(spaceId, { forceSnapshot: true });
// see the space image in the snapshot
await waitFor(() => {
const snapshot = spaceStream.view.snapshot();
expect(snapshot?.content.case === 'spaceContent' &&
snapshot.content.value.spaceImage !== undefined &&
snapshot.content.value.spaceImage.data !== undefined).toBe(true);
});
expect(spaceImageUpdatedCounter.count).toBe(1);
// decrypt the snapshot and assert the image values
const snapshot = spaceStream.view.snapshot();
const encryptedData = snapshot?.content.case === 'spaceContent'
? snapshot.content.value.spaceImage?.data
: undefined;
expect(encryptedData !== undefined &&
isEncryptedData(encryptedData) &&
encryptedData.algorithm === AES_GCM_DERIVED_ALGORITHM).toBe(true);
const decrypted = await spaceStream.view.spaceContent.getSpaceImage();
expect(decrypted !== undefined &&
decrypted.info?.mimetype === image.mimetype &&
decrypted.info?.filename === image.filename &&
decrypted.encryption.case === 'aesgcm' &&
decrypted.encryption.value.secretKey !== undefined).toBe(true);
// make another space image event
const mediaStreamId2 = makeUniqueMediaStreamId();
const image2 = create(MediaInfoSchema, {
mimetype: 'image/jpg',
filename: 'bob-2.jpg',
});
const chunkedMediaInfo2 = {
info: image2,
streamId: mediaStreamId2,
encryption: {
case: 'aesgcm',
value: { secretKey: key, iv },
},
thumbnail: undefined,
};
await bobsClient.setSpaceImage(spaceId, chunkedMediaInfo2);
// make a snapshot
spaceStream.view.saveSnapshots = true;
await bobsClient.debugForceMakeMiniblock(spaceId, { forceSnapshot: true });
// see the space image in the snapshot
await waitFor(() => {
const snapshot = spaceStream.view.snapshot();
expect(snapshot?.content.case === 'spaceContent' &&
snapshot.content.value.spaceImage !== undefined &&
snapshot.content.value.spaceImage.data !== undefined).toBe(true);
});
// decrypt the snapshot and assert the image values
const spaceImage = await spaceStream.view.spaceContent.getSpaceImage();
expect(spaceImage !== undefined &&
spaceImage?.info?.mimetype === image2.mimetype &&
spaceImage?.info?.filename === image2.filename &&
spaceImage.encryption.case === 'aesgcm' &&
spaceImage.encryption.value.secretKey !== undefined).toBe(true);
expect(spaceImageUpdatedCounter.count).toBe(2);
}
finally {
spaceStream.off('spaceImageUpdated', spaceImageUpdated.bind(null, spaceId, spaceImageUpdatedCounter));
}
});
});
//# sourceMappingURL=space.test.js.map