From 315d2f5ebe0fae583b354c767ae756b63a09ca68 Mon Sep 17 00:00:00 2001 From: Tanay Date: Sat, 21 Mar 2026 11:52:26 +0000 Subject: [PATCH 1/3] test(apps): add tests for ModifyDeleter accessor Fixes #39795 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../server/accessors/ModifyDeleter.spec.ts | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts diff --git a/packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts b/packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts new file mode 100644 index 0000000000000..9ad634eb4f39e --- /dev/null +++ b/packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts @@ -0,0 +1,126 @@ +import { AsyncTest, Expect, SetupFixture, SpyOn } from 'alsatian'; + +import type { IMessage } from '../../../src/definition/messages'; +import type { IUser } from '../../../src/definition/users'; +import { UserType } from '../../../src/definition/users'; +import { ModifyDeleter } from '../../../src/server/accessors/ModifyDeleter'; +import type { AppBridges, MessageBridge, RoomBridge, UserBridge } from '../../../src/server/bridges'; +import { TestData } from '../../test-data/utilities'; + +export class ModifyDeleterAccessorTestFixture { + private mockAppId: string; + + private mockRoomBridge: RoomBridge; + + private mockMessageBridge: MessageBridge; + + private mockUserBridge: UserBridge; + + private mockBridges: AppBridges; + + @SetupFixture + public setupFixture() { + this.mockAppId = 'testing-app'; + + this.mockRoomBridge = { + doDelete(roomId: string, appId: string): Promise { + return Promise.resolve(); + }, + doRemoveUsers(roomId: string, usernames: Array, appId: string): Promise { + return Promise.resolve(); + }, + } as RoomBridge; + + this.mockMessageBridge = { + doDelete(message: IMessage, user: IUser, appId: string): Promise { + return Promise.resolve(); + }, + } as MessageBridge; + + this.mockUserBridge = { + doDeleteUsersCreatedByApp(appId: string, userType: UserType): Promise { + return Promise.resolve(true); + }, + } as UserBridge; + + const rmBridge = this.mockRoomBridge; + const msgBridge = this.mockMessageBridge; + const usrBridge = this.mockUserBridge; + this.mockBridges = { + getRoomBridge() { + return rmBridge; + }, + getMessageBridge() { + return msgBridge; + }, + getUserBridge() { + return usrBridge; + }, + } as AppBridges; + } + + @AsyncTest() + public async useModifyDeleter() { + Expect(() => new ModifyDeleter(this.mockBridges, this.mockAppId)).not.toThrow(); + + const md = new ModifyDeleter(this.mockBridges, this.mockAppId); + + const spRoom = SpyOn(this.mockRoomBridge, 'doDelete'); + const spMsg = SpyOn(this.mockMessageBridge, 'doDelete'); + const spUser = SpyOn(this.mockUserBridge, 'doDeleteUsersCreatedByApp'); + + await md.deleteRoom('room-id'); + Expect(this.mockRoomBridge.doDelete).toHaveBeenCalledWith('room-id', this.mockAppId); + + const message = TestData.getMessage(); + const user = TestData.getUser(); + await md.deleteMessage(message, user); + Expect(this.mockMessageBridge.doDelete).toHaveBeenCalledWith(message, user, this.mockAppId); + + const result = await md.deleteUsers('app-id', UserType.APP); + Expect(this.mockUserBridge.doDeleteUsersCreatedByApp).toHaveBeenCalledWith('app-id', UserType.APP); + Expect(result).toBe(true); + + await md.deleteUsers('app-id', UserType.BOT); + Expect(this.mockUserBridge.doDeleteUsersCreatedByApp).toHaveBeenCalledWith('app-id', UserType.BOT); + + spRoom.restore(); + spMsg.restore(); + spUser.restore(); + } + + @AsyncTest() + public async removeUsersFromRoom() { + const md = new ModifyDeleter(this.mockBridges, this.mockAppId); + + const spRemove = SpyOn(this.mockRoomBridge, 'doRemoveUsers'); + + const usernames = ['user1', 'user2', 'user3']; + await md.removeUsersFromRoom('room-id', usernames); + Expect(this.mockRoomBridge.doRemoveUsers).toHaveBeenCalledWith('room-id', usernames, this.mockAppId); + + spRemove.restore(); + } + + @AsyncTest() + public async removeUsersFromRoomAtBoundary() { + const md = new ModifyDeleter(this.mockBridges, this.mockAppId); + + const spRemove = SpyOn(this.mockRoomBridge, 'doRemoveUsers'); + + const fiftyUsers = Array.from({ length: 50 }, (_, i) => `user${i}`); + await md.removeUsersFromRoom('room-id', fiftyUsers); + Expect(this.mockRoomBridge.doRemoveUsers).toHaveBeenCalledWith('room-id', fiftyUsers, this.mockAppId); + + spRemove.restore(); + } + + @AsyncTest() + public async removeUsersFromRoomThrowsWhenExceedingLimit() { + const md = new ModifyDeleter(this.mockBridges, this.mockAppId); + + const tooManyUsers = Array.from({ length: 51 }, (_, i) => `user${i}`); + + await Expect(async () => md.removeUsersFromRoom('room-id', tooManyUsers)).toThrowAsync(); + } +} From 22c494ecad985836995d0e410e8d8cb998c00a3b Mon Sep 17 00:00:00 2001 From: Tanay Date: Sat, 21 Mar 2026 12:02:51 +0000 Subject: [PATCH 2/3] test(apps): add tests for ModifyDeleter accessor Fixes #39795 --- .../server/accessors/ModifyDeleter.spec.ts | 126 ------------------ .../server/accessors/ModifyDeleter.test.ts | 126 ++++++++++++++++++ 2 files changed, 126 insertions(+), 126 deletions(-) delete mode 100644 packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts create mode 100644 packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts diff --git a/packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts b/packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts deleted file mode 100644 index 9ad634eb4f39e..0000000000000 --- a/packages/apps-engine/tests/server/accessors/ModifyDeleter.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { AsyncTest, Expect, SetupFixture, SpyOn } from 'alsatian'; - -import type { IMessage } from '../../../src/definition/messages'; -import type { IUser } from '../../../src/definition/users'; -import { UserType } from '../../../src/definition/users'; -import { ModifyDeleter } from '../../../src/server/accessors/ModifyDeleter'; -import type { AppBridges, MessageBridge, RoomBridge, UserBridge } from '../../../src/server/bridges'; -import { TestData } from '../../test-data/utilities'; - -export class ModifyDeleterAccessorTestFixture { - private mockAppId: string; - - private mockRoomBridge: RoomBridge; - - private mockMessageBridge: MessageBridge; - - private mockUserBridge: UserBridge; - - private mockBridges: AppBridges; - - @SetupFixture - public setupFixture() { - this.mockAppId = 'testing-app'; - - this.mockRoomBridge = { - doDelete(roomId: string, appId: string): Promise { - return Promise.resolve(); - }, - doRemoveUsers(roomId: string, usernames: Array, appId: string): Promise { - return Promise.resolve(); - }, - } as RoomBridge; - - this.mockMessageBridge = { - doDelete(message: IMessage, user: IUser, appId: string): Promise { - return Promise.resolve(); - }, - } as MessageBridge; - - this.mockUserBridge = { - doDeleteUsersCreatedByApp(appId: string, userType: UserType): Promise { - return Promise.resolve(true); - }, - } as UserBridge; - - const rmBridge = this.mockRoomBridge; - const msgBridge = this.mockMessageBridge; - const usrBridge = this.mockUserBridge; - this.mockBridges = { - getRoomBridge() { - return rmBridge; - }, - getMessageBridge() { - return msgBridge; - }, - getUserBridge() { - return usrBridge; - }, - } as AppBridges; - } - - @AsyncTest() - public async useModifyDeleter() { - Expect(() => new ModifyDeleter(this.mockBridges, this.mockAppId)).not.toThrow(); - - const md = new ModifyDeleter(this.mockBridges, this.mockAppId); - - const spRoom = SpyOn(this.mockRoomBridge, 'doDelete'); - const spMsg = SpyOn(this.mockMessageBridge, 'doDelete'); - const spUser = SpyOn(this.mockUserBridge, 'doDeleteUsersCreatedByApp'); - - await md.deleteRoom('room-id'); - Expect(this.mockRoomBridge.doDelete).toHaveBeenCalledWith('room-id', this.mockAppId); - - const message = TestData.getMessage(); - const user = TestData.getUser(); - await md.deleteMessage(message, user); - Expect(this.mockMessageBridge.doDelete).toHaveBeenCalledWith(message, user, this.mockAppId); - - const result = await md.deleteUsers('app-id', UserType.APP); - Expect(this.mockUserBridge.doDeleteUsersCreatedByApp).toHaveBeenCalledWith('app-id', UserType.APP); - Expect(result).toBe(true); - - await md.deleteUsers('app-id', UserType.BOT); - Expect(this.mockUserBridge.doDeleteUsersCreatedByApp).toHaveBeenCalledWith('app-id', UserType.BOT); - - spRoom.restore(); - spMsg.restore(); - spUser.restore(); - } - - @AsyncTest() - public async removeUsersFromRoom() { - const md = new ModifyDeleter(this.mockBridges, this.mockAppId); - - const spRemove = SpyOn(this.mockRoomBridge, 'doRemoveUsers'); - - const usernames = ['user1', 'user2', 'user3']; - await md.removeUsersFromRoom('room-id', usernames); - Expect(this.mockRoomBridge.doRemoveUsers).toHaveBeenCalledWith('room-id', usernames, this.mockAppId); - - spRemove.restore(); - } - - @AsyncTest() - public async removeUsersFromRoomAtBoundary() { - const md = new ModifyDeleter(this.mockBridges, this.mockAppId); - - const spRemove = SpyOn(this.mockRoomBridge, 'doRemoveUsers'); - - const fiftyUsers = Array.from({ length: 50 }, (_, i) => `user${i}`); - await md.removeUsersFromRoom('room-id', fiftyUsers); - Expect(this.mockRoomBridge.doRemoveUsers).toHaveBeenCalledWith('room-id', fiftyUsers, this.mockAppId); - - spRemove.restore(); - } - - @AsyncTest() - public async removeUsersFromRoomThrowsWhenExceedingLimit() { - const md = new ModifyDeleter(this.mockBridges, this.mockAppId); - - const tooManyUsers = Array.from({ length: 51 }, (_, i) => `user${i}`); - - await Expect(async () => md.removeUsersFromRoom('room-id', tooManyUsers)).toThrowAsync(); - } -} diff --git a/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts b/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts new file mode 100644 index 0000000000000..50a4ff9593671 --- /dev/null +++ b/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts @@ -0,0 +1,126 @@ +import * as assert from 'node:assert'; +import { afterEach, beforeEach, describe, it, mock } from 'node:test'; + +import type { IMessage } from '../../../src/definition/messages'; +import type { IUser } from '../../../src/definition/users'; +import { UserType } from '../../../src/definition/users'; +import { ModifyDeleter } from '../../../src/server/accessors/ModifyDeleter'; +import type { AppBridges, MessageBridge, RoomBridge, UserBridge } from '../../../src/server/bridges'; +import { TestData } from '../../test-data/utilities'; + +describe('ModifyDeleter', () => { + let mockAppId: string; + let mockRoomBridge: RoomBridge; + let mockMessageBridge: MessageBridge; + let mockUserBridge: UserBridge; + let mockBridges: AppBridges; + + beforeEach(() => { + mockAppId = 'testing-app'; + + mockRoomBridge = { + doDelete(roomId: string, appId: string): Promise { + return Promise.resolve(); + }, + doRemoveUsers(roomId: string, usernames: Array, appId: string): Promise { + return Promise.resolve(); + }, + } as RoomBridge; + + mockMessageBridge = { + doDelete(message: IMessage, user: IUser, appId: string): Promise { + return Promise.resolve(); + }, + } as MessageBridge; + + mockUserBridge = { + doDeleteUsersCreatedByApp(appId: string, userType: UserType): Promise { + return Promise.resolve(true); + }, + } as UserBridge; + + const rmBridge = mockRoomBridge; + const msgBridge = mockMessageBridge; + const usrBridge = mockUserBridge; + mockBridges = { + getRoomBridge: () => rmBridge, + getMessageBridge: () => msgBridge, + getUserBridge: () => usrBridge, + } as AppBridges; + }); + + afterEach(() => { + mock.restoreAll(); + }); + + it('deleteRoom delegates to RoomBridge', async () => { + const md = new ModifyDeleter(mockBridges, mockAppId); + const spy = mock.method(mockRoomBridge, 'doDelete'); + + await md.deleteRoom('room-id'); + + assert.strictEqual(spy.mock.calls.length, 1); + assert.deepStrictEqual(spy.mock.calls[0].arguments, ['room-id', mockAppId]); + }); + + it('deleteMessage delegates to MessageBridge', async () => { + const md = new ModifyDeleter(mockBridges, mockAppId); + const spy = mock.method(mockMessageBridge, 'doDelete'); + + const message = TestData.getMessage(); + const user = TestData.getUser(); + await md.deleteMessage(message, user); + + assert.strictEqual(spy.mock.calls.length, 1); + assert.deepStrictEqual(spy.mock.calls[0].arguments, [message, user, mockAppId]); + }); + + it('deleteUsers delegates to UserBridge and returns result', async () => { + const md = new ModifyDeleter(mockBridges, mockAppId); + const spy = mock.method(mockUserBridge, 'doDeleteUsersCreatedByApp'); + + const result = await md.deleteUsers('app-id', UserType.APP); + + assert.strictEqual(result, true); + assert.strictEqual(spy.mock.calls.length, 1); + assert.deepStrictEqual(spy.mock.calls[0].arguments, ['app-id', UserType.APP]); + + await md.deleteUsers('app-id', UserType.BOT); + assert.deepStrictEqual(spy.mock.calls[1].arguments, ['app-id', UserType.BOT]); + }); + + it('removeUsersFromRoom delegates to RoomBridge', async () => { + const md = new ModifyDeleter(mockBridges, mockAppId); + const spy = mock.method(mockRoomBridge, 'doRemoveUsers'); + + const usernames = ['user1', 'user2', 'user3']; + await md.removeUsersFromRoom('room-id', usernames); + + assert.strictEqual(spy.mock.calls.length, 1); + assert.deepStrictEqual(spy.mock.calls[0].arguments, ['room-id', usernames, mockAppId]); + }); + + it('removeUsersFromRoom accepts exactly 50 users', async () => { + const md = new ModifyDeleter(mockBridges, mockAppId); + const spy = mock.method(mockRoomBridge, 'doRemoveUsers'); + + const fiftyUsers = Array.from({ length: 50 }, (_, i) => `user${i}`); + await md.removeUsersFromRoom('room-id', fiftyUsers); + + assert.strictEqual(spy.mock.calls.length, 1); + assert.deepStrictEqual(spy.mock.calls[0].arguments, ['room-id', fiftyUsers, mockAppId]); + }); + + it('removeUsersFromRoom throws when exceeding 50 users', async () => { + const md = new ModifyDeleter(mockBridges, mockAppId); + const spy = mock.method(mockRoomBridge, 'doRemoveUsers'); + + const tooManyUsers = Array.from({ length: 51 }, (_, i) => `user${i}`); + + await assert.rejects( + () => md.removeUsersFromRoom('room-id', tooManyUsers), + { name: 'Error', message: 'A maximum of 50 members can be removed in a single call' }, + ); + assert.strictEqual(spy.mock.calls.length, 0); + }); +}); From 505386bb2f27cb8e5d47a8f811e15f73599e12ea Mon Sep 17 00:00:00 2001 From: Tanay Date: Sun, 29 Mar 2026 19:24:14 +0000 Subject: [PATCH 3/3] test(apps): add call count assertion for deleteUsers second invocation --- .../apps-engine/tests/server/accessors/ModifyDeleter.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts b/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts index 50a4ff9593671..c40eb55617ad5 100644 --- a/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts +++ b/packages/apps-engine/tests/server/accessors/ModifyDeleter.test.ts @@ -86,6 +86,7 @@ describe('ModifyDeleter', () => { assert.deepStrictEqual(spy.mock.calls[0].arguments, ['app-id', UserType.APP]); await md.deleteUsers('app-id', UserType.BOT); + assert.strictEqual(spy.mock.calls.length, 2); assert.deepStrictEqual(spy.mock.calls[1].arguments, ['app-id', UserType.BOT]); });