diff --git a/backend/jest.config.json b/backend/jest.config.json index 3f2fd5d188..f750c69e16 100644 --- a/backend/jest.config.json +++ b/backend/jest.config.json @@ -14,8 +14,8 @@ "coverageThreshold": { "global": { "statements": 96, - "branches": 87, - "functions": 94, + "branches": 88, + "functions": 98, "lines": 97 } }, diff --git a/backend/prisma/migrations/20240624231416_int_to_bigint_meeting_create_time/migration.sql b/backend/prisma/migrations/20240624231416_int_to_bigint_meeting_create_time/migration.sql new file mode 100644 index 0000000000..8b72a9bcfc --- /dev/null +++ b/backend/prisma/migrations/20240624231416_int_to_bigint_meeting_create_time/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE `Meeting` MODIFY `createTime` BIGINT NULL; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 3c32494187..4280892567 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -66,7 +66,7 @@ model Meeting { createdAt DateTime @default(now()) voiceBridge Int? dialNumber String? @db.VarChar(64) - createTime Int? + createTime BigInt? createDate DateTime? user User? } diff --git a/backend/src/api/BBB.ts b/backend/src/api/BBB.ts index 34cea1c296..8987bc9ccf 100644 --- a/backend/src/api/BBB.ts +++ b/backend/src/api/BBB.ts @@ -170,10 +170,10 @@ export const createMeeting = async ( interface JoinMeetinLinkOptions { fullName: string meetingID: string - // role: 'MODERATOR' | 'VIEWER' - password: string - // createTime: string - // userID: string + role?: 'MODERATOR' | 'VIEWER' + password?: string + createTime?: string + userID?: string } export const joinMeetingLink = (options: JoinMeetinLinkOptions): string => { @@ -185,17 +185,6 @@ export const joinMeetingLink = (options: JoinMeetinLinkOptions): string => { return CONFIG.BBB_URL + 'join?' + params + '&checksum=' + checksum } -const handleOpenRomms = async (): Promise => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const rooms = await getMeetings() - // console.log(rooms.map((m) => m.attendees?.attendee)) -} - -export const checkForOpenRooms = (): void => { - void handleOpenRomms() - setTimeout(checkForOpenRooms, 60 * 1000) -} - /* export const listHooks = async () => { try { diff --git a/backend/src/auth/authChecker.spec.ts b/backend/src/auth/authChecker.spec.ts index 5649c466ef..53a76f2a42 100644 --- a/backend/src/auth/authChecker.spec.ts +++ b/backend/src/auth/authChecker.spec.ts @@ -37,15 +37,13 @@ describe('authChecker', () => { it('returns access denied error', async () => { await expect( testServer.executeOperation({ - query: 'query { joinMyRoom }', + query: 'mutation { joinMyRoom }', }), ).resolves.toMatchObject({ body: { kind: 'single', singleResult: { - data: { - joinMyRoom: null, - }, + data: null, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment errors: expect.arrayContaining([ expect.objectContaining({ @@ -69,7 +67,7 @@ describe('authChecker', () => { it('creates user in database', async () => { await testServer.executeOperation( { - query: 'query { joinMyRoom }', + query: 'mutation { joinMyRoom }', }, { contextValue: { @@ -88,7 +86,8 @@ describe('authChecker', () => { createdAt: expect.any(Date), name: 'User', username: 'mockedUser', - meetingId: null, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + meetingId: expect.any(Number), }, ]) }) @@ -117,7 +116,7 @@ describe('authChecker', () => { it('has the same user in database', async () => { await testServer.executeOperation( { - query: 'query { joinMyRoom }', + query: 'mutation { joinMyRoom }', }, { contextValue: { diff --git a/backend/src/graphql/resolvers/RoomResolver.spec.ts b/backend/src/graphql/resolvers/RoomResolver.spec.ts index fd0ceb482c..dad52cac58 100644 --- a/backend/src/graphql/resolvers/RoomResolver.spec.ts +++ b/backend/src/graphql/resolvers/RoomResolver.spec.ts @@ -48,13 +48,13 @@ describe('RoomResolver', () => { it('throws access denied', async () => { await expect( testServer.executeOperation({ - query: 'query { joinMyRoom }', + query: 'mutation { joinMyRoom }', }), ).resolves.toMatchObject({ body: { kind: 'single', singleResult: { - data: { joinMyRoom: null }, + data: null, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment errors: expect.arrayContaining([ expect.objectContaining({ @@ -288,17 +288,43 @@ describe('RoomResolver', () => { }) describe('joinMyRoom', () => { - describe('createMeeting returns undefined', () => { - it('returns null', async () => { - createMeetingMock.mockResolvedValue(null) + beforeAll(async () => { + await prisma.meeting.deleteMany() + await prisma.user.deleteMany() + }) + + let meetingId: string | undefined + + describe('meeting does not exist', () => { + joinMeetingLinkMock.mockReturnValue('https://my-link') + createMeetingMock.mockResolvedValue({ + returncode: 'SUCCESS', + meetingID: 'xxx', + internalMeetingID: 'b60d121b438a380c343d5ec3c2037564b82ffef3-1715231322715', + parentMeetingID: 'bbb-none', + attendeePW: 'w3VUvMcp', + moderatorPW: 'MyPp9Zfq', + createTime: 1718189921310, + voiceBridge: 255, + dialNumber: '613-555-1234', + createDate: new Date(), + hasUserJoined: false, + duration: 0, + hasBeenForciblyEnded: false, + messageKey: '', + message: '', + }) + + it('returns link to room', async () => { await expect( testServer.executeOperation( { - query: 'query { joinMyRoom }', + query: 'mutation { joinMyRoom }', }, { contextValue: { token: 'token', + user: undefined, }, }, ), @@ -306,43 +332,75 @@ describe('RoomResolver', () => { body: { kind: 'single', singleResult: { - data: { joinMyRoom: null }, + data: { + joinMyRoom: 'https://my-link', + }, errors: undefined, }, }, }) }) - }) - describe('createMeeting returns meeting', () => { - it('returns link to the meeting', async () => { - joinMeetingLinkMock.mockReturnValue('https://my-link') - createMeetingMock.mockResolvedValue({ - returncode: 'SUCCESS', - meetingID: 'xxx', - internalMeetingID: 'b60d121b438a380c343d5ec3c2037564b82ffef3-1715231322715', - parentMeetingID: 'bbb-none', - attendeePW: 'w3VUvMcp', - moderatorPW: 'MyPp9Zfq', - createTime: 1715231322715, - voiceBridge: 255, - dialNumber: '613-555-1234', - createDate: new Date(), - hasUserJoined: false, - duration: 0, - hasBeenForciblyEnded: false, - messageKey: '', - message: '', + it('creates meeting in database', async () => { + const result = await prisma.user.findFirst({ + include: { + meeting: true, + }, }) + meetingId = result?.meeting?.meetingID + expect(result).toMatchObject({ + meeting: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + id: expect.any(Number), + name: 'mockedUser', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + meetingID: expect.any(String), + attendeePW: 'w3VUvMcp', + moderatorPW: 'MyPp9Zfq', + voiceBridge: 255, + dialNumber: '613-555-1234', + createTime: 1718189921310n, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + createDate: expect.any(Date), + }, + }) + }) + }) + describe('meeting exists in DB', () => { + beforeAll(() => { + jest.clearAllMocks() + }) + + joinMeetingLinkMock.mockReturnValue('https://my-link') + createMeetingMock.mockResolvedValue({ + returncode: 'SUCCESS', + meetingID: 'xxx', + internalMeetingID: 'b60d121b438a380c343d5ec3c2037564b82ffef3-1715231322715', + parentMeetingID: 'bbb-none', + attendeePW: 'w3VUvMcp', + moderatorPW: 'MyPp9Zfq', + createTime: 1718189921310, + voiceBridge: 255, + dialNumber: '613-555-1234', + createDate: new Date(), + hasUserJoined: false, + duration: 0, + hasBeenForciblyEnded: false, + messageKey: '', + message: '', + }) + + it('returns link to room', async () => { await expect( testServer.executeOperation( { - query: 'query { joinMyRoom }', + query: 'mutation { joinMyRoom }', }, { contextValue: { token: 'token', + user: undefined, }, }, ), @@ -350,12 +408,69 @@ describe('RoomResolver', () => { body: { kind: 'single', singleResult: { - data: { joinMyRoom: 'https://my-link' }, + data: { + joinMyRoom: 'https://my-link', + }, errors: undefined, }, }, }) }) + + it('updates meeting in database', async () => { + await expect( + prisma.user.findFirst({ + include: { + meeting: true, + }, + }), + ).resolves.toMatchObject({ + meeting: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + id: expect.any(Number), + name: 'mockedUser', + meetingID: meetingId, + attendeePW: 'w3VUvMcp', + moderatorPW: 'MyPp9Zfq', + voiceBridge: 255, + dialNumber: '613-555-1234', + createTime: 1718189921310n, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + createDate: expect.any(Date), + }, + }) + }) + }) + + describe('createMeeting returns undefined', () => { + it('throws meeting error', async () => { + createMeetingMock.mockResolvedValue(null) + await expect( + testServer.executeOperation( + { + query: 'mutation { joinMyRoom }', + }, + { + contextValue: { + token: 'token', + }, + }, + ), + ).resolves.toMatchObject({ + body: { + kind: 'single', + singleResult: { + data: null, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + errors: expect.arrayContaining([ + expect.objectContaining({ + message: 'Could not create meeting!', + }), + ]), + }, + }, + }) + }) }) }) diff --git a/backend/src/graphql/resolvers/RoomResolver.ts b/backend/src/graphql/resolvers/RoomResolver.ts index 4c7e9676a9..3363b00429 100644 --- a/backend/src/graphql/resolvers/RoomResolver.ts +++ b/backend/src/graphql/resolvers/RoomResolver.ts @@ -1,9 +1,11 @@ +import { Meeting } from '@prisma/client' import { Resolver, Mutation, Query, Authorized, Ctx, Arg, Int } from 'type-graphql' // eslint-disable-next-line import/named import { v4 as uuidv4 } from 'uuid' import { createMeeting, joinMeetingLink, getMeetings, MeetingInfo } from '#api/BBB' import { Room, OpenRoom } from '#models/RoomModel' +import logger from '#src/logger' import { prisma } from '#src/prisma' import { Context } from '#src/server/context' @@ -50,22 +52,80 @@ export class RoomResolver { } @Authorized() - @Query(() => String, { nullable: true }) - async joinMyRoom(@Ctx() context: Context): Promise { + @Mutation(() => String) + async joinMyRoom(@Ctx() context: Context): Promise { const { user } = context - if (!user) return null + if (!user) throw new Error('User not found!') + + let dbMeeting: Meeting | null = null + + try { + if (user.meetingId) { + dbMeeting = await prisma.meeting.findUnique({ + where: { + id: user.meetingId, + }, + }) + if (!dbMeeting) throw new Error('Meeting not found!') + } else { + let meetingID: string = uuidv4() + while ( + await prisma.meeting.count({ + where: { + meetingID, + }, + }) + ) { + meetingID = uuidv4() + } + + dbMeeting = await prisma.meeting.create({ + data: { + name: user.username, + meetingID, + }, + }) + await prisma.user.update({ + where: { id: user.id }, + data: { meetingId: dbMeeting.id }, + }) + } + } catch (err) { + logger.error('Could not create Meeting in DB!', err) + throw new Error('Could not create Meeting in DB!') + } + const meeting = await createMeeting({ - name: 'Dreammall Entwicklung', - meetingID: 'Dreammall-Entwicklung', + name: dbMeeting.name, + meetingID: dbMeeting.meetingID, }) - if (!meeting) return null + + if (!meeting) throw new Error('Could not create meeting!') + + try { + await prisma.meeting.update({ + where: { id: dbMeeting.id }, + data: { + attendeePW: meeting.attendeePW, + moderatorPW: meeting.moderatorPW, + voiceBridge: meeting.voiceBridge, + dialNumber: meeting.dialNumber, + createTime: meeting.createTime, + createDate: new Date(meeting.createDate).toISOString(), + }, + }) + } catch (err) { + logger.error('Could not update Meeting in DB!', err) + throw new Error('Could not update Meeting in DB!') + } + return joinMeetingLink({ fullName: user.name, - meetingID: 'Dreammall-Entwicklung', + meetingID: meeting.meetingID, password: meeting.moderatorPW, - // role: 'MODERATOR', - // createTime: meeting.createTime.toString(), - // userID: user.id.toString(), + role: 'MODERATOR', + createTime: meeting.createTime.toString(), + userID: user.id.toString(), }) } diff --git a/backend/src/graphql/resolvers/dal/handleOpenRooms.spec.ts b/backend/src/graphql/resolvers/dal/handleOpenRooms.spec.ts new file mode 100644 index 0000000000..0bb80153a0 --- /dev/null +++ b/backend/src/graphql/resolvers/dal/handleOpenRooms.spec.ts @@ -0,0 +1,191 @@ +import { getMeetings } from '#api/BBB' +import { prisma } from '#src/prisma' + +import { handleOpenRooms } from './handleOpenRooms' + +jest.mock('#api/BBB') + +const getMeetingsMock = getMeetings as jest.MockedFunction + +describe('handleOpenRooms', () => { + describe('two meetings in db', () => { + beforeAll(async () => { + await prisma.meeting.create({ + data: { + name: 'Meeting 1', + meetingID: 'Meeting-1', + createTime: 1234, + }, + }) + await prisma.meeting.create({ + data: { + name: 'Meeting 2', + meetingID: 'Meeting-2', + createTime: 1234, + }, + }) + }) + + describe('get meetings returns both meetings', () => { + beforeEach(async () => { + getMeetingsMock.mockResolvedValue([ + { + meetingName: 'Meeting 1', + meetingID: 'Meeting-1', + internalMeetingID: '258ea7269760758304b6b8494f17e9bf69dc1efe-1718189921310', + createTime: 1234, + createDate: new Date('Wed Jun 12 10:58:41 UTC 2024'), + voiceBridge: 96378, + dialNumber: '613-555-1234', + attendeePW: 'MqgUFwdD', + moderatorPW: 'mTtxYGo2', + running: true, + duration: 0, + hasUserJoined: true, + recording: false, + hasBeenForciblyEnded: false, + startTime: 1718189, + endTime: 0, + participantCount: 0, + listenerCount: 1, + voiceParticipantCount: 0, + videoCount: 0, + maxUsers: 0, + moderatorCount: 1, + attendees: '', + metadata: '', + isBreakout: false, + }, + { + meetingName: 'Meeting 2', + meetingID: 'Meeting-2', + internalMeetingID: '258ea7269760758304b6b8494f17e9bf69dc1efe-1718189921310', + createTime: 1234, + createDate: new Date('Wed Jun 12 10:58:41 UTC 2024'), + voiceBridge: 96378, + dialNumber: '613-555-1234', + attendeePW: 'MqgUFwdD', + moderatorPW: 'mTtxYGo2', + running: true, + duration: 0, + hasUserJoined: true, + recording: false, + hasBeenForciblyEnded: false, + startTime: 1718189, + endTime: 0, + participantCount: 0, + listenerCount: 1, + voiceParticipantCount: 0, + videoCount: 0, + maxUsers: 0, + moderatorCount: 1, + attendees: '', + metadata: '', + isBreakout: false, + }, + ]) + + await handleOpenRooms() + }) + + it('does not alter the DB', async () => { + const meetings = await prisma.meeting.findMany() + expect(meetings).toHaveLength(2) + expect(meetings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'Meeting 1', + meetingID: 'Meeting-1', + }), + expect.objectContaining({ + name: 'Meeting 2', + meetingID: 'Meeting-2', + }), + ]), + ) + }) + }) + + describe('get meetings returns one meeting', () => { + beforeEach(async () => { + getMeetingsMock.mockResolvedValue([ + { + meetingName: 'Meeting 1', + meetingID: 'Meeting-1', + internalMeetingID: '258ea7269760758304b6b8494f17e9bf69dc1efe-1718189921310', + createTime: 1234, + createDate: new Date('Wed Jun 12 10:58:41 UTC 2024'), + voiceBridge: 96378, + dialNumber: '613-555-1234', + attendeePW: 'MqgUFwdD', + moderatorPW: 'mTtxYGo2', + running: true, + duration: 0, + hasUserJoined: true, + recording: false, + hasBeenForciblyEnded: false, + startTime: 1718189, + endTime: 0, + participantCount: 0, + listenerCount: 1, + voiceParticipantCount: 0, + videoCount: 0, + maxUsers: 0, + moderatorCount: 1, + attendees: '', + metadata: '', + isBreakout: false, + }, + ]) + + await handleOpenRooms() + }) + + it('resets the missing meeting in DB', async () => { + const meetings = await prisma.meeting.findMany() + expect(meetings).toHaveLength(2) + expect(meetings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'Meeting 1', + meetingID: 'Meeting-1', + createTime: 1234n, + }), + expect.objectContaining({ + name: 'Meeting 2', + meetingID: 'Meeting-2', + createTime: null, + }), + ]), + ) + }) + }) + + describe('get meetings returns empty array', () => { + beforeEach(async () => { + getMeetingsMock.mockResolvedValue([]) + + await handleOpenRooms() + }) + + it('resets the meetings in DB', async () => { + const meetings = await prisma.meeting.findMany() + expect(meetings).toHaveLength(2) + expect(meetings).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: 'Meeting 1', + meetingID: 'Meeting-1', + createTime: null, + }), + expect.objectContaining({ + name: 'Meeting 2', + meetingID: 'Meeting-2', + createTime: null, + }), + ]), + ) + }) + }) + }) +}) diff --git a/backend/src/graphql/resolvers/dal/handleOpenRooms.ts b/backend/src/graphql/resolvers/dal/handleOpenRooms.ts new file mode 100644 index 0000000000..5a3342d257 --- /dev/null +++ b/backend/src/graphql/resolvers/dal/handleOpenRooms.ts @@ -0,0 +1,24 @@ +import { getMeetings, MeetingInfo } from '#api/BBB' +import { prisma } from '#src/prisma' + +export const handleOpenRooms = async (): Promise => { + const rooms = await getMeetings() + await prisma.meeting.updateMany({ + where: { + createTime: { not: null }, + meetingID: { + not: { + in: rooms.map((m: MeetingInfo) => m.meetingID), + }, + }, + }, + data: { + attendeePW: null, + moderatorPW: null, + voiceBridge: null, + dialNumber: null, + createTime: null, + createDate: null, + }, + }) +} diff --git a/backend/src/index.ts b/backend/src/index.ts index 14101b73f2..58fe7f32b7 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,15 +1,20 @@ // eslint-disable-next-line import/no-unassigned-import import 'reflect-metadata' -// import { checkForOpenRooms } from '#api/BBB' +import { handleOpenRooms } from '#graphql/resolvers/dal/handleOpenRooms' import logger from './logger' import { prisma } from './prisma' import { listen } from './server/server' +const checkForOpenRooms = (): void => { + void handleOpenRooms() + setTimeout(checkForOpenRooms, 60 * 1000) +} + export const main = async (): Promise => { const url = await listen(4000) logger.info(`🚀 Server is ready at ${url}`) - // checkForOpenRooms() + checkForOpenRooms() } void main() diff --git a/backend/test/helpers.ts b/backend/test/helpers.ts index 3e484318b1..abfaf87d3b 100644 --- a/backend/test/helpers.ts +++ b/backend/test/helpers.ts @@ -6,6 +6,7 @@ export const deleteAll = async () => { await prisma.$executeRaw`DELETE FROM NewsletterSubscription` await prisma.event.deleteMany() await prisma.user.deleteMany() + await prisma.meeting.deleteMany() } export const disconnect = async () => { diff --git a/frontend/.storybook/ApolloWrapper.vue b/frontend/.storybook/ApolloWrapper.vue index 30871c1058..33872d5e2a 100644 --- a/frontend/.storybook/ApolloWrapper.vue +++ b/frontend/.storybook/ApolloWrapper.vue @@ -6,14 +6,14 @@ import { defineComponent, provide } from 'vue' import { DefaultApolloClient } from '@vue/apollo-composable' -import { joinMyRoomQuery } from '#queries/joinMyRoomQuery' +import { joinMyRoomMutation } from '#mutations/joinMyRoomMutation' import { MockedProvider } from '@apollo/client/testing' const apolloClient = new MockedProvider({ mocks: [ { request: { - query: joinMyRoomQuery, + query: joinMyRoomMutation, }, result: { data: { diff --git a/frontend/package.json b/frontend/package.json index aa94757c3e..11c5cee209 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -158,6 +158,7 @@ "#assets/*": "./src/assets/*", "#layouts/*": "./src/layouts/*", "#queries/*": "./src/graphql/queries/*", + "#mutations/*": "./src/graphql/mutations/*", "#stores/*": "./src/stores/*", "#src/*": "./src/*", "#plugins/*": "./renderer/plugins/*", diff --git a/frontend/src/components/buttons/CreateButton.test.ts b/frontend/src/components/buttons/CreateButton.test.ts index 35ed5abcbf..26a594448a 100644 --- a/frontend/src/components/buttons/CreateButton.test.ts +++ b/frontend/src/components/buttons/CreateButton.test.ts @@ -1,12 +1,24 @@ +import { ApolloError } from '@apollo/client/errors' import { mount } from '@vue/test-utils' import { navigate } from 'vike/client/router' import { describe, it, expect, beforeEach, vi } from 'vitest' +import { joinMyRoomMutation } from '#mutations/joinMyRoomMutation' +import { useActiveRoomStore } from '#stores/activeRoomStore' +import { mockClient } from '#tests/mock.apolloClient' +import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' + import CreateButton from './CreateButton.vue' vi.mock('vike/client/router') vi.mocked(navigate).mockResolvedValue() +const joinMyRoomMutationMock = vi.fn() + +mockClient.setRequestHandler(joinMyRoomMutation, joinMyRoomMutationMock) + +const activeRoomStore = useActiveRoomStore() + describe('CreateButton', () => { const Wrapper = () => { return mount(CreateButton, { @@ -47,14 +59,82 @@ describe('CreateButton', () => { beforeEach(() => { wrapper = Wrapper() }) + describe('enter room', () => { - beforeEach(async () => { - await wrapper.find('#create-button').trigger('click') - await wrapper.find('button.new-table-button').trigger('click') + describe('apollo with success', () => { + beforeEach(async () => { + vi.clearAllMocks() + joinMyRoomMutationMock.mockResolvedValue({ + data: { + joinMyRoom: 'http://link-to-my.room', + }, + }) + await wrapper.find('#create-button').trigger('click') + await wrapper.find('button.new-table-button').trigger('click') + }) + + it('calls the api', () => { + expect(joinMyRoomMutationMock).toBeCalled() + }) + + it('updates the store', () => { + expect(activeRoomStore.activeRoom).toBe('http://link-to-my.room') + }) + + it.skip('navigates to room page', () => { + expect(navigate).toBeCalledWith('/room/') + }) }) - it.skip('opens url in new tab', () => { - expect(navigate).toBeCalledWith('/room/') + describe('apollo with no data', () => { + beforeEach(async () => { + activeRoomStore.setActiveRoom(null) + vi.clearAllMocks() + joinMyRoomMutationMock.mockResolvedValue({ + data: null, + }) + await wrapper.find('#create-button').trigger('click') + await wrapper.find('button.new-table-button').trigger('click') + }) + + it('calls the api', () => { + expect(joinMyRoomMutationMock).toBeCalled() + }) + + it('does not update the store', () => { + expect(activeRoomStore.activeRoom).toBe(null) + }) + + it.skip('toasts no room found error', () => { + expect(errorHandlerSpy).toBeCalledWith('No room found') + }) + }) + + describe('apollo with error', () => { + beforeEach(async () => { + activeRoomStore.setActiveRoom(null) + vi.clearAllMocks() + joinMyRoomMutationMock.mockRejectedValue({ + message: 'OUCH', + }) + await wrapper.find('#create-button').trigger('click') + await wrapper.find('button.new-table-button').trigger('click') + }) + + it('calls the api', () => { + expect(joinMyRoomMutationMock).toBeCalled() + }) + + it('does not update the store', () => { + expect(activeRoomStore.activeRoom).toBe(null) + }) + + it('toasts no room found error', () => { + expect(errorHandlerSpy).toBeCalledWith( + 'Error opening room', + new ApolloError({ errorMessage: 'OUCH' }), + ) + }) }) }) }) diff --git a/frontend/src/components/buttons/CreateButton.vue b/frontend/src/components/buttons/CreateButton.vue index 0044e6664e..bec727abef 100644 --- a/frontend/src/components/buttons/CreateButton.vue +++ b/frontend/src/components/buttons/CreateButton.vue @@ -341,10 +341,14 @@ diff --git a/frontend/src/components/embedded-room/useMyRoom.spec.ts b/frontend/src/components/embedded-room/useMyRoom.spec.ts deleted file mode 100644 index b0ed5e144d..0000000000 --- a/frontend/src/components/embedded-room/useMyRoom.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { flushPromises, mount } from '@vue/test-utils' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { defineComponent } from 'vue' - -import { joinMyRoomQuery } from '#queries/joinMyRoomQuery' -import { mockClient } from '#tests/mock.apolloClient' -import { errorHandlerSpy } from '#tests/plugin.globalErrorHandler' - -import useMyRoom from './useMyRoom' - -const joinMyRoomQueryMock = vi.fn() - -const testUrl = 'http://some.url' - -mockClient.setRequestHandler(joinMyRoomQuery, joinMyRoomQueryMock) - -describe('useMyRoom', () => { - const TestComponent = defineComponent({ - setup() { - return { - ...useMyRoom(), - } - }, - }) - const Wrapper = () => { - return mount(TestComponent) - } - - let wrapper: ReturnType - - describe('without apollo error', () => { - beforeEach(() => { - joinMyRoomQueryMock.mockResolvedValue({ data: { joinMyRoom: testUrl } }) - wrapper = Wrapper() - }) - - it('calls the API', () => { - expect(joinMyRoomQueryMock).toBeCalled() - }) - - it('returns correct url', async () => { - await flushPromises() - expect(wrapper.vm.roomUrl).toBe(testUrl) - }) - }) - - describe('with apollo error', () => { - beforeEach(() => { - wrapper.unmount() - vi.clearAllMocks() - joinMyRoomQueryMock.mockRejectedValue({ message: 'Aua!', data: undefined }) - wrapper = Wrapper() - }) - - it('logs error message', async () => { - await flushPromises() - expect(errorHandlerSpy).toBeCalledWith('Aua!') - }) - }) -}) diff --git a/frontend/src/components/embedded-room/useMyRoom.ts b/frontend/src/components/embedded-room/useMyRoom.ts deleted file mode 100644 index 211562530f..0000000000 --- a/frontend/src/components/embedded-room/useMyRoom.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useQuery } from '@vue/apollo-composable' -import { watch, ref } from 'vue' - -import GlobalErrorHandler from '#plugins/globalErrorHandler' -import { JoinMyRoomQueryResult, joinMyRoomQuery } from '#queries/joinMyRoomQuery' - -export default function useMyRoom() { - const { result: joinMyRoomQueryResult, error: joinMyRoomQueryError } = - useQuery(joinMyRoomQuery, null, { - prefetch: false, - fetchPolicy: 'no-cache', - }) - - const roomUrl = ref(null) - - watch(joinMyRoomQueryResult, () => { - if (joinMyRoomQueryResult.value) { - roomUrl.value = joinMyRoomQueryResult.value.joinMyRoom - } - }) - - watch(joinMyRoomQueryError, () => { - if (joinMyRoomQueryError.value) { - GlobalErrorHandler.error(joinMyRoomQueryError.value.message) - } - }) - - return { roomUrl } -} diff --git a/frontend/src/graphql/mutations/joinMyRoomMutation.ts b/frontend/src/graphql/mutations/joinMyRoomMutation.ts new file mode 100644 index 0000000000..9449ab2767 --- /dev/null +++ b/frontend/src/graphql/mutations/joinMyRoomMutation.ts @@ -0,0 +1,11 @@ +import { gql } from 'graphql-tag' + +export const joinMyRoomMutation = gql` + mutation { + joinMyRoom + } +` + +export type JoinMyRoomMutationResult = { + joinMyRoom: string +} diff --git a/frontend/src/graphql/queries/joinMyRoomQuery.ts b/frontend/src/graphql/queries/joinMyRoomQuery.ts deleted file mode 100644 index f662e7fc51..0000000000 --- a/frontend/src/graphql/queries/joinMyRoomQuery.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { gql } from 'graphql-tag' - -export const joinMyRoomQuery = gql` - query { - joinMyRoom - } -` - -export type JoinMyRoomQueryResult = { - joinMyRoom: string -} diff --git a/frontend/src/pages/room/+Page.vue b/frontend/src/pages/room/+Page.vue index 454680a10f..0fd789b534 100644 --- a/frontend/src/pages/room/+Page.vue +++ b/frontend/src/pages/room/+Page.vue @@ -7,11 +7,15 @@