diff --git a/backend/src/data/models/guild.ts b/backend/src/data/models/guild.ts index 063409d6..01e8dd1d 100644 --- a/backend/src/data/models/guild.ts +++ b/backend/src/data/models/guild.ts @@ -1,6 +1,6 @@ import { Document, model, Schema } from 'mongoose'; import patterns from '../../types/patterns'; -import { createdAtToDate, getNameAcronym, useId } from '../../utils/utils'; +import { createdAtToDate, useId } from '../../utils/utils'; import { generateSnowflake } from '../snowflake-entity'; // properties we don't need to define when creating a guild diff --git a/backend/src/ws/modules/ws-guard.ts b/backend/src/ws/modules/ws-guard.ts index e82b534f..6e9282e6 100644 --- a/backend/src/ws/modules/ws-guard.ts +++ b/backend/src/ws/modules/ws-guard.ts @@ -67,15 +67,4 @@ export class WSGuard { const id = this.users.verifyToken(token); return { id }; } - - public validateKeys(type: K, partial: any) { - const contains = this.includesProhibited(partial, type); - if (contains) - throw new TypeError('Contains readonly values'); - } - - private includesProhibited(partial: any, type: K) { - const keys = Object.keys(partial); - return Prohibited[type].some(k => keys.includes(k)); - } } diff --git a/backend/src/ws/ws-events/guild-update.ts b/backend/src/ws/ws-events/guild-update.ts index 645a5c64..5c46ac07 100644 --- a/backend/src/ws/ws-events/guild-update.ts +++ b/backend/src/ws/ws-events/guild-update.ts @@ -18,18 +18,18 @@ export default class implements WSEvent<'GUILD_UPDATE'> { public async invoke(ws: WebSocket, client: Socket, { guildId, name, iconURL }: WS.Params.GuildUpdate) { await this.guard.validateCan(client, guildId, 'MANAGE_GUILD'); - const partialGuild = { name, iconURL }; - this.guard.validateKeys('guild', partialGuild); - const guild = await this.guilds.get(guildId); + const partial: Partial = {}; + const hasChanged = (key: string, value: any) => value && guild[key] !== value; - if (iconURL) guild.iconURL = iconURL; - if (name) guild.name = name; + if (hasChanged('iconURL', iconURL)) guild.iconURL = iconURL; + if (hasChanged('name', name)) guild.name = name!; + Object.assign(guild, partial); await guild.save(); ws.io .to(guildId) - .emit('GUILD_UPDATE', { guildId, partialGuild } as WS.Args.GuildUpdate); + .emit('GUILD_UPDATE', { guildId, partialGuild: partial } as WS.Args.GuildUpdate); } } diff --git a/backend/test/integration/ws/add-friend.tests.ts b/backend/test/integration/ws/add-friend.tests.ts deleted file mode 100644 index b595f9cb..00000000 --- a/backend/test/integration/ws/add-friend.tests.ts +++ /dev/null @@ -1,119 +0,0 @@ -import AddFriend from '../../../src/ws/ws-events/add-friend'; -import RemoveFriend from '../../../src/ws/ws-events/remove-friend'; -import { WebSocket } from '../../../src/ws/websocket'; -import io from 'socket.io-client'; -import { Mock } from '../../mock/mock'; -import { expect } from 'chai'; -import { SelfUserDocument, User } from '../../../src/data/models/user'; -import { Channel } from '../../../src/data/models/channel'; - -describe('add-friend', () => { - const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; - let event: AddFriend; - let ws: WebSocket; - - let sender: SelfUserDocument; - let friend: SelfUserDocument; - - beforeEach(async () => { - ({ event, ws, user: sender as any } = await Mock.defaultSetup(client, AddFriend)); - - friend = await Mock.self(); - }); - - afterEach(async () => await Mock.afterEach(ws)); - after(async () => await Mock.after(client)); - - it('user sends request, fulfilled', async () => { - await expect(addFriend()).to.be.fulfilled; - }); - - it('user adds self as friend, rejected', async () => { - friend.username = sender.username; - - await expect(addFriend()).to.be.rejectedWith('You cannot add yourself as a friend'); - }); - - it('friend blocked sender, rejected', async () => { - friend.ignored?.userIds.push(sender.id); - await friend.updateOne(friend); - - await expect(addFriend()).to.be.rejectedWith('This user is blocking you'); - }); - - it('user adds non existing user, rejected', async () => { - await friend.deleteOne(); - - await expect(addFriend()).to.be.rejectedWith('User Not Found'); - }); - - it('both users add each other, creates one dm channel', async () => { - await addFriend(); - await returnFriend(); - - const exists = await Channel.countDocuments({ memberIds: sender.id }); - expect(exists).to.equal(1); - }); - - it('user add, remove, then re-add each other, reuses old dm channel', async () => { - await addFriend(); - await returnFriend(); - - await removeFriend(); - - await addFriend(); - await returnFriend(); - - const docs = await Channel.countDocuments({ type: 'DM' }); - expect(docs).to.equal(1); - }); - - it('both users add each other, friend request removed', async () => { - await addFriend(); - await returnFriend(); - - sender = await User.findById(sender.id) as any; - friend = await User.findById(friend.id) as any; - - expect(sender.friendRequestIds).to.be.empty; - expect(friend.friendRequestIds).to.be.empty; - }); - - it('both users add each other, friend added', async () => { - await addFriend(); - await returnFriend(); - - friend = await User.findById(friend.id) as any; - sender = await User.findById(sender.id) as any; - - expect(friend.friendIds.length).to.equal(1); - expect(sender.friendIds.length).to.equal(1); - }); - - it('user adds friends twice, rejected', async () => { - await addFriend(); - await expect(addFriend()).to.be.rejectedWith('Friend request already sent'); - }); - - it('user already friends, rejected', async () => { - await addFriend(); - await returnFriend(); - - await expect(addFriend()).to.be.rejectedWith('You are already friends'); - }); - - async function addFriend() { - return event.invoke(ws, client, { username: friend.username }); - } - - async function returnFriend() { - ws.sessions.set(client.id, friend.id); - await event.invoke(ws, client, { username: sender.username }); - - ws.sessions.set(client.id, sender.id); - } - - async function removeFriend() { - await new RemoveFriend().invoke(ws, client, { friendId: friend.id }); - } -}); diff --git a/backend/test/integration/ws/guild-create.tests.ts b/backend/test/integration/ws/guild-create.tests.ts index a403c065..aa7b23b9 100644 --- a/backend/test/integration/ws/guild-create.tests.ts +++ b/backend/test/integration/ws/guild-create.tests.ts @@ -11,11 +11,10 @@ describe('guild-create', () => { let ws: WebSocket; let event: GuildCreate; - let user: UserDocument; - let member: GuildMemberDocument; + let user: SelfUserDocument; beforeEach(async () => { - ({ ws, event, member, user } = await Mock.defaultSetup(client, GuildCreate)); + ({ ws, event, user } = await Mock.defaultSetup(client, GuildCreate)); }); afterEach(async () => Mock.afterEach(ws)); @@ -26,19 +25,17 @@ describe('guild-create', () => { }); it('user creates guild, added to user.guilds', async () => { - const oldCount = user.guilds.length; + const oldCount = user.guildIds.length; await guildCreate(); user = await User.findById(user.id); - expect(user.guilds.length).to.be.greaterThan(oldCount); + expect(user.guildIds.length).to.be.greaterThan(oldCount); }); function guildCreate(partialGuild?: Partial) { return event.invoke(ws, client, { - partialGuild: { - name: 'Mock Guild', - ...partialGuild, - }, + name: 'Mock Guild', + ...partialGuild, }); } }); \ No newline at end of file diff --git a/backend/test/integration/ws/guild-delete.tests.ts b/backend/test/integration/ws/guild-delete.tests.ts index ee706e52..2ae70b18 100644 --- a/backend/test/integration/ws/guild-delete.tests.ts +++ b/backend/test/integration/ws/guild-delete.tests.ts @@ -73,12 +73,12 @@ describe('guild-delete', () => { expect(guild).to.be.null; }); - it('owner deletes guild, removed from all user.guilds', async () => { - const oldCount = user.guilds.length; + it('owner deletes guild, removed from all user guildIds', async () => { + const oldCount = user.guildIds.length; await guildDelete(); user = await User.findById(user.id); - expect(user.guilds.length).to.be.lessThan(oldCount); + expect(user.guildIds.length).to.be.lessThan(oldCount); }); function guildDelete() { @@ -90,7 +90,6 @@ describe('guild-delete', () => { user = await User.findById(guild.ownerId); } async function makeNoob() { - user = await User.findById(guild.members[1].userId); ws.sessions.set(client.id, user.id); } }); diff --git a/backend/test/integration/ws/guild-member-add.tests.ts b/backend/test/integration/ws/guild-member-add.tests.ts index 28152632..7991faf7 100644 --- a/backend/test/integration/ws/guild-member-add.tests.ts +++ b/backend/test/integration/ws/guild-member-add.tests.ts @@ -9,24 +9,20 @@ import { Invite, InviteDocument } from '../../../src/data/models/invite'; import { expect, spy } from 'chai'; import Guilds from '../../../src/data/guilds'; import Users from '../../../src/data/users'; +import { GuildMember } from '../../../src/data/models/guild-member'; describe('guild-member-add', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; let event: GuildMemberAdd; - let guilds: Guilds; - let users: Users; let ws: WebSocket; - let user: UserDocument; + let user: SelfUserDocument; let guild: GuildDocument; let invite: InviteDocument; beforeEach(async() => { ({ event, ws, user, guild } = await Mock.defaultSetup(client, GuildMemberAdd)); - guilds = Deps.get(Guilds); - users = Deps.get(Users); - user = await Mock.user([]); ws.sessions.set(client.id, user.id); @@ -42,7 +38,6 @@ describe('guild-member-add', () => { it('user already joined, rejected', async () => { await guildMemberAdd(); - await expect(guildMemberAdd()).to.be.rejectedWith('User already in guild'); }); @@ -54,18 +49,18 @@ describe('guild-member-add', () => { }); it('valid invite and code, member added to guild', async () => { - const oldMemberCount = guild.members.length; + const oldMemberCount = await GuildMember.count({ guildId: guild.id }); await guildMemberAdd(); - guild = await guilds.get(guild.id); - expect(guild.members.length).to.be.greaterThan(oldMemberCount); + const newMemberCount = await GuildMember.count({ guildId: guild.id }); + expect(newMemberCount).to.be.greaterThan(oldMemberCount); }); it('valid invite and code, guild added to user guilds', async () => { await guildMemberAdd(); - user = await users.get(user.id); - expect(user.guilds.length).to.equal(1); + user = await Mock.users.get(user.id); + expect(user.guildIds.length).to.equal(1); }); it('invite has reached max uses, is deleted', async () => { @@ -86,19 +81,9 @@ describe('guild-member-add', () => { it('invalid invite code, rejected', async () => { invite.id = ''; - await expect(guildMemberAdd()).to.be.rejectedWith('Invite Not Found'); }); - it('valid invite and code, emits to guild room', async () => { - const to = spy.on(ws.io, 'to'); - - await guildMemberAdd(); - - guild = await guilds.get(guild.id); - expect(to).to.have.been.called.with(guild.id); - }); - function guildMemberAdd() { return event.invoke(ws, client, { inviteCode: invite.id }); } diff --git a/backend/test/integration/ws/guild-member-remove.tests.ts b/backend/test/integration/ws/guild-member-remove.tests.ts index 1bc15b0f..125b340e 100644 --- a/backend/test/integration/ws/guild-member-remove.tests.ts +++ b/backend/test/integration/ws/guild-member-remove.tests.ts @@ -4,15 +4,15 @@ import io from 'socket.io-client'; import { Mock } from '../../mock/mock'; import { GuildDocument } from '../../../src/data/models/guild'; import { expect } from 'chai'; -import { GuildMemberDocument } from '../../../src/data/models/guild-member'; -import { User, UserDocument } from '../../../src/data/models/user'; +import { GuildMember, GuildMemberDocument } from '../../../src/data/models/guild-member'; +import { SelfUserDocument, User } from '../../../src/data/models/user'; describe('guild-member-remove', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; let event: GuildMemberRemove; let ws: WebSocket; - let user: UserDocument; + let user: SelfUserDocument; let member: GuildMemberDocument; let guild: GuildDocument; @@ -32,8 +32,7 @@ describe('guild-member-remove', () => { }); it('cannot leave owned guild, rejected', async () => { - makeGuildOwner(); - + await makeGuildOwner(); await expect(guildMemberRemove()).to.be.rejectedWith('You cannot leave a guild you own'); }); @@ -41,34 +40,36 @@ describe('guild-member-remove', () => { await guildMemberRemove(); user = await User.findById(user.id); - expect(user.guilds).to.not.include(guild.id); + expect(user.guildIds).to.not.include(guild.id); }); it('kick noob member, as noob member, missing permissions', async () => { await makeAnotherNoob(); - await expect(guildMemberRemove()).to.be.rejectedWith('Missing Permissions'); }); it('kick noob member, as guild owner, removed from user guilds', async () => { - makeGuildOwner(); + await makeGuildOwner(); - member = guild.members[1] as GuildMemberDocument; + member = await GuildMember.findOne({ + guildId: guild.id, + userId: { $ne: guild.ownerId }, + }); await guildMemberRemove(); user = await User.findById(member.userId); - expect(user.guilds).to.not.include(guild.id); + expect(user.guildIds).to.not.include(guild.id); }); function guildMemberRemove() { - return event.invoke(ws, client, { - guildId: guild.id, - memberId: member.id, - }); + return event.invoke(ws, client, { memberId: member.id }); } - function makeGuildOwner() { - member = guild.members[0] as GuildMemberDocument; + async function makeGuildOwner() { + const member = await GuildMember.findOne({ + guildId: guild.id, + userId: guild.ownerId, + }); ws.sessions.set(client.id, guild.ownerId); } diff --git a/backend/test/integration/ws/guild-update.tests.ts b/backend/test/integration/ws/guild-update.tests.ts index c773a0de..44e29ea5 100644 --- a/backend/test/integration/ws/guild-update.tests.ts +++ b/backend/test/integration/ws/guild-update.tests.ts @@ -2,11 +2,12 @@ import { WebSocket } from '../../../src/ws/websocket'; import io from 'socket.io-client'; import GuildUpdate from '../../../src/ws/ws-events/guild-update'; import { Guild, GuildDocument } from '../../../src/data/models/guild'; -import { UserDocument } from '../../../src/data/models/user'; +import { SelfUserDocument, UserDocument } from '../../../src/data/models/user'; import { Mock } from '../../mock/mock'; import { expect } from 'chai'; import { GuildMemberDocument } from '../../../src/data/models/guild-member'; import { generateSnowflake } from '../../../src/data/snowflake-entity'; +import { PermissionTypes } from '../../../src/types/permission-types'; describe('guild-update', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; @@ -14,11 +15,10 @@ describe('guild-update', () => { let event: GuildUpdate; let guild: GuildDocument; - let user: UserDocument; let member: GuildMemberDocument; beforeEach(async () => { - ({ ws, event, guild, member, user } = await Mock.defaultSetup(client, GuildUpdate)); + ({ ws, event, guild, member } = await Mock.defaultSetup(client, GuildUpdate)); }); afterEach(async () => Mock.afterEach(ws)); @@ -44,76 +44,10 @@ describe('guild-update', () => { await expect(guildUpdate({})).to.be.fulfilled; }); - it('name acronym updated', async () => { - makeOwner(); - await guildUpdate({ name: 'K E K K' }); - - guild = await Guild.findById(guild.id); - expect(guild.nameAcronym).to.equal('KEK'); - }); - - it('contains banned keys, rejected', async () => { - makeOwner(); - await expect(guildUpdate({ id: '123' })).to.be.rejectedWith('Contains readonly values'); - }); - - it('reordered roles correctly, fulfilled', async () => { - makeOwner(); - - const roleIds = guild.roles.map(r => r.id); - await expect(guildUpdate({ roles: roleIds as any })).to.be.fulfilled; - }); - - it('reordered roles, added a role, rejected', async () => { - makeOwner(); - const newRole = await Mock.role(guild); - const roleIds = guild.roles.concat(newRole).map(r => r.id); - - await expect(guildUpdate({ roles: roleIds as any })).to.be.rejectedWith('Cannot add or remove roles this way'); - }); - - it('reordered roles, removed a role, rejected', async () => { - makeOwner(); - await Mock.role(guild); - const roleIds = guild.roles.slice(0, 1); - - await expect(guildUpdate({ roles: roleIds as any })).to.be.rejectedWith('Cannot add or remove roles this way'); - }); - - it('reordered roles, moved everyone role, rejected', async () => { - makeOwner(); - const roleIds = [generateSnowflake(), ...guild.roles]; - - await expect(guildUpdate({ roles: roleIds as any })).to.be.rejectedWith('Cannot reorder the @everyone role'); - }); - - it('sorted channels, did not add channels, fulfilled', async () => { - makeOwner(); - - const roleIds = guild.channels.map(r => r.id); - await expect(guildUpdate({ channels: roleIds as any })).to.be.fulfilled; - }); - - it('sorted channels, added a channel, rejected', async () => { - makeOwner(); - const channel = await Mock.channel(guild); - const channelIds = guild.channels.concat(channel).map(r => r.id); - - await expect(guildUpdate({ channels: channelIds as any })).to.be.rejectedWith('Cannot add or remove channels this way'); - }); - - it('sorted channels, removed a channel, rejected', async () => { - makeOwner(); - await Mock.channel(guild); - const channelIds = guild.channels.slice(0, 1); - - await expect(guildUpdate({ channels: channelIds as any })).to.be.rejectedWith('Cannot add or remove channels this way'); - }); - - function guildUpdate(partialGuild: Partial) { + function guildUpdate(partial: Partial) { return event.invoke(ws, client, { guildId: guild.id, - partialGuild, + ...partial, }); } diff --git a/backend/test/integration/ws/invite-create.tests.ts b/backend/test/integration/ws/invite-create.tests.ts index ad4dfd79..fb369ef9 100644 --- a/backend/test/integration/ws/invite-create.tests.ts +++ b/backend/test/integration/ws/invite-create.tests.ts @@ -4,7 +4,8 @@ import io from 'socket.io-client'; import { Mock } from '../../mock/mock'; import { GuildDocument } from '../../../src/data/models/guild'; import { expect } from 'chai'; -import { Role, RoleDocument } from '../../../src/data/models/role'; +import { RoleDocument } from '../../../src/data/models/role'; +import { PermissionTypes } from '../../../src/types/permission-types'; describe('invite-create', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; @@ -31,10 +32,10 @@ describe('invite-create', () => { await expect(inviteCreate()).to.be.rejectedWith('Missing Permissions'); }); - function inviteCreate() { + function inviteCreate(options?: Partial) { return event.invoke(ws, client, { guildId: guild.id, - options: {}, + options, }); } }); diff --git a/backend/test/integration/ws/invite-delete.tests.ts b/backend/test/integration/ws/invite-delete.tests.ts index 857b84c2..c5860642 100644 --- a/backend/test/integration/ws/invite-delete.tests.ts +++ b/backend/test/integration/ws/invite-delete.tests.ts @@ -6,6 +6,7 @@ import { GuildDocument } from '../../../src/data/models/guild'; import { Invite, InviteDocument } from '../../../src/data/models/invite'; import { expect } from 'chai'; import { RoleDocument } from '../../../src/data/models/role'; +import { PermissionTypes } from '../../../src/types/permission-types'; describe('invite-delete', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; @@ -38,7 +39,6 @@ describe('invite-delete', () => { it('guild member, has manage guild perms, fulfilled', async () => { await Mock.giveRolePerms(role, PermissionTypes.General.MANAGE_GUILD); - await expect(inviteDelete()).to.be.fulfilled; }); @@ -52,7 +52,6 @@ describe('invite-delete', () => { it('invite does not exist, rejected', async () => { await invite.deleteOne(); - await expect(inviteDelete()).to.be.rejectedWith('Invite Not Found'); }); diff --git a/backend/test/integration/ws/message-create.tests.ts b/backend/test/integration/ws/message-create.tests.ts index 75d58721..487a16f0 100644 --- a/backend/test/integration/ws/message-create.tests.ts +++ b/backend/test/integration/ws/message-create.tests.ts @@ -36,13 +36,11 @@ describe('message-create', () => { it('user is guild member without chat perms, rejected', async () => { await Mock.clearRolePerms(guild); - await expect(messageCreate()).to.be.rejectedWith('Missing Permissions'); }); it('user is guild owner, fulfilled', async () => { ws.sessions.set(client.id, guild.ownerId); - await expect(messageCreate()).to.be.fulfilled; }); @@ -53,20 +51,19 @@ describe('message-create', () => { expect(channel.lastMessageId).to.be.a('string'); }); - it('lastReadMessages updated in author', async () => { + it('lastReadMessageIds updated on author document', async () => { await messageCreate(); channel = await Channel.findById(channel.id) as any; user = await User.findById(user.id) as any; - expect(user.lastReadMessages[channel.id]).to.equal(channel.lastMessageId); + expect(user.lastReadMessageIds[channel.id]).to.equal(channel.lastMessageId); }); - function messageCreate() { + function messageCreate(options?: Partial) { return event.invoke(ws, client, { channelId: channel.id, - partialMessage: { - content: 'hi' - }, + content: 'hi', + ...options, }); } }); diff --git a/backend/test/integration/ws/message-delete.tests.ts b/backend/test/integration/ws/message-delete.tests.ts index fb20b3b8..1be19342 100644 --- a/backend/test/integration/ws/message-delete.tests.ts +++ b/backend/test/integration/ws/message-delete.tests.ts @@ -7,7 +7,7 @@ import { GuildDocument } from '../../../src/data/models/guild'; import { UserDocument } from '../../../src/data/models/user'; import { Message, MessageDocument } from '../../../src/data/models/message'; import { generateSnowflake } from '../../../src/data/snowflake-entity'; -import { Channel } from '../../../src/data/models/channel'; +import { Channel, ChannelDocument } from '../../../src/data/models/channel'; describe('message-delete', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; @@ -17,13 +17,12 @@ describe('message-delete', () => { let user: UserDocument; let guild: GuildDocument; let message: MessageDocument; - let channelId: string; + let channel: ChannelDocument; beforeEach(async () => { - ({ ws, event, user, guild } = await Mock.defaultSetup(client, MessageDelete)); + ({ ws, event, channel, guild } = await Mock.defaultSetup(client, MessageDelete)); - channelId = guild.channels[0].id; - message = await Mock.message(user, channelId); + message = await Mock.message(user, channel.id); }); afterEach(async () => await Mock.afterEach(ws)); @@ -62,25 +61,24 @@ describe('message-delete', () => { it('message does not exist, rejected', async () => { await message.deleteOne(); - await expect(deleteMessage()).to.be.rejectedWith('Message Not Found'); }); it('message is last channel message, channel last message updated to previous', async () => { const previousMessage = message; - message = await Mock.message(user, channelId); + message = await Mock.message(user, channel.id); await deleteMessage(); - const channel = await Channel.findById(channelId); - expect(channel.lastMessageId).to.equal(previousMessage.id); + const newChannel = await Channel.findById(channel.id); + expect(newChannel.lastMessageId).to.equal(previousMessage.id); }); it('message is only channel message, channel last message updated to null', async () => { await deleteMessage(); - const channel = await Channel.findById(channelId); - expect(channel.lastMessageId).to.be.null; + const newChannel = await Channel.findById(channel.id); + expect(newChannel.lastMessageId).to.be.null; }); async function deleteMessage() { diff --git a/backend/test/integration/ws/message-update.tests.ts b/backend/test/integration/ws/message-update.tests.ts index 28b7efdc..1dc23db9 100644 --- a/backend/test/integration/ws/message-update.tests.ts +++ b/backend/test/integration/ws/message-update.tests.ts @@ -1,15 +1,13 @@ import MessageUpdate from '../../../src/ws/ws-events/message-update'; import { WebSocket } from '../../../src/ws/websocket'; -import Deps from '../../../src/utils/deps'; import { expect } from 'chai'; import io from 'socket.io-client'; import { Mock } from '../../mock/mock'; import { GuildDocument } from '../../../src/data/models/guild'; -import { User, UserDocument } from '../../../src/data/models/user'; +import { SelfUserDocument } from '../../../src/data/models/user'; import { ChannelDocument } from '../../../src/data/models/channel'; import { MessageDocument } from '../../../src/data/models/message'; import { generateSnowflake } from '../../../src/data/snowflake-entity'; -import { Partial } from '../../../src/data/types/ws-types'; describe('message-update', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; @@ -17,7 +15,7 @@ describe('message-update', () => { let channel: ChannelDocument; let event: MessageUpdate; let ws: WebSocket; - let user: UserDocument; + let user: SelfUserDocument; let guild: GuildDocument; let message: MessageDocument; @@ -30,30 +28,22 @@ describe('message-update', () => { afterEach(async () => await Mock.afterEach(ws)); after(async () => await Mock.after(client)); - it('user not author, rejected', async () => { + it('user not message author, rejected', async () => { await makeGuildOwner(); - await expect(messageUpdate()).to.be.rejectedWith('Unauthorized'); }); it('message does not exist, rejected', async () => { - message.id = generateSnowflake(); - - await expect(messageUpdate()).to.be.rejectedWith('Message Not Found'); - }); - - it('update includes banned keys, rejected', async () => { - await expect(messageUpdate({ id: '123' })).to.be.rejectedWith('Contains readonly values'); + await expect(messageUpdate({ + messageId: generateSnowflake(), + })).to.be.rejectedWith('Message Not Found'); }); function messageUpdate(options?: Partial) { return event.invoke(ws, client, { messageId: message.id, - partialMessage: { - ...options, - content: 'test', - }, - withEmbed: true + content: 'test', + ...options, }); } diff --git a/backend/test/integration/ws/ready.tests.ts b/backend/test/integration/ws/ready.tests.ts index 6cb3b15a..39bef1ae 100644 --- a/backend/test/integration/ws/ready.tests.ts +++ b/backend/test/integration/ws/ready.tests.ts @@ -7,8 +7,8 @@ import Users from '../../../src/data/users'; import { Mock } from '../../mock/mock'; import { WebSocket } from '../../../src/ws/websocket'; import io from 'socket.io-client'; -import { SystemBot } from '../../../src/system/bot'; import { GuildDocument } from '../../../src/data/models/guild'; +import { Channel } from '../../../src/data/models/channel'; describe('ready', () => { const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; @@ -16,7 +16,7 @@ describe('ready', () => { let event: Ready; let users: Users; let token: string; - let user: UserDocument; + let user: SelfUserDocument; let guild: GuildDocument; let ws: WebSocket; @@ -45,25 +45,6 @@ describe('ready', () => { expect(rooms()).to.include(user.id); }); - it('joins system bot room', async () => { - const systemBot = Deps.get(SystemBot); - await systemBot.init(); - - await ready(); - - expect(rooms()).to.include(systemBot.self.id); - }); - - it('joins dm channel room', async () => { - const dm = await Mock.channel({ type: 'DM' }); - dm.memberIds.push(user.id); - await dm.update(dm); - - await ready(); - - expect(rooms()).to.include(dm.id); - }); - it('joins self room', async () => { await ready(); expect(rooms()).to.include(user.id); @@ -82,8 +63,11 @@ describe('ready', () => { await makeOwner(); await ready(); - expect(rooms()).to.contain(guild.channels[0].id); - expect(rooms()).to.contain(guild.channels[1].id); + const guildChannels = await Channel.find({ guildId: guild.id }); + const ids = guildChannels.map(c => c.id); + + expect(rooms()).to.contain(ids[0]); + expect(rooms()).to.contain(ids[1]); expect(rooms()).to.contain(newChannel.id); }); diff --git a/backend/test/integration/ws/remove-friend.tests.ts b/backend/test/integration/ws/remove-friend.tests.ts deleted file mode 100644 index f6524bf8..00000000 --- a/backend/test/integration/ws/remove-friend.tests.ts +++ /dev/null @@ -1,62 +0,0 @@ -import RemoveFriend from '../../../src/ws/ws-events/remove-friend'; -import { WebSocket } from '../../../src/ws/websocket'; -import io from 'socket.io-client'; -import { Mock } from '../../mock/mock'; -import { expect } from 'chai'; -import { generateSnowflake } from '../../../src/data/snowflake-entity'; -import { SelfUserDocument, User, UserDocument } from '../../../src/data/models/user'; - -describe('remove-friend', () => { - const client = (io as any)(`http://localhost:${process.env.PORT}`) as any; - let event: RemoveFriend; - let ws: WebSocket; - - let sender: SelfUserDocument; - let friend: SelfUserDocument; - - beforeEach(async () => { - ({ event, ws, user: sender as any } = await Mock.defaultSetup(client, RemoveFriend)); - friend = await Mock.self(); - }); - - afterEach(async () => await Mock.afterEach(ws)); - after(async () => await Mock.after(client)); - - it('user sends request, fulfilled', async () => { - await expect(removeFriend()).to.be.fulfilled; - }); - - it('user removes self as friend, rejected', async () => { - friend.id = sender.id; - - await expect(removeFriend()).to.be.rejectedWith('You cannot remove yourself as a friend'); - }); - - it('removes non existing friend, rejected', async () => { - friend.id = generateSnowflake(); - - await expect(removeFriend()).to.be.rejectedWith('User Not Found'); - }); - - it('sender cancels request, friend request removed', async () => { - await removeFriend(); - - sender = await User.findById(sender.id) as any; - - expect(sender.friendRequestIds).to.be.empty; - }); - - it('sender removes friend, friend removed on both users', async () => { - await removeFriend(); - - friend = await User.findById(friend.id) as any; - sender = await User.findById(sender.id) as any; - - expect(friend.friendIds.length).to.equal(0); - expect(sender.friendIds.length).to.equal(0); - }); - - async function removeFriend() { - return event.invoke(ws, client, { friendId: friend.id }); - } -}); diff --git a/backend/test/integration/ws/user-update.tests.ts b/backend/test/integration/ws/user-update.tests.ts index 5a4ab96b..0750a872 100644 --- a/backend/test/integration/ws/user-update.tests.ts +++ b/backend/test/integration/ws/user-update.tests.ts @@ -2,7 +2,7 @@ import UserUpdate from '../../../src/ws/ws-events/user-update'; import { WebSocket } from '../../../src/ws/websocket'; import io from 'socket.io-client'; import{ Mock } from '../../mock/mock'; -import{ SelfUserDocument, User, UserDocument } from '../../../src/data/models/user'; +import{ SelfUserDocument, User } from '../../../src/data/models/user'; import { expect } from 'chai'; import Deps from '../../../src/utils/deps'; import Users from '../../../src/data/users'; diff --git a/backend/test/mock/mock.ts b/backend/test/mock/mock.ts index 662744a5..140bf1a3 100644 --- a/backend/test/mock/mock.ts +++ b/backend/test/mock/mock.ts @@ -1,7 +1,7 @@ import { Channel, ChannelDocument } from '../../src/data/models/channel'; import { Guild, GuildDocument } from '../../src/data/models/guild'; import { GuildMember, GuildMemberDocument } from '../../src/data/models/guild-member'; -import { User, SelfUserDocument, UserDocument } from '../../src/data/models/user'; +import { User, SelfUserDocument } from '../../src/data/models/user'; import { generateSnowflake } from '../../src/data/snowflake-entity'; import { Role, RoleDocument } from '../../src/data/models/role'; import { Message } from '../../src/data/models/message'; @@ -17,15 +17,17 @@ import GuildMembers from '../../src/data/guild-members'; import Channels from '../../src/data/channels'; import { PermissionTypes } from '../../src/types/permission-types'; import { REST } from '../../src/rest/server'; +import Users from '../../src/data/users'; -// TODO: mostly replace with data wrappers +// TODO: eventually replace with data wrappers export class Mock { - private static channels = Deps.get(Channels); - private static guilds = Deps.get(Guilds); - private static guildMembers = Deps.get(GuildMembers); - private static messages = Deps.get(Messages); - private static invites = Deps.get(Invites); - private static roles = Deps.get(Roles); + public static channels = Deps.get(Channels); + public static guilds = Deps.get(Guilds); + public static guildMembers = Deps.get(GuildMembers); + public static messages = Deps.get(Messages); + public static invites = Deps.get(Invites); + public static roles = Deps.get(Roles); + public static users = Deps.get(Users); public static async defaultSetup(client: any, eventType: any = function() {}) { Deps.get(REST); @@ -46,6 +48,11 @@ export class Mock { Mock.ioClient(client); ws.sessions.set(client.id, user.id); + // TODO: + // rename 'user' -> ownerUser + // rename 'member' -> ownerMember + // add 'ownerUser' + // add 'ownerMember' return { event, guild, user, member, ws, role, channel }; } public static async afterEach(ws) { @@ -56,7 +63,7 @@ export class Mock { client.disconnect(); } - // FIXME: garbage coding + // FIXME: less maintainable than daily YouTube uploads public static ioClient(client: any) { client.rooms = []; client.sockets = { @@ -100,24 +107,15 @@ export class Mock { username: `mock-user-${generateSnowflake()}`, discriminator: 1, ...options, - } as any); + } as any) as any; } public static async self(guildIds: string[] = []) { return await this.user({ guildIds }) as any as SelfUserDocument; } - public static async bot(guildIds: string[] = []): Promise { - return await User.create({ - bot: true, - email: `${generateSnowflake()}@gmail.com`, - guildIds, - status: 'ONLINE', - discriminator: 1, - username: `mock-bot-${generateSnowflake()}`, - } as any) as any as SelfUserDocument; + return await Mock.user({ bot: true, guildIds }); } - public static guildMember(user: SelfUserDocument, guild: GuildDocument): Promise { return this.guildMembers.create(guild.id, user); } @@ -130,21 +128,15 @@ export class Mock { public static invite(guildId: string, options?: InviteTypes.Options) { return this.invites.create({ options, guildId }, generateSnowflake()); } - public static everyoneRole(guildId: string, permissions = PermissionTypes.defaultPermissions) { return this.roles.create(guildId, { name: '@everyone', permissions }); } public static async clearRolePerms(guild: Entity.Guild) { - await Role.updateMany( - { guildId: guild.id }, - { permissions: 0 }, - ); + await Role.updateMany({ guildId: guild.id }, { permissions: 0 }); } - public static async giveRolePerms(role: RoleDocument, permissions: PermissionTypes.Permission) { - role.permissions |= permissions; - await role.save(); + await role.updateOne({ permissions: role.permissions | permissions }); } public static async giveEveryoneAdmin(guild: Entity.Guild) { diff --git a/backend/test/test.ts b/backend/test/test.ts index 744b4928..c11af1be 100644 --- a/backend/test/test.ts +++ b/backend/test/test.ts @@ -46,19 +46,19 @@ console.log(`${space(48 * 3)}TESTS${space(54 * 2)}`.bgWhite.black); // TODO: import('./integration/ws/channel-delete.tests'); // TODO: import('./integration/ws/channel-update.tests'); import('./integration/ws/guild-member-add.tests'); //fail - // import('./integration/ws/guild-member-remove.tests'); //fail - // import('./integration/ws/guild-member-update.tests'); // fail - // import('./integration/ws/guild-create.tests'); // fail - // import('./integration/ws/guild-delete.tests'); // fail - // import('./integration/ws/guild-update.tests'); // fail - // import('./integration/ws/invite-create.tests'); //fail - // import('./integration/ws/invite-delete.tests'); //fail - // import('./integration/ws/message-create.tests'); //fail - // import('./integration/ws/message-update.tests'); // fail - // import('./integration/ws/message-delete.tests'); // fail - // import('./integration/ws/ready.tests'); + import('./integration/ws/guild-member-remove.tests'); //fail + import('./integration/ws/guild-member-update.tests'); // fail + import('./integration/ws/guild-create.tests'); // fail + import('./integration/ws/guild-delete.tests'); // fail + import('./integration/ws/guild-update.tests'); // fail + import('./integration/ws/invite-create.tests'); //fail + import('./integration/ws/invite-delete.tests'); //fail + import('./integration/ws/message-create.tests'); //fail + import('./integration/ws/message-update.tests'); // fail + import('./integration/ws/message-delete.tests'); // fail + import('./integration/ws/ready.tests'); import('./integration/ws/user-update.tests'); - // import('./integration/ws/ws-guard.tests'); // fail + import('./integration/ws/ws-guard.tests'); // fail import('./unit/models/app.tests'); import('./unit/models/channel.tests'); diff --git a/backend/test/unit/ws/ws-cooldowns.tests.ts b/backend/test/unit/ws/ws-cooldowns.tests.ts index 13af27dc..e3d70096 100644 --- a/backend/test/unit/ws/ws-cooldowns.tests.ts +++ b/backend/test/unit/ws/ws-cooldowns.tests.ts @@ -21,16 +21,14 @@ describe('ws-cooldowns', () => { }); it('handle, exceeds max cooldowns, throws error', () => { - for (let i = 0; i < 60; i++) - handle(); - + for (let i = 0; i < 60; i++) handle(); expect(handle).to.throw('You are doing too many things at once!'); }); it('handle, prunes old cooldowns', () => { cooldowns.active.set(clientId, [ - { eventName: 'ADD_FRIEND', timestamp: new Date(0).getTime() }, - { eventName: 'ADD_FRIEND', timestamp: new Date(0).getTime() }, + { eventName: 'MESSAGE_CREATE', timestamp: new Date(0).getTime() }, + { eventName: 'MESSAGE_CREATE', timestamp: new Date(0).getTime() }, ]); handle(); @@ -39,6 +37,6 @@ describe('ws-cooldowns', () => { }); function handle() { - return cooldowns.handle(clientId, 'ADD_FRIEND'); + return cooldowns.handle(clientId, 'MESSAGE_CREATE'); } }); diff --git a/types/ws.d.ts b/types/ws.d.ts index 8b641f8c..23858709 100644 --- a/types/ws.d.ts +++ b/types/ws.d.ts @@ -163,8 +163,8 @@ declare namespace WS { } export interface GuildUpdate { guildId: string; - name: string; - iconURL: string; + name?: string; + iconURL?: string; } export interface InviteCreate { guildId: string;