Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import configuration from './config/configuration';
import { SequelizeModule, SequelizeModuleOptions } from '@nestjs/sequelize';
import { format } from 'sql-formatter';
import { UserModule } from './modules/user/user.module';
import { WebhookModule } from './modules/webhook/webhook.module';
import { SharedModule } from './shared/shared.module';
import { LoggerModule } from './common/logger/logger.module';

const defaultDbConfig = (
Expand Down Expand Up @@ -77,8 +76,7 @@ const defaultDbConfig = (
}),
}),
CallModule,
UserModule,
WebhookModule,
SharedModule,
],
controllers: [],
})
Expand Down
1 change: 0 additions & 1 deletion src/externals/http/http.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { HttpsAgent } from 'agentkeepalive';
Expand Down
1 change: 0 additions & 1 deletion src/externals/payments.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Inject, Injectable } from '@nestjs/common';
Expand Down
85 changes: 44 additions & 41 deletions src/modules/call/call.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {
NotFoundException,
} from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { UsersInRoomDto } from '../room/dto/users-in-room.dto';
import { RoomUserUseCase } from '../room/room-user.usecase';
import { UsersInRoomDto } from './dto/users-in-room.dto';
import { RoomService } from './services/room.service';
import { CallController } from './call.controller';
import { CallUseCase } from './call.usecase';
import { JoinCallDto, JoinCallResponseDto } from './dto/join-call.dto';
Expand All @@ -17,7 +17,7 @@ import { createMockUserToken, mockUserPayload } from './fixtures';
describe('Testing Call Endpoints', () => {
let callController: CallController;
let callUseCase: DeepMocked<CallUseCase>;
let roomUserUseCase: DeepMocked<RoomUserUseCase>;
let roomService: DeepMocked<RoomService>;

const mockRoomId = 'test-room-id';
const mockJoinCallDto: JoinCallDto = {
Expand Down Expand Up @@ -51,21 +51,13 @@ describe('Testing Call Endpoints', () => {
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CallController],
providers: [
{
provide: CallUseCase,
useValue: createMock<CallUseCase>(),
},
{
provide: RoomUserUseCase,
useValue: createMock<RoomUserUseCase>(),
},
],
}).compile();
})
.useMocker(createMock)
.compile();

callController = module.get<CallController>(CallController);
callUseCase = module.get<DeepMocked<CallUseCase>>(CallUseCase);
roomUserUseCase = module.get<DeepMocked<RoomUserUseCase>>(RoomUserUseCase);
roomService = module.get<DeepMocked<RoomService>>(RoomService);
});

describe('Creating a call', () => {
Expand All @@ -88,15 +80,10 @@ describe('Testing Call Endpoints', () => {
appId: 'jitsi-app-id',
};

callUseCase.validateUserHasNoActiveRoom.mockResolvedValueOnce(undefined);
callUseCase.createCallAndRoom.mockResolvedValueOnce(mockResponse);

const result = await callController.createCall(mockUserToken.payload);

expect(callUseCase.validateUserHasNoActiveRoom).toHaveBeenCalledWith(
mockUserToken.payload.uuid,
mockUserToken.payload.email,
);
expect(callUseCase.createCallAndRoom).toHaveBeenCalledWith(
mockUserToken.payload,
);
Expand All @@ -106,7 +93,7 @@ describe('Testing Call Endpoints', () => {
it('When the room already exists, then an error indicating so is thrown', async () => {
const mockUserToken = createMockUserToken();

callUseCase.validateUserHasNoActiveRoom.mockRejectedValueOnce(
callUseCase.createCallAndRoom.mockRejectedValueOnce(
new ConflictException('User already has an active room as host'),
);

Expand All @@ -118,7 +105,7 @@ describe('Testing Call Endpoints', () => {
it('When an unexpected error occurs, then an error indicating so is thrown', async () => {
const mockUserToken = createMockUserToken();

callUseCase.validateUserHasNoActiveRoom.mockRejectedValueOnce(
callUseCase.createCallAndRoom.mockRejectedValueOnce(
new Error('Unexpected error'),
);

Expand Down Expand Up @@ -198,7 +185,11 @@ describe('Testing Call Endpoints', () => {
);

await expect(
callController.joinCall(mockRoomId, mockUserToken.payload),
callController.joinCall(
mockRoomId,
mockUserToken.payload,
mockJoinCallDto,
),
).rejects.toThrow(NotFoundException);
});

Expand All @@ -210,7 +201,11 @@ describe('Testing Call Endpoints', () => {
);

await expect(
callController.joinCall(mockRoomId, mockUserToken.payload),
callController.joinCall(
mockRoomId,
mockUserToken.payload,
mockJoinCallDto,
),
).rejects.toThrow(ConflictException);
});

Expand All @@ -222,7 +217,11 @@ describe('Testing Call Endpoints', () => {
);

await expect(
callController.joinCall(mockRoomId, mockUserToken.payload),
callController.joinCall(
mockRoomId,
mockUserToken.payload,
mockJoinCallDto,
),
).rejects.toThrow(BadRequestException);
});

Expand All @@ -232,7 +231,11 @@ describe('Testing Call Endpoints', () => {
callUseCase.joinCall.mockRejectedValueOnce(new Error('Unexpected error'));

await expect(
callController.joinCall(mockRoomId, mockUserToken.payload),
callController.joinCall(
mockRoomId,
mockUserToken.payload,
mockJoinCallDto,
),
).rejects.toThrow(Error);
});

Expand Down Expand Up @@ -265,51 +268,51 @@ describe('Testing Call Endpoints', () => {

describe('Getting users in a call', () => {
it('should get users in a call for authenticated user', async () => {
roomUserUseCase.getUsersInRoom.mockResolvedValue(mockUsersInRoom);
roomService.getUsersInRoom.mockResolvedValue(mockUsersInRoom);

const result = await callController.getUsersInCall(mockRoomId);

expect(result).toEqual(mockUsersInRoom);
expect(roomUserUseCase.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
});

it('should get users in a call for anonymous user', async () => {
roomUserUseCase.getUsersInRoom.mockResolvedValue(mockUsersInRoom);
roomService.getUsersInRoom.mockResolvedValue(mockUsersInRoom);

const result = await callController.getUsersInCall(mockRoomId);

expect(result).toEqual(mockUsersInRoom);
expect(roomUserUseCase.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
});

it('When the room is not found, it should propagate NotFoundException', async () => {
roomUserUseCase.getUsersInRoom.mockRejectedValueOnce(
roomService.getUsersInRoom.mockRejectedValue(
new NotFoundException('Specified room not found'),
);

await expect(callController.getUsersInCall(mockRoomId)).rejects.toThrow(
NotFoundException,
);
expect(roomUserUseCase.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
});

it('When an unexpected error occurs, it should propagate the error', async () => {
roomUserUseCase.getUsersInRoom.mockRejectedValueOnce(
roomService.getUsersInRoom.mockRejectedValue(
new Error('Unexpected error'),
);

await expect(callController.getUsersInCall(mockRoomId)).rejects.toThrow(
Error,
);
expect(roomUserUseCase.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
});

it('When no users are in the room, it should return an empty array', async () => {
roomUserUseCase.getUsersInRoom.mockResolvedValueOnce([]);
roomService.getUsersInRoom.mockResolvedValue([]);

const result = await callController.getUsersInCall(mockRoomId);

expect(roomUserUseCase.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
expect(roomService.getUsersInRoom).toHaveBeenCalledWith(mockRoomId);
expect(result).toEqual([]);
expect(result.length).toBe(0);
});
Expand All @@ -336,7 +339,7 @@ describe('Testing Call Endpoints', () => {
const leaveCallDto = new LeaveCallDto();
leaveCallDto.userId = anonymousUserId;

callUseCase.leaveCall.mockResolvedValueOnce();
callUseCase.leaveCall.mockResolvedValue();

await callController.leaveCall(mockRoomId, null, leaveCallDto);

Expand All @@ -350,7 +353,7 @@ describe('Testing Call Endpoints', () => {
const leaveCallDto = new LeaveCallDto();
leaveCallDto.userId = 'anonymous-user-id';

callUseCase.leaveCall.mockResolvedValueOnce();
callUseCase.leaveCall.mockResolvedValue();

const userToken = createMockUserToken();
await callController.leaveCall(
Expand All @@ -367,7 +370,7 @@ describe('Testing Call Endpoints', () => {

it('should pass undefined when neither authenticated user nor DTO with userId are provided', async () => {
const emptyDto = new LeaveCallDto();
callUseCase.leaveCall.mockResolvedValueOnce();
callUseCase.leaveCall.mockResolvedValue();

await callController.leaveCall(mockRoomId, null, emptyDto);

Expand All @@ -377,7 +380,7 @@ describe('Testing Call Endpoints', () => {
it('should propagate NotFoundException when room is not found', async () => {
const userToken = createMockUserToken();

callUseCase.leaveCall.mockRejectedValueOnce(
callUseCase.leaveCall.mockRejectedValue(
new NotFoundException('Specified room not found'),
);

Expand All @@ -387,7 +390,7 @@ describe('Testing Call Endpoints', () => {
});

it('should propagate BadRequestException when no userId is provided', async () => {
callUseCase.leaveCall.mockRejectedValueOnce(
callUseCase.leaveCall.mockRejectedValue(
new BadRequestException('User ID is required'),
);

Expand Down
24 changes: 9 additions & 15 deletions src/modules/call/call.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {
BadRequestException,
Body,
ConflictException,
Controller,
Get,
HttpCode,
HttpException,
InternalServerErrorException,
Logger,
Param,
Expand All @@ -27,8 +27,8 @@ import { JwtAuthGuard } from '../auth/auth.guard';
import { OptionalAuth } from '../auth/decorators/optional-auth.decorator';
import { User } from '../auth/decorators/user.decorator';
import { UserTokenData } from '../auth/dto/user.dto';
import { UsersInRoomDto } from '../room/dto/users-in-room.dto';
import { RoomUserUseCase } from '../room/room-user.usecase';
import { UsersInRoomDto } from './dto/users-in-room.dto';
import { RoomService } from './services/room.service';
import { CallUseCase } from './call.usecase';
import { CreateCallResponseDto } from './dto/create-call.dto';
import { JoinCallDto, JoinCallResponseDto } from './dto/join-call.dto';
Expand All @@ -41,7 +41,7 @@ export class CallController {

constructor(
private readonly callUseCase: CallUseCase,
private readonly roomUserUseCase: RoomUserUseCase,
private readonly roomService: RoomService,
) {}

@Post('/')
Expand Down Expand Up @@ -70,32 +70,26 @@ export class CallController {
}

try {
await this.callUseCase.validateUserHasNoActiveRoom(uuid, email);
const call = await this.callUseCase.createCallAndRoom(user);
return call;
} catch (error) {
const err = error as Error;
this.logger.error(
`Failed to create call: ${err.message}`,
{
userId: uuid,
email: email,
error: err.name,
err,
},
err.stack,
'Failed to create a call and room',
);

if (
error instanceof BadRequestException ||
error instanceof ConflictException ||
error instanceof InternalServerErrorException
) {
if (error instanceof HttpException) {
throw error;
}

throw new InternalServerErrorException(
'An unexpected error occurred while creating the call',
{ cause: err.stack ?? err.message },
{ cause: err.message },
);
}
}
Expand Down Expand Up @@ -151,7 +145,7 @@ export class CallController {
@ApiNotFoundResponse({ description: 'Call/Room not found' })
@ApiInternalServerErrorResponse({ description: 'Internal server error' })
getUsersInCall(@Param('id') roomId: string): Promise<UsersInRoomDto[]> {
return this.roomUserUseCase.getUsersInRoom(roomId);
return this.roomService.getUsersInRoom(roomId);
}

@Post('/:id/users/leave')
Expand Down
26 changes: 17 additions & 9 deletions src/modules/call/call.module.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
import { Module } from '@nestjs/common';
import { CallService } from './call.service';
import { CallService } from './services/call.service';
import { CallController } from './call.controller';
import { PaymentService } from '../../externals/payments.service';
import { HttpClientModule } from '../../externals/http/http.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthModule } from '../auth/auth.module';
import { RoomUseCase } from '../room/room.usecase';
import { SequelizeRoomRepository } from '../room/room.repository';
import { RoomService } from './services/room.service';
import { SequelizeRoomRepository } from './infrastructure/room.repository';
import { SequelizeModule } from '@nestjs/sequelize';
import { RoomModel } from '../room/models/room.model';
import { RoomModule } from '../room/room.module';
import { RoomModel } from './models/room.model';
import { RoomUserModel } from './models/room-user.model';
import { SequelizeRoomUserRepository } from './infrastructure/room-user.repository';
import { CallUseCase } from './call.usecase';
import { AvatarService } from '../../externals/avatar/avatar.service';
import { SharedModule } from '../../shared/shared.module';
import { JitsiWebhookService } from './webhooks/jitsi/jitsi-webhook.service';
import { JitsiWebhookController } from './webhooks/jitsi/jitsi-webhook.controller';

@Module({
controllers: [CallController],
controllers: [CallController, JitsiWebhookController],
providers: [
CallService,
PaymentService,
ConfigService,
RoomUseCase,
RoomService,
SequelizeRoomRepository,
SequelizeRoomUserRepository,
CallUseCase,
AvatarService,
JitsiWebhookService,
],
imports: [
HttpClientModule,
ConfigModule,
AuthModule,
RoomModule,
SequelizeModule.forFeature([RoomModel]),
SharedModule,
SequelizeModule.forFeature([RoomModel, RoomUserModel]),
],
})
export class CallModule {}
Loading
Loading