UNPKG

actionhero

Version:

The reusable, scalable, and quick node.js API server for stateless and stateful applications

590 lines (516 loc) 20.3 kB
import { api, Process, config, utils, specHelper, chatRoom, Connection, } from "./../../src"; const actionhero = new Process(); describe("Core", () => { describe("chatRoom", () => { beforeAll(async () => { await actionhero.start(); for (var room in config!.general!.startingChatRooms as Record< string, Record<string, any> >) { try { await chatRoom.destroy(room); await chatRoom.add(room); } catch (error) { if ( config.errors && typeof config.errors.connectionRoomExists === "function" ) { if ( !error.toString().match(config.errors.connectionRoomExists(room)) ) { throw error; } } } } }); afterAll(async () => { await actionhero.stop(); }); describe("say and clients on separate servers", () => { let client1: Connection; let client2: Connection; let client3: Connection; beforeAll(async () => { client1 = await specHelper.buildConnection(); client2 = await specHelper.buildConnection(); client3 = await specHelper.buildConnection(); client1.verbs("roomAdd", "defaultRoom"); client2.verbs("roomAdd", "defaultRoom"); client3.verbs("roomAdd", "defaultRoom"); await utils.sleep(100); }); afterAll(async () => { client1.destroy(); client2.destroy(); client3.destroy(); await utils.sleep(100); }); test("all connections can join the default room and client #1 can see them", async () => { const { room, membersCount } = await client1.verbs( "roomView", "defaultRoom", ); expect(room).toEqual("defaultRoom"); expect(membersCount).toEqual(3); }); test("all connections can join the default room and client #2 can see them", async () => { const { room, membersCount } = await client2.verbs( "roomView", "defaultRoom", ); expect(room).toEqual("defaultRoom"); expect(membersCount).toEqual(3); }); test("all connections can join the default room and client #3 can see them", async () => { const { room, membersCount } = await client3.verbs( "roomView", "defaultRoom", ); expect(room).toEqual("defaultRoom"); expect(membersCount).toEqual(3); }); test("clients can communicate across the cluster", async () => { await client1.verbs("say", [ "defaultRoom", "Hi", "from", "client", "1", ]); await utils.sleep(100); if (!client2.messages) throw new Error("no messages for client2"); const { message, room, from } = client2.messages[client2.messages.length - 1]; expect(message).toEqual("Hi from client 1"); expect(room).toEqual("defaultRoom"); expect(from).toEqual(client1.id); }); }); describe("chat", () => { beforeEach(async () => { try { await chatRoom.destroy("newRoom"); } catch (error) { // it's fine } }); test("can check if rooms exist", async () => { const found = await chatRoom.exists("defaultRoom"); expect(found).toEqual(true); }); test("can check if a room does not exist", async () => { const found = await chatRoom.exists("missingRoom"); expect(found).toEqual(false); }); test("server can create new room", async () => { const room = "newRoom"; let found; found = await chatRoom.exists(room); expect(found).toEqual(false); await chatRoom.add(room); found = await chatRoom.exists(room); expect(found).toEqual(true); }); test("server cannot create already existing room", async () => { try { await chatRoom.add("defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual("Error: room exists"); } }); test("can enumerate all the rooms in the system", async () => { await chatRoom.add("newRoom"); const rooms = await chatRoom.list(); expect(rooms).toHaveLength(3); ["defaultRoom", "newRoom", "otherRoom"].forEach((r) => { expect(rooms.indexOf(r)).toBeGreaterThan(-1); }); }); test("server can add connections to a LOCAL room", async () => { const client = await specHelper.buildConnection(); expect(client.rooms).toHaveLength(0); const didAdd = await chatRoom.addMember(client.id, "defaultRoom"); expect(didAdd).toEqual(true); expect(client.rooms[0]).toEqual("defaultRoom"); client.destroy(); }); test("will not re-add a member to a room", async () => { const client = await specHelper.buildConnection(); expect(client.rooms).toHaveLength(0); let didAdd = await chatRoom.addMember(client.id, "defaultRoom"); expect(didAdd).toEqual(true); try { didAdd = await chatRoom.addMember(client.id, "defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual( "Error: connection already in this room (defaultRoom)", ); client.destroy(); } }); test("will not add a member to a non-existent room", async () => { const client = await specHelper.buildConnection(); expect(client.rooms).toHaveLength(0); try { await chatRoom.addMember(client.id, "crazyRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual("Error: room does not exist"); client.destroy(); } }); test("server will not remove a member not in a room", async () => { const client = await specHelper.buildConnection(); try { await chatRoom.removeMember(client.id, "defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual( "Error: connection not in this room (defaultRoom)", ); client.destroy(); } }); test("server can remove connections to a room", async () => { const client = await specHelper.buildConnection(); const didAdd = await chatRoom.addMember(client.id, "defaultRoom"); expect(didAdd).toEqual(true); const didRemove = await chatRoom.removeMember(client.id, "defaultRoom"); expect(didRemove).toEqual(true); client.destroy(); }); test("server can destroy a room and connections will be removed", async () => { try { // to ensure it starts empty await chatRoom.destroy("newRoom"); } catch (error) {} const client = await specHelper.buildConnection(); await chatRoom.add("newRoom"); const didAdd = await chatRoom.addMember(client.id, "newRoom"); expect(didAdd).toEqual(true); expect(client.rooms[0]).toEqual("newRoom"); await chatRoom.destroy("newRoom"); expect(client.rooms).toHaveLength(0); // testing for the receipt of this message is a race condition with room.destroy and broadcast in test // client.messages[1].message.should.equal('this room has been deleted') // client.messages[1].room.should.equal('newRoom') client.destroy(); }); test("can get a list of room members", async () => { const client = await specHelper.buildConnection(); expect(client.rooms).toHaveLength(0); await chatRoom.add("newRoom"); await chatRoom.addMember(client.id, "newRoom"); const { room, membersCount } = await chatRoom.roomStatus("newRoom"); expect(room).toEqual("newRoom"); expect(membersCount).toEqual(1); client.destroy(); await chatRoom.destroy("newRoom"); }); test("can see the details of the other members in the room", async () => { const client = await specHelper.buildConnection(); const otherClient = await specHelper.buildConnection(); await chatRoom.add("newRoom"); await chatRoom.addMember(client.id, "newRoom"); await utils.sleep(100); await chatRoom.addMember(otherClient.id, "newRoom"); const { members, membersCount } = await chatRoom.roomStatus("newRoom"); expect(membersCount).toBe(2); expect(members).toEqual({ [client.id]: { id: client.id, joinedAt: expect.any(Number) }, [otherClient.id]: { id: otherClient.id, joinedAt: expect.any(Number), }, }); expect(members[otherClient.id].joinedAt).toBeGreaterThan( members[client.id].joinedAt, ); client.destroy(); otherClient.destroy(); await chatRoom.destroy("newRoom"); }); describe("chat middleware", () => { let clientA: any; let clientB: any; let originalGenerateMessagePayload: (message: any) => any; beforeEach(async () => { originalGenerateMessagePayload = api.chatRoom.generateMessagePayload; clientA = await specHelper.buildConnection(); clientB = await specHelper.buildConnection(); }); afterEach(() => { api.chatRoom.middleware = {}; api.chatRoom.globalMiddleware = []; clientA.destroy(); clientB.destroy(); api.chatRoom.generateMessagePayload = originalGenerateMessagePayload; }); test("generateMessagePayload can be overloaded", async () => { api.chatRoom.generateMessagePayload = (message) => { return { thing: "stuff", room: message.connection.room, from: message.connection.id, }; }; await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientA.verbs("say", ["defaultRoom", "hi there"]); await utils.sleep(100); const message = clientB.messages[clientB.messages.length - 1]; expect(message.thing).toEqual("stuff"); expect(message.message).toBeUndefined(); }); test("(join + leave) can add middleware to announce members", async () => { chatRoom.addMiddleware({ name: "add chat middleware", join: async (connection: Connection, room: string) => { await chatRoom.broadcast( {}, room, `I have entered the room: ${connection.id}`, ); }, }); chatRoom.addMiddleware({ name: "leave chat middleware", leave: async (connection: Connection, room: string) => { await chatRoom.broadcast( {}, room, `I have left the room: ${connection.id}`, ); }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomLeave", "defaultRoom"); await utils.sleep(100); expect(clientA.messages.pop().message).toEqual( "I have left the room: " + clientB.id, ); expect(clientA.messages.pop().message).toEqual( "I have entered the room: " + clientB.id, ); }); test("(say) can modify message payloads", async () => { chatRoom.addMiddleware({ name: "chat middleware", say: ( connection: Connection, room: string, messagePayload: { from: number; message: string }, ) => { if (messagePayload.from !== 0) { messagePayload.message = "something else"; } return messagePayload; }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("say", ["defaultRoom", "something", "awesome"]); await utils.sleep(100); const lastMessage = clientA.messages[clientA.messages.length - 1]; expect(lastMessage.message).toEqual("something else"); }); test("can add middleware in a particular order and will be passed modified messagePayloads", async () => { chatRoom.addMiddleware({ name: "chat middleware 1", priority: 1000, say: ( connection: Connection, room: string, messagePayload: { from: number; message: string }, ) => { messagePayload.message = "MIDDLEWARE 1"; return messagePayload; }, }); chatRoom.addMiddleware({ name: "chat middleware 2", priority: 2000, say: ( connection: Connection, room: string, messagePayload: { from: number; message: string }, ) => { messagePayload.message = messagePayload.message + " MIDDLEWARE 2"; return messagePayload; }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("say", ["defaultRoom", "something", "awesome"]); await utils.sleep(100); const lastMessage = clientA.messages[clientA.messages.length - 1]; expect(lastMessage.message).toEqual("MIDDLEWARE 1 MIDDLEWARE 2"); }); test("say middleware can block execution", async () => { chatRoom.addMiddleware({ name: "chat middleware", say: () => { throw new Error("messages blocked"); }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("say", ["defaultRoom", "something", "awesome"]); await utils.sleep(100); // welcome message is passed, no join/leave/or say messages expect(clientA.messages).toHaveLength(1); expect(clientA.messages[0].welcome).toMatch(/Welcome/); }); test("join middleware can block execution", async () => { chatRoom.addMiddleware({ name: "chat middleware", join: () => { throw new Error("joining rooms blocked"); }, }); try { await clientA.verbs("roomAdd", "defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual("Error: joining rooms blocked"); expect(clientA.rooms).toHaveLength(0); } }); test("leave middleware can block execution", async () => { chatRoom.addMiddleware({ name: "chat middleware", leave: () => { throw new Error("Hotel California"); }, }); const didJoin = await clientA.verbs("roomAdd", "defaultRoom"); expect(didJoin).toEqual(true); expect(clientA.rooms).toHaveLength(1); expect(clientA.rooms[0]).toEqual("defaultRoom"); try { await clientA.verbs("roomLeave", "defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual("Error: Hotel California"); expect(clientA.rooms).toHaveLength(1); } }); test("(say verb with async keyword) can modify message payloads", async () => { chatRoom.addMiddleware({ name: "chat middleware", say: async ( connection: Connection, room: string, messagePayload: any, ) => { if (messagePayload.from !== 0) { messagePayload.message = "something else"; } return messagePayload; }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("say", ["defaultRoom", "something", "awesome"]); await utils.sleep(100); const lastMessage = clientA.messages[clientA.messages.length - 1]; expect(lastMessage.message).toEqual("something else"); }); test("can add middleware in a particular order and will be passed modified messagePayloads with both being async functions", async () => { chatRoom.addMiddleware({ name: "chat middleware 1", priority: 1000, say: async ( connection: Connection, room: string, messagePayload: any, ) => { messagePayload.message = "MIDDLEWARE 1"; return messagePayload; }, }); chatRoom.addMiddleware({ name: "chat middleware 2", priority: 2000, say: async ( connection: Connection, room: string, messagePayload: any, ) => { messagePayload.message = messagePayload.message + " MIDDLEWARE 2"; return messagePayload; }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("say", ["defaultRoom", "something", "awesome"]); await utils.sleep(100); const lastMessage = clientA.messages[clientA.messages.length - 1]; expect(lastMessage.message).toEqual("MIDDLEWARE 1 MIDDLEWARE 2"); }); test("say async function middleware can block execution", async () => { chatRoom.addMiddleware({ name: "chat middleware", say: async () => { throw new Error("messages blocked"); }, }); await clientA.verbs("roomAdd", "defaultRoom"); await clientB.verbs("roomAdd", "defaultRoom"); await clientB.verbs("say", ["defaultRoom", "something", "awesome"]); await utils.sleep(100); // welcome message is passed, no join/leave/or say messages expect(clientA.messages).toHaveLength(1); expect(clientA.messages[0].welcome).toMatch(/Welcome/); }); test("join async function middleware can block execution", async () => { chatRoom.addMiddleware({ name: "chat middleware", join: async () => { throw new Error("joining rooms blocked"); }, }); try { await clientA.verbs("roomAdd", "defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual("Error: joining rooms blocked"); expect(clientA.rooms).toHaveLength(0); } }); test("leave async function middleware can block execution", async () => { chatRoom.addMiddleware({ name: "chat middleware", leave: async () => { throw new Error("Hotel California"); }, }); const didJoin = await clientA.verbs("roomAdd", "defaultRoom"); expect(didJoin).toEqual(true); expect(clientA.rooms).toHaveLength(1); expect(clientA.rooms[0]).toEqual("defaultRoom"); try { await clientA.verbs("roomLeave", "defaultRoom"); throw new Error("should not get here"); } catch (error) { expect(error.toString()).toEqual("Error: Hotel California"); expect(clientA.rooms).toHaveLength(1); } }); }); }); }); });