diff --git a/src/backend/src/modules/comments/comments.service.ts b/src/backend/src/modules/comments/comments.service.ts index b701943f6..d26085b57 100644 --- a/src/backend/src/modules/comments/comments.service.ts +++ b/src/backend/src/modules/comments/comments.service.ts @@ -33,6 +33,7 @@ export class CommentsService { constructor( @InjectRepository(CommentEntity) private commentRepository: Repository, + @InjectRepository(ReplyEntity) private replyRepository: Repository, private readonly piaService: PiaIntakeService, ) {} diff --git a/src/backend/test/unit/comments/comments.controller.spec.ts b/src/backend/test/unit/comments/comments.controller.spec.ts index f007f9537..82fa129fa 100644 --- a/src/backend/test/unit/comments/comments.controller.spec.ts +++ b/src/backend/test/unit/comments/comments.controller.spec.ts @@ -14,11 +14,15 @@ import { commentROMock, commentsCountROMock, createCommentDtoMock, + createReplyDtoMock, findCommentsROMock, + replyROMock, } from 'test/util/mocks/data/comments.mock'; import { FindCommentsDto } from 'src/modules/comments/dto/find-comments.dto'; import { AllowedCommentPaths } from 'src/modules/comments/enums/allowed-comment-paths.enum'; import { FindCommentsCountDto } from 'src/modules/comments/dto/find-comments-count.dto'; +import { CreateReplyDto } from 'src/modules/comments/dto/create-reply.dto'; +import { ReplyRO } from 'src/modules/comments/ro/get-comment.ro'; /** * @Description @@ -102,6 +106,42 @@ describe('CommentsController', () => { }); }); + /** + * @method createReply + * + * @description + * This test suite validates that the method passes the correct values to the service, + * mock the service result and return correct result to the user + */ + describe('`createReply` method', () => { + it('should be defined', () => { + expect(controller.createReply).toBeDefined(); + }); + + it('succeeds with correct data : Happy flow', async () => { + const createReplyDto: CreateReplyDto = { ...createReplyDtoMock }; + const mockReq: any = { + user: { ...keycloakUserMock }, + userRoles: [], + }; + + service.createReply = jest.fn(async () => { + delay(10); + return replyROMock; + }); + + const result = await controller.createReply(createReplyDto, mockReq); + + expect(service.createReply).toHaveBeenCalledWith( + createReplyDto, + mockReq.user, + mockReq.userRoles, + ); + + expect(result).toStrictEqual(replyROMock); + }); + }); + /** * @Description * This test suite validates that the method passes the correct values to the service, @@ -245,6 +285,48 @@ describe('CommentsController', () => { }); }); + /** + * @method removeReply + * + * @description + * This test suite validates that the method passes the correct values to the service, + * mock the service result and return correct result to the user + */ + describe('`removeReply` method', () => { + it('should be defined', () => { + expect(controller.removeReply).toBeDefined(); + }); + + it('succeeds with correct data', async () => { + const replyId = '101'; // Example ID + const mockReq: any = { + user: { ...keycloakUserMock }, + userRoles: [], + }; + + const expectedResponse: ReplyRO = { + ...replyROMock, + isActive: false, + text: null, + }; + + service.removeReply = jest.fn(async () => { + delay(10); + return expectedResponse; + }); + + const result = await controller.removeReply(replyId, mockReq); + + expect(service.removeReply).toHaveBeenCalledWith( + +replyId, + mockReq.user, + mockReq.userRoles, + ); + + expect(result).toStrictEqual(expectedResponse); + }); + }); + /** * @method resolve * diff --git a/src/backend/test/unit/comments/comments.service.spec.ts b/src/backend/test/unit/comments/comments.service.spec.ts index 5a714e770..b6fe211e6 100644 --- a/src/backend/test/unit/comments/comments.service.spec.ts +++ b/src/backend/test/unit/comments/comments.service.spec.ts @@ -32,6 +32,7 @@ import { CreateCommentDto } from 'src/modules/comments/dto/create-comment.dto'; import { AllowedCommentPaths } from 'src/modules/comments/enums/allowed-comment-paths.enum'; import { piaIntakeServiceMock } from 'test/util/mocks/services/pia-intake.service.mock'; import * as checkUpdatePermissions from 'src/modules/pia-intake/helper/check-update-permissions'; +import { ReplyEntity } from 'src/modules/comments/entities/reply.entity'; /** * @Description @@ -40,7 +41,7 @@ import * as checkUpdatePermissions from 'src/modules/pia-intake/helper/check-upd describe('CommentsService', () => { let commentsService: CommentsService; let piaService: PiaIntakeService; - let commentRepository; + let commentRepository, replyRepository; const checkUpdatePermissionsSpy = jest .spyOn(checkUpdatePermissions, 'checkUpdatePermissions') @@ -60,6 +61,10 @@ describe('CommentsService', () => { provide: getRepositoryToken(CommentEntity), useValue: { ...repositoryMock }, }, + { + provide: getRepositoryToken(ReplyEntity), + useValue: { ...repositoryMock }, + }, { provide: getRepositoryToken(PiaIntakeEntity), useValue: { ...repositoryMock }, @@ -70,6 +75,7 @@ describe('CommentsService', () => { commentsService = module.get(CommentsService); piaService = module.get(PiaIntakeService); commentRepository = module.get(getRepositoryToken(CommentEntity)); + replyRepository = module.get(getRepositoryToken(ReplyEntity)); checkUpdatePermissionsSpy.mockClear(); }); @@ -195,6 +201,71 @@ describe('CommentsService', () => { }); }); + /** + * @Description + * These set of tests validates that the method passes the correct values to the repository, + * mocking the database save operation. + * + * @method createReply + */ + describe('`createReply` method', () => { + it('should be defined', () => { + expect(commentsService.createReply).toBeDefined(); + }); + + it('succeeds creating a reply to a comment', async () => { + const createReplyDto = { + commentId: commentEntityMock.id, + text: 'This is a test reply', + }; + const user = { ...keycloakUserMock }; + const userRoles = []; + + commentsService.findOneBy = jest + .fn() + .mockResolvedValue(commentEntityMock); + + piaService.validatePiaAccess = jest.fn(async () => ({ + ...piaIntakeEntityMock, + })); + + checkUpdatePermissionsSpy.mockReturnValue(true); // Allow adding a reply + + const mockReply = { + id: 101, + commentId: commentEntityMock.id, + text: 'This is a test reply', + createdByDisplayName: user.display_name, + createdAt: new Date(), + updatedAt: new Date(), + }; + + replyRepository.save = jest.fn().mockResolvedValue(mockReply); + + const result = await commentsService.createReply( + createReplyDto, + user, + userRoles, + ); + + expect(commentsService.findOneBy).toHaveBeenCalledWith({ + id: createReplyDto.commentId, + }); + expect(piaService.validatePiaAccess).toHaveBeenCalledWith( + commentEntityMock.piaId, + user, + userRoles, + ); + expect(checkUpdatePermissionsSpy).toHaveBeenCalledWith({ + status: piaIntakeEntityMock.status, + entityType: 'comment', + entityAction: 'add', + }); + expect(replyRepository.save).toHaveBeenCalledWith(expect.any(Object)); + expect(result).toEqual(mockReply); + }); + }); + /** * @Description * These set of tests validates that the method passes the correct values to the repository, @@ -491,6 +562,48 @@ describe('CommentsService', () => { }); }); + /** + * @Description + * These set of tests validates that the method passes the correct values to the repository, + * removes the comment, mocking the database delete operation. + * + * @method removeReply + */ + describe('`removeReply` method', () => { + it('should be defined', () => { + expect(commentsService.removeReply).toBeDefined(); + }); + + it('succeeds in removing a reply', async () => { + const id = 101; // example reply ID + const user = { ...keycloakUserMock }; + const userRoles = []; + + const mockReply = { + id, + commentId: commentEntityMock.id, + createdByGuid: user.idir_user_guid, + }; + + commentsService.findOneReplyBy = jest.fn().mockResolvedValue(mockReply); + commentsService.findOneBy = jest + .fn() + .mockResolvedValue(commentEntityMock); + + const updatedMockReply = { ...mockReply, isActive: false, text: null }; + replyRepository.save = jest.fn().mockResolvedValue(updatedMockReply); + + const result = await commentsService.removeReply(id, user, userRoles); + + expect(commentsService.findOneReplyBy).toHaveBeenCalledWith({ id }); + expect(commentsService.findOneBy).toHaveBeenCalledWith({ + id: mockReply.commentId, + }); + expect(replyRepository.save).toHaveBeenCalledWith(expect.any(Object)); + expect(result).toEqual(updatedMockReply); + }); + }); + /** * @Description * These set of tests validates that the method passes the correct values to the repository, diff --git a/src/backend/test/util/mocks/data/comments.mock.ts b/src/backend/test/util/mocks/data/comments.mock.ts index 33507776f..9199e5d7b 100644 --- a/src/backend/test/util/mocks/data/comments.mock.ts +++ b/src/backend/test/util/mocks/data/comments.mock.ts @@ -5,9 +5,10 @@ import { CommentsCountDbRO, CommentsCountRO, } from 'src/modules/comments/ro/comments-count-ro'; -import { CommentRO } from 'src/modules/comments/ro/get-comment.ro'; +import { CommentRO, ReplyRO } from 'src/modules/comments/ro/get-comment.ro'; import { keycloakUserMock } from './auth.mock'; import { piaIntakeEntityMock } from './pia-intake.mock'; +import { CreateReplyDto } from 'src/modules/comments/dto/create-reply.dto'; export const createCommentDtoMock: CreateCommentDto = { path: AllowedCommentPaths['accuracyCorrectionAndRetention.accuracy'], @@ -68,3 +69,20 @@ export const commentsCountDbRO: CommentsCountDbRO = { export const commentsCountROMock: Partial = { [commentsCountDbRO.path]: +commentsCountDbRO.count, }; + +export const createReplyDtoMock: CreateReplyDto = { + commentId: 1, + text: 'This is a test reply', +}; + +export const replyROMock: ReplyRO = { + id: 101, + commentId: 1, + text: 'Test Reply 1', + createdByDisplayName: 'Test User', + createdAt: new Date(), + updatedAt: new Date(), + isResolved: false, + isActive: true, + createdByGuid: keycloakUserMock.idir_user_guid, +};