jest.mock('../../../src/api/modules/users/infrastructure/dataAccessObjects/userDAO');
import { faker } from '@faker-js/faker/locale/en_US';
import { Competency } from '../../../src/api/modules/competencies/business/models/competency';
import {
    allPromises,
    BadRequestError,
    CursorIdPagination,
    ErrorCodes,
    EventEmitterPort,
    ForbiddenError,
    IllegalStateError,
    ListOptions,
    LoggerPort,
    NotFoundError,
    NotImplementedError,
    PromisesPort,
    RequestConflictError,
    UtilsAdapter,
} from '@humand-packages/common';
import { mock, MockProxy } from 'jest-mock-extended';
import { CoreForm } from '../../../src/api/kernels/forms/business/models/coreForm';
import { FormAnswer } from '../../../src/api/kernels/forms/business/models/formAnswer';
import { getFormDraftContentReferenceIds } from '../../../src/api/kernels/forms/business/types/formDraftContent';
import { UserDAO } from '../../../src/api/modules/users/infrastructure/dataAccessObjects/userDAO';
import { FormTemplate } from '../../../src/api/modules/formTemplates/business/models/formTemplate';
import { FORM_TEMPLATE_ORIGINS } from '../../../src/api/modules/formTemplates/business/types/origins';
import { FORM_TEMPLATE_STATUS, FormTemplateStatusValues } from '../../../src/api/modules/formTemplates/business/types/status';
import { GoalCycleStatus } from '../../../src/api/modules/goals/business/types/goalCycleStatus';
import { GoalStatus } from '../../../src/api/modules/goals/business/types/goalStatus';
import { DuplicatePerformanceReviewCycleInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/duplicatePerformanceReviewCycleInterface';
import { DuplicatePerformanceReviewExternalReviewerInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/duplicatePerformanceReviewExternalReviewerInterface';
import { DuplicatePerformanceReviewFormReviewInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/duplicatePerformanceReviewFormReviewInterface';
import { DuplicatePerformanceReviewFormTemplateInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/duplicatePerformanceReviewFormTemplateInterface';
import { FillPerformanceReviewFormReviewInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/fillPerformanceReviewFormReviewInterface';
import {
    questionWeightsArrayToObject,
    questionWeightsObjectToArray,
    ScoreDetails,
    UpdatePerformanceReviewFormTemplateInterface,
} from '../../../src/api/modules/performanceReviews/business/interfaces/performanceReviewFormTemplateInterface';
import {
    InstanceInformationForReviews,
    UserInformationForReviews,
} from '../../../src/api/modules/performanceReviews/business/interfaces/performanceReviewInformationForReviews';
import { RequestPerformanceReviewFormReviewEditInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/RequestPerformanceReviewFormReviewEditInterface';
import { ReviewPerformanceReviewFormReviewEditRequestInterface } from '../../../src/api/modules/performanceReviews/business/interfaces/reviewPerformanceReviewFormReviewEditRequestInterface';
import {
    PerformanceReviewCycle,
    PerformanceReviewCycleForCreation,
} from '../../../src/api/modules/performanceReviews/business/models/performanceReviewCycle';
import {
    PerformanceReviewExternalReviewer,
    PerformanceReviewExternalReviewerForCreation,
} from '../../../src/api/modules/performanceReviews/business/models/PerformanceReviewExternalReviewer';
import { PerformanceReviewFormReview } from '../../../src/api/modules/performanceReviews/business/models/performanceReviewFormReview';
import { PerformanceReviewFormReviewEditRequest } from '../../../src/api/modules/performanceReviews/business/models/performanceReviewFormReviewEditRequest';
import { PerformanceReviewFormTemplate } from '../../../src/api/modules/performanceReviews/business/models/performanceReviewFormTemplate';
import {
    PerformanceReviewGoalQuestionWithAnswer,
    PerformanceReviewGoalQuestionWithAnswerDataFields,
    PerformanceReviewGoalQuestionWithAnswerForCreation,
} from '../../../src/api/modules/performanceReviews/business/models/performanceReviewGoalQuestionWithAnswer';
import { PerformanceReviewNineBoxPoint } from '../../../src/api/modules/performanceReviews/business/models/PerformanceReviewNineBoxPoint';
import {
    PerformanceReviewReviewer,
    PerformanceReviewReviewerForCreation,
} from '../../../src/api/modules/performanceReviews/business/models/performanceReviewReviewer';
import {
    PerformanceReviewTargetUser,
    PerformanceReviewTargetUserForCreation,
} from '../../../src/api/modules/performanceReviews/business/models/performanceReviewTargetUser';
import {
    PerformanceReviewTemplate,
    PerformanceReviewTemplateForCreation,
} from '../../../src/api/modules/performanceReviews/business/models/performanceReviewTemplate';
import {
    PerformanceReviewUser,
    PerformanceReviewUserWithStats,
} from '../../../src/api/modules/performanceReviews/business/models/performanceReviewUser';
import { PerformanceReviewCoreFormsPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewCoreFormsPort';
import { PerformanceReviewFormTemplatesPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewFormTemplatesPort';
import { PerformanceReviewGoalsPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewGoalsPort';
import { PerformanceReviewNotificationsPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewNotificationsPort';
import { PerformanceReviewsRepositoryPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewsRepositoryPort';
import { PerformanceReviewUsersPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewUsersPort';
import { PerformanceReviewUtilsPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewUtilsPort';
import { PerformanceReviewCalibrationService } from '../../../src/api/modules/performanceReviews/business/services/performanceReviewCalibrationService';
import { PerformanceReviewsService } from '../../../src/api/modules/performanceReviews/business/services/performanceReviewsService';
import { ListPerformanceReviewFormReviewsOptions } from '../../../src/api/modules/performanceReviews/business/types/listPerformanceReviewFormTemplateAnswersOptions';
import { PerformanceReviewCycleParticipantType } from '../../../src/api/modules/performanceReviews/business/types/performanceReviewCycleRole';
import { PerformanceReviewCycleReviewedUsersMode } from '../../../src/api/modules/performanceReviews/business/types/performanceReviewCycleReviewedUsersMode';
import {
    PerformanceReviewCycleStatus,
    PerformanceReviewCycleStatusValues,
} from '../../../src/api/modules/performanceReviews/business/types/performanceReviewCycleStatus';
import {
    PerformanceReviewEvaluationTypes,
    PerformanceReviewEvaluationTypesValues,
} from '../../../src/api/modules/performanceReviews/business/types/performanceReviewEvaluationTypes';
import { SubordinateReviewConfig } from '../../../src/api/modules/performanceReviews/business/types/performanceReviewFormReviewConfiguration';

import {
    PerformanceReviewFormEditRequestFinalStatus,
    PerformanceReviewFormEditRequestStatus,
} from '../../../src/api/modules/performanceReviews/business/types/performanceReviewFormEditRequestStatus';
import {
    isFormSharedStatuses,
    PerformanceReviewFormShareStatus,
} from '../../../src/api/modules/performanceReviews/business/types/performanceReviewFormShareStatus';
import { ReviewStatsByTypeAndStatus } from '../../../src/api/modules/performanceReviews/business/types/performanceReviewFormStats';
import { PerformanceReviewFormStatus } from '../../../src/api/modules/performanceReviews/business/types/performanceReviewFormStatus';
import { PerformanceReviewTemplateGoalsProgressMode } from '../../../src/api/modules/performanceReviews/business/types/performanceReviewTemplateGoalsProgressMode';
import { UpdatePerformanceReviewTargetUserVC } from '../../../src/api/modules/performanceReviews/presentation/validationClasses/updatePerformanceReviewargetUserVC';
import { QuestionTemplate } from '../../../src/api/modules/questionTemplates/business/models/questionTemplate';
import { QUESTION_TEMPLATE_STATUS } from '../../../src/api/modules/questionTemplates/business/types/status';
import { QUESTION_TEMPLATES_QUESTION_TYPES } from '../../../src/api/modules/questionTemplates/business/types/types';
import { User } from '../../../src/api/modules/users/business/models/user';
import { QUESTION_TYPES } from '../../../src/api/types/questionTypes';
import { fakePositiveInteger, fakePositiveIntegerArray, generateRandomCursorPagination } from '../../common/common';
import { checkMockCallsCount, mockImplementationTimes } from '../../common/mock/mockHelpers';
import {
    generateFormAnswerWithValues,
    generateFormDraftWithQuestions,
    generateRandomCoreFormQuestion,
    generateRandomCoreFormWithQuestions,
    generateRandomFormQuestionAnswers,
} from '../../kernels/forms/common';
import { generateRandomInstance } from '../courses/commons';
import { generateRandomFormTemplate } from '../formTemplates/common';
import { generateRandomGoal, generateRandomGoalCycle, generateRandomGoalWeight } from '../goals/common';
import {
    generateCreateQuestionTemplateDataRandomly,
    generateQuestionTemplateWithValues,
    generateRandomQuestionTemplate,
} from '../questionTemplates/common';
import { generateUserWithProfileFields, userWithProfileFieldsToUserDao } from '../users/common';
import {
    addStatsToPerformanceReviewUser,
    checkCycleFormReviewsHaveUsers,
    checkStartedPerformanceReviewCycle,
    formTemplateToFormTemplateParams,
    generateCompetencyQuestionWithAnswer,
    generateDefaultScoreDetails,
    generateExternalReviewerFromCreation,
    generateFormTemplateDuplication,
    generateFormTemplateWithValues,
    generateGoalQuestionWithAnswer,
    generateGoalQuestionWithAnswerFromCreation,
    generatePerformanceReviewCycleFromCreation,
    generatePerformanceReviewCycleWithValues,
    generatePerformanceReviewFormReviewWithValues,
    generatePerformanceReviewFormTemplateFromCreation,
    generatePerformanceReviewFormTemplateWithDraft,
    generatePerformanceReviewFormTemplateWithValues,
    generatePerformanceReviewReviewer,
    generatePerformanceReviewReviewerFromCreation,
    generatePerformanceReviewTemplateFromCreation,
    generateQuestionWeightsFromIds,
    generateRandomEditRequest,
    generateRandomExternalReviewer,
    generateRandomGoalQuestionWithAnswerForCreation,
    generateRandomNineBoxPoint,
    generateRandomPerformanceReviewCycle,
    generateRandomPerformanceReviewFormReview,
    generateRandomPerformanceReviewFormTemplate,
    generateRandomPerformanceReviewFormTemplateWithTemplate,
    generateRandomPerformanceReviewReviewerForCreation,
    generateRandomPerformanceReviewTargetUser,
    generateRandomPerformanceReviewTargetUserForCreation,
    generateRandomPerformanceReviewUser,
    generateRandomReviewCountsForShareStatuses,
    generateRandomReviewCountsForTypesAndStatuses,
    generateRandomReviewCycleDuplication,
    generateRandomReviewDuplication,
    generateRandomReviewUserStatsByType,
    generateRandomScoreDetails,
    generateRandomTemplate,
    generateRandomTemplateForCreation,
    generateRandomUser,
    getPerformanceReviewFormReviewFromCreation,
    getPerformanceReviewFormReviewFromDuplication,
    getPerformanceReviewFormTemplateFromDuplication,
    getPerformanceReviewTargetUserFromCreation,
} from './common';
import { DepartmentRepositoryPort } from '../../../src/api/modules/departments/business/ports/departmentRepositoryPort';
import { PerformanceReviewCompetenciesPort } from '../../../src/api/modules/performanceReviews/business/ports/performanceReviewCompetenciesPort';

let repositoryMock: MockProxy<PerformanceReviewsRepositoryPort>;
let formTemplatesPortMock: MockProxy<PerformanceReviewFormTemplatesPort>;
let coreFormsPortMock: MockProxy<PerformanceReviewCoreFormsPort>;
let promisesPort: MockProxy<PromisesPort>;
let utilsPort: MockProxy<PerformanceReviewUtilsPort>; 
let eventsEmitterPort: MockProxy<EventEmitterPort>;
let usersPortMock: MockProxy<PerformanceReviewUsersPort>;
let goalsPort: MockProxy<PerformanceReviewGoalsPort>;
let competenciesPort: MockProxy<PerformanceReviewCompetenciesPort>;
let performanceReviewNotificationsPort: MockProxy<PerformanceReviewNotificationsPort>;
let departmentsPort: MockProxy<DepartmentRepositoryPort>;
let loggerPort: MockProxy<LoggerPort>;
let calibrationServiceMock: MockProxy<PerformanceReviewCalibrationService>;
let performanceReviewsService: PerformanceReviewsService;

describe('PerformanceReviewsService', () => {
    beforeEach(() => {
        repositoryMock = mock<PerformanceReviewsRepositoryPort>();
        formTemplatesPortMock = mock<PerformanceReviewFormTemplatesPort>();
        coreFormsPortMock = mock<PerformanceReviewCoreFormsPort>();
        promisesPort = mock<PromisesPort>();
        utilsPort = mock<PerformanceReviewUtilsPort>();
        eventsEmitterPort = mock<EventEmitterPort>();
        usersPortMock = mock<PerformanceReviewUsersPort>();
        goalsPort = mock<PerformanceReviewGoalsPort>();
        competenciesPort = mock<PerformanceReviewCompetenciesPort>();
        performanceReviewNotificationsPort = mock<PerformanceReviewNotificationsPort>();
        departmentsPort = mock<DepartmentRepositoryPort>()
        loggerPort = mock<LoggerPort>();
        calibrationServiceMock = mock<PerformanceReviewCalibrationService>();
        performanceReviewsService = new PerformanceReviewsService(
            repositoryMock,
            formTemplatesPortMock,
            coreFormsPortMock,
            promisesPort,
            eventsEmitterPort,
            utilsPort,
            usersPortMock,
            goalsPort,
            performanceReviewNotificationsPort,
            departmentsPort,
            competenciesPort,
            loggerPort,
            calibrationServiceMock,
        );

        // General implementation mocks
        promisesPort.allPromises.mockImplementation((promises) => {
            return allPromises(promises);
        });
        promisesPort.allPromises.mockImplementation(async (promiseFunctions) => {
            const promises = promiseFunctions?.map((promiseFunction) => promiseFunction());
            return Promise.all(promises);
        });

        utilsPort.splitInteger.mockImplementation(new UtilsAdapter().splitInteger);

        // Safe defaults for the score-persistence repo methods. Tests that exercise the
        // recompute hooks override these with `mockResolvedValueOnce`.
        repositoryMock.getUsersScoresByEvaluationType.mockResolvedValue({});
        repositoryMock.updateTargetUserScore.mockResolvedValue(undefined);
        repositoryMock.bulkUpdateTargetUserScores.mockResolvedValue(undefined);
        repositoryMock.lockTargetUserRow.mockResolvedValue(undefined);
        repositoryMock.getTargetUsersScoresMap.mockResolvedValue({});
        repositoryMock.getTargetUserIds.mockResolvedValue(new Set());
        repositoryMock.getAverageScoreForTargetUsers.mockResolvedValue(null);
    });

    afterEach(() => {
        jest.clearAllMocks();

    });

    describe('createCycle', () => {
        let instanceId: number;
        let cycleName: string;
        let fakeId: number;
        let fakeDate: Date;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            cycleName = faker.word.words();
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
        });

        it('create cycle successfully', async () => {
            repositoryMock.newCycle.mockImplementationOnce((reviewCycle) => {
                const newReviewCycle = new PerformanceReviewCycle({
                    canBeShared: reviewCycle.canBeShared,
                    createdAt: fakeDate,
                    deletedAt: null,
                    duplicationId: reviewCycle.duplicationId,
                    endDate: reviewCycle.endDate,
                    id: fakeId,
                    instanceId: reviewCycle.instanceId,
                    name: reviewCycle.name,
                    peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                    peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                    peerReviewExternalReviewerSelectionVisibleForBosses: reviewCycle.peerReviewExternalReviewerSelectionVisibleForBosses,
                    peerReviewReviewerSelectionVisibleForBosses: reviewCycle.peerReviewReviewerSelectionVisibleForBosses,
                    reviewedUsersMode: reviewCycle.reviewedUsersMode,
                    reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                    reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                    startDate: reviewCycle.startDate,
                    status: reviewCycle.status,
                    updatedAt: fakeDate,
                    upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                    withSummary: reviewCycle.withSummary,
                });
                return Promise.resolve(newReviewCycle);
            });

            const result = await performanceReviewsService.createCycle(instanceId, { name: cycleName });

            expect(result).toBeInstanceOf(PerformanceReviewCycle);
            expect(result.name).toBe(cycleName);
            expect(result.instanceId).toBe(instanceId);
            expect(result.status).toBe(PerformanceReviewCycleStatus.DRAFT);
            expect(result.reviewedUsersMode).toBe(PerformanceReviewCycleReviewedUsersMode.ALL_USERS);
            expect(result.startDate).toBeNull();
            expect(result.endDate).toBeNull();
            expect(result.id).toBe(fakeId);
            expect(result.createdAt).toBe(fakeDate);
            expect(result.updatedAt).toBe(fakeDate);
            expect(result.canBeShared).toBeTrue();
            expect(result.deletedAt).toBeNull();
            expect(result.formTemplates).toHaveLength(0);
            expect(result.reviewsVisibleAfterFinishedReviewCycle).toBeFalse();
            expect(result.reviewerLimitByHiringDate).toBeNull();
            expect(result.peerReviewReviewerSelectionVisibleForBosses).toBeNull();
            expect(result.peerReviewExternalReviewerSelectionVisibleForBosses).toBeNull();
            expect(result.withSummary).toBeFalse();
            expect(result.upwardReviewAnonymousAnswers).toBeNull();
        });
    });

    describe('updateCycle', () => {
        let reviewCycle: PerformanceReviewCycle;
        let fakeId: number;
        let fakeDate: Date;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
            });
            reviewCycle.formTemplates = [];
        });

        it('update cycle successfully if nothing changed', async () => {
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            const result = await performanceReviewsService.updateCycle(reviewCycle, {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            });

            expect(result).toBeInstanceOf(PerformanceReviewCycle);
            expect(result.name).toBe(reviewCycle.name);
            expect(result.instanceId).toBe(reviewCycle.instanceId);
            expect(result.status).toBe(reviewCycle.status);
            expect(result.reviewedUsersMode).toBe(reviewCycle.reviewedUsersMode);
            expect(result.goalCycleId).toBeNull();
            expect(result.startDate).toBe(reviewCycle.startDate);
            expect(result.endDate).toBe(reviewCycle.endDate);
            expect(result.id).toBe(reviewCycle.id);
            expect(result.createdAt).toBe(reviewCycle.createdAt);
            expect(result.withSummary).toBe(reviewCycle.withSummary);
            expect(result.reviewerLimitByHiringDate).toBe(reviewCycle.reviewerLimitByHiringDate);
            expect(result.reviewsVisibleAfterFinishedReviewCycle).toBe(reviewCycle.reviewsVisibleAfterFinishedReviewCycle);
            expect(result.peerReviewExternalReviewerSelectionVisibleForBosses).toBe(reviewCycle.peerReviewExternalReviewerSelectionVisibleForBosses);
            expect(result.peerReviewReviewerSelectionVisibleForBosses).toBe(reviewCycle.peerReviewReviewerSelectionVisibleForBosses);
            expect(result.canBeShared).toBe(reviewCycle.canBeShared);
            expect(result.upwardReviewAnonymousAnswers).toBe(reviewCycle.upwardReviewAnonymousAnswers);
        });

        it('update cycle successfully if goal cycle id changed', async () => {
            const goalCycle = generateRandomGoalCycle(GoalCycleStatus.IN_PROGRESS);
            goalsPort.getGoalCycleByPk.mockResolvedValueOnce(goalCycle);
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            const result = await performanceReviewsService.updateCycle(reviewCycle, {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: reviewCycle.goalCycleId,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            });

            expect(result).toBeInstanceOf(PerformanceReviewCycle);
            expect(result.name).toBe(reviewCycle.name);
            expect(result.instanceId).toBe(reviewCycle.instanceId);
            expect(result.status).toBe(reviewCycle.status);
            expect(result.reviewedUsersMode).toBe(reviewCycle.reviewedUsersMode);
            expect(result.goalCycleId).toBe(reviewCycle.goalCycleId);
            expect(result.startDate).toBe(reviewCycle.startDate);
            expect(result.endDate).toBe(reviewCycle.endDate);
            expect(result.id).toBe(reviewCycle.id);
            expect(result.createdAt).toBe(reviewCycle.createdAt);
            expect(result.withSummary).toBe(reviewCycle.withSummary);
            expect(result.reviewerLimitByHiringDate).toBe(reviewCycle.reviewerLimitByHiringDate);
            expect(result.reviewsVisibleAfterFinishedReviewCycle).toBe(reviewCycle.reviewsVisibleAfterFinishedReviewCycle);
            expect(result.peerReviewExternalReviewerSelectionVisibleForBosses).toBe(reviewCycle.peerReviewExternalReviewerSelectionVisibleForBosses);
            expect(result.peerReviewReviewerSelectionVisibleForBosses).toBe(reviewCycle.peerReviewReviewerSelectionVisibleForBosses);
            expect(result.canBeShared).toBe(reviewCycle.canBeShared);
            expect(result.upwardReviewAnonymousAnswers).toBe(reviewCycle.upwardReviewAnonymousAnswers);
        });

        it('update cycle successfully if nothing changed and cycle has form templates', async () => {
            const formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, { evaluationType: PerformanceReviewEvaluationTypes.PEER_REVIEW }),
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType: PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                }),
            ];
            reviewCycle.formTemplates = formTemplates;
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            const result = await performanceReviewsService.updateCycle(reviewCycle, {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: reviewCycle.peerReviewExternalReviewerSelectionVisibleForBosses,
                peerReviewReviewerSelectionVisibleForBosses: reviewCycle.peerReviewReviewerSelectionVisibleForBosses,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            });

            expect(result.formTemplates).not.toBeNull();
            expect(result.formTemplates).toHaveLength(formTemplates.length);

            result.formTemplates?.forEach((formTemplate, index) => {
                expect(formTemplate.id).toBe(formTemplates[index].id);
                expect(formTemplate.cycleId).toBe(formTemplates[index].cycleId);
                expect(formTemplate.evaluationType).toBe(formTemplates[index].evaluationType);
                expect(formTemplate.templateId).toBe(formTemplates[index].templateId);
                expect(formTemplate.coreFormId).toBe(formTemplates[index].coreFormId);
                expect(formTemplate.coreFormTag).toBe(formTemplates[index].coreFormTag);
                expect(formTemplate.formReviewConfig).toEqual(formTemplates[index].formReviewConfig);
                expect(formTemplate.scoreConfig).toEqual(formTemplates[index].scoreConfig);
            });
        });

        it('update cycle status to selection successfully', async () => {
            const formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType: faker.helpers.arrayElement([
                        PerformanceReviewEvaluationTypes.PEER_REVIEW,
                        PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                    ]),
                }),
            ];
            reviewCycle.formTemplates = formTemplates;
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            const result = await performanceReviewsService.updateCycle(reviewCycle, {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: PerformanceReviewCycleStatus.SELECTION,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            });

            expect(result.status).toBe(PerformanceReviewCycleStatus.SELECTION);
            expect(result.formTemplates).not.toBeNull();
            expect(result.formTemplates).toHaveLength(formTemplates.length);

            result.formTemplates?.forEach((formTemplate, index) => {
                expect(formTemplate.id).toBe(formTemplates[index].id);
                expect(formTemplate.cycleId).toBe(formTemplates[index].cycleId);
                expect(formTemplate.evaluationType).toBe(formTemplates[index].evaluationType);
                expect(formTemplate.templateId).toBe(formTemplates[index].templateId);
                expect(formTemplate.coreFormId).toBe(formTemplates[index].coreFormId);
                expect(formTemplate.coreFormTag).toBe(formTemplates[index].coreFormTag);
                expect(formTemplate.scoreConfig?.withScore).toBe(formTemplates[index].scoreConfig?.withScore);
                expect(formTemplate.scoreConfig?.withGoalScore).toBe(formTemplates[index].scoreConfig?.withGoalScore);
                expect(formTemplate.scoreConfig?.withCompetenciesScore).toBe(formTemplates[index].scoreConfig?.withCompetenciesScore);
                expect(formTemplate.scoreConfig?.scaleStart).toBe(formTemplates[index].scoreConfig?.scaleStart);
                expect(formTemplate.scoreConfig?.scaleEnd).toBe(formTemplates[index].scoreConfig?.scaleEnd);
                expect(formTemplate.scoreConfig?.formWeight).toBe(formTemplates[index].scoreConfig?.formWeight);
                expect(formTemplate.scoreConfig?.goalsWeight).toBe(formTemplates[index].scoreConfig?.goalsWeight);
            });
        });

        it('update cycle successfully if form templates change', async () => {
            const formTemplatesInterface = [
                {
                    evaluationType: faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes)),
                    templateId: fakePositiveInteger(),
                },
                {
                    evaluationType: faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes)),
                    templateId: null,
                },
            ];
            const updateParams = {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: formTemplatesInterface,
                goalCycleId: null,
                name: reviewCycle.name,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
                othersReviewReviewerSelectionVisibleForBosses: false,
                othersReviewAnonymousAnswers: false,
            };


            const template = generateRandomTemplate(formTemplatesInterface[0].templateId!);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            repositoryMock.countTemplatesWithIds.mockResolvedValueOnce(1);
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.createFormTemplates.mockImplementationOnce((formTemplates) => {
                const response = formTemplates.map((formTemplate) => generatePerformanceReviewFormTemplateFromCreation(formTemplate));
                return Promise.resolve(response);
            });
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));
            formTemplatesPortMock.findFormTemplatesById.mockResolvedValueOnce({
                [template.formTemplateId!]: generateRandomFormTemplate(template.formTemplateId!),
            });
            formTemplatesPortMock.getRatingQuestionsOfFormTemplates.mockResolvedValueOnce(new Map([[template.formTemplateId!, [generateRandomQuestionTemplate(QUESTION_TEMPLATES_QUESTION_TYPES.RATING)]]]));

            const result = await performanceReviewsService.updateCycle(reviewCycle, updateParams);

            expect(result).not.toBeNull();
            expect(result.formTemplates).not.toBeNull();
            expect(result.formTemplates).toHaveLength(updateParams.formTemplates.length);

            result.formTemplates?.forEach((formTemplate, index) => {
                expect(formTemplate.cycleId).toBe(reviewCycle.id);
                expect(formTemplate.evaluationType).toBe(updateParams.formTemplates[index].evaluationType);
                expect(formTemplate.templateId).toBe(updateParams.formTemplates[index].templateId);
                expect(formTemplate.coreFormId).toBeNull();
                expect(formTemplate.coreFormTag).toBeNull();
            });
        });

        it('update cycle successfully if mode changes to all users', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            reviewCycle.formTemplates = [];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            const result = await performanceReviewsService.updateCycle(reviewCycle, {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            });

            expect(result.reviewedUsersMode).toBe(PerformanceReviewCycleReviewedUsersMode.ALL_USERS);
        });

        it('throw bad request error when updating cycle for selection and there is no peer evaluation type', async () => {
            const formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType: faker.helpers.arrayElement([
                        PerformanceReviewEvaluationTypes.AUTO_REVIEW,
                        PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                        PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                    ]),
                }),
            ];
            reviewCycle.formTemplates = formTemplates;
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            const updateParams = {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: PerformanceReviewCycleStatus.SELECTION,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            };

            await expect(performanceReviewsService.updateCycle(reviewCycle, updateParams)).rejects.toThrow(BadRequestError);
        });

        it('throw illegal state error if cycle does not have form templates loaded', async () => {
            reviewCycle.formTemplates = null;
            const updateParams = {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: [] as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            };

            await expect(performanceReviewsService.updateCycle(reviewCycle, updateParams)).rejects.toThrow(IllegalStateError);
        });

        it('throw bad request error if cycle has already started', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.FINISHED, PerformanceReviewCycleStatus.IN_PROGRESS]),
            });
            reviewCycle.formTemplates = [];
            const updateParams = {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            };

            await expect(performanceReviewsService.updateCycle(reviewCycle, updateParams)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if new form templates do not exist', async () => {
            const updateParams = {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: [
                    {
                        evaluationType: faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes)),
                        formTemplateId: fakePositiveInteger(),
                        templateId: fakePositiveInteger(),
                        withScore: faker.datatype.boolean(),
                    },
                ],
                goalCycleId: null,
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            };

            repositoryMock.countTemplatesWithIds.mockResolvedValueOnce(0);

            await expect(performanceReviewsService.updateCycle(reviewCycle, updateParams)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if associated goal cycle is in invalid state', async () => {
            const goalCycle = generateRandomGoalCycle(GoalCycleStatus.PENDING);
            goalsPort.getGoalCycleByPk.mockResolvedValueOnce(goalCycle);
            const updateParams = {
                canBeShared: reviewCycle.canBeShared,
                canBeSharedBeforeCycleFinish: false,
                formTemplates: reviewCycle.formTemplates as PerformanceReviewFormTemplate[],
                goalCycleId: fakePositiveInteger(),
                name: reviewCycle.name,
                othersReviewAnonymousAnswers: false,
                othersReviewReviewerSelectionVisibleForBosses: null,
                peerExternalReviewAnonymousAnswers: reviewCycle.peerExternalReviewAnonymousAnswers,
                peerReviewAnonymousAnswers: reviewCycle.peerReviewAnonymousAnswers,
                peerReviewExternalReviewerSelectionVisibleForBosses: null,
                peerReviewReviewerSelectionVisibleForBosses: null,
                reviewedUsersMode: reviewCycle.reviewedUsersMode,
                reviewerLimitByHiringDate: reviewCycle.reviewerLimitByHiringDate,
                reviewsVisibleAfterFinishedReviewCycle: reviewCycle.reviewsVisibleAfterFinishedReviewCycle,
                status: reviewCycle.status,
                upwardReviewAnonymousAnswers: reviewCycle.upwardReviewAnonymousAnswers,
                withSummary: reviewCycle.withSummary,
            };

            await expect(performanceReviewsService.updateCycle(reviewCycle, updateParams)).rejects.toThrow(BadRequestError);
        });
    });

    describe('updateTargetUsersOfCycle', () => {
        let fakeId: number;
        let fakeDate: Date;
        let userIds: number[];

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            userIds = faker.helpers.multiple(faker.number.int);
        });

        it('update target users of cycle successfully if cycle was already targeted', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.upsertTargetUsers.mockImplementationOnce((targetUsers) => {
                const transformedTargetUsers = targetUsers.map((targetUser) => getPerformanceReviewTargetUserFromCreation(targetUser));
                return Promise.resolve(transformedTargetUsers);
            });
            repositoryMock.getAllReviewers.mockResolvedValueOnce([]);
            repositoryMock.deleteReviewedsNotIn.mockResolvedValueOnce();

            const result = await performanceReviewsService.updateTargetUsersOfCycle(reviewCycle, userIds);

            expect(result.reviewedUsersMode).toBe(PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS);
            expect(result.targetUsers).not.toBeNull();
            expect(result.targetUsers).toHaveLength(userIds.length);

            result.targetUsers?.forEach((targetUser, index) => {
                expect(targetUser).toBeInstanceOf(PerformanceReviewTargetUser);
                expect(targetUser.cycleId).toBe(reviewCycle.id);
                expect(targetUser.userId).toBe(userIds[index]);
                expect(targetUser.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARE_DISABLED);
            });

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 1);
            checkMockCallsCount(repositoryMock.saveCycle, 0);
            checkMockCallsCount(repositoryMock.upsertTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteReviewedsNotIn, 1);
        });

        it('update target users of cycle successfully if cycle was not targeted', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));
            repositoryMock.upsertTargetUsers.mockImplementationOnce((targetUsers) => {
                const transformedTargetUsers = targetUsers.map((targetUser) => getPerformanceReviewTargetUserFromCreation(targetUser));
                return Promise.resolve(transformedTargetUsers);
            });

            repositoryMock.getAllReviewers.mockResolvedValueOnce([]);
            repositoryMock.deleteReviewedsNotIn.mockResolvedValueOnce();

            const result = await performanceReviewsService.updateTargetUsersOfCycle(reviewCycle, userIds);

            expect(result.reviewedUsersMode).toBe(PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS);
            expect(result.targetUsers).not.toBeNull();
            expect(result.targetUsers).toHaveLength(userIds.length);

            result.targetUsers?.forEach((targetUser, index) => {
                expect(targetUser).toBeInstanceOf(PerformanceReviewTargetUser);
                expect(targetUser.cycleId).toBe(reviewCycle.id);
                expect(targetUser.userId).toBe(userIds[index]);
            });

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 0);
            checkMockCallsCount(repositoryMock.saveCycle, 1);
            checkMockCallsCount(repositoryMock.upsertTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteReviewedsNotIn, 1);
        });

        it('throw bad request error if cycle has already started', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.FINISHED, PerformanceReviewCycleStatus.IN_PROGRESS]),
            });

            await expect(performanceReviewsService.updateTargetUsersOfCycle(reviewCycle, userIds)).rejects.toThrow(BadRequestError);
        });
    });

    describe('updateFormTemplate', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewTemplate: PerformanceReviewFormTemplate;
        let params: UpdatePerformanceReviewFormTemplateInterface;
        let goalsWeight: number;
        let formWeight: number;

        beforeEach(() => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });
            reviewTemplate = generateRandomPerformanceReviewFormTemplate(reviewCycle.id);
            params = {
                configDetails: null,
                templateId: fakePositiveInteger(),
            };
            formWeight = fakePositiveInteger({ max: 99 });
            goalsWeight = 100 - formWeight;
        });

        it('update form template successfully', async () => {
            params = {
                configDetails: null,
                templateId: fakePositiveInteger(),
            };
            const template = generateRandomTemplate(params.templateId!, reviewCycle.instanceId);

            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));
            const questionTemplate = generateRandomQuestionTemplate(QUESTION_TEMPLATES_QUESTION_TYPES.RATING);
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce([questionTemplate]);

            const result = await performanceReviewsService.updateFormTemplate(reviewCycle, reviewTemplate, params);

            expect(result).toBeInstanceOf(PerformanceReviewFormTemplate);
            expect(result.templateId).toBe(params.templateId);
            expect(result.scoreConfig?.withScore).toBe(true);
            expect(result.scoreConfig?.withHiddenScoreForReviewed).toBe(false);
            expect(result.scoreConfig?.withGoalScore).toBe(false);
            expect(result.scoreConfig?.withCompetenciesScore).toBe(false);
            expect(result.scoreConfig?.formTemplateQuestionWeights).toStrictEqual({ [questionTemplate.id!]: 100 });
            expect(result.scoreConfig?.formWeight).toBe(100);
            expect(result.scoreConfig?.goalsWeight).toBe(0);
            expect(result.scoreConfig?.scaleStart).toBe(0);
            expect(result.scoreConfig?.scaleEnd).toBe(10);
            expect(result.template).not.toBeNull();
            expect(result.template?.id).toBe(params.templateId);
            expect(result.scoreConfig?.directionWeight).toBe(null);
            expect(result.formReviewConfig).toEqual(reviewTemplate.formReviewConfig);
            expect(result.scoreConfig).toEqual(reviewTemplate.scoreConfig);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 1);
        });

        it('allows withCalibration true when subordinate review form template has score enabled', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: true,
            });
            params = {
                templateId: reviewTemplate.templateId!,
                configDetails: {
                    canBeShared: true,
                    reviewsVisibleAfterFinishedReviewCycle: true,
                    canBeSharedBeforeCycleFinish: false,
                    withCalibration: true,
                },
            };
            const template = generateRandomTemplate(params.templateId, reviewCycle.instanceId);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            repositoryMock.saveCycle.mockResolvedValueOnce(reviewCycle);
            repositoryMock.saveFormTemplate.mockImplementationOnce((formTemplate) => Promise.resolve(formTemplate));

            const result = await performanceReviewsService.updateFormTemplate(reviewCycle, reviewTemplate, params);

            expect(result.formReviewConfig).toMatchObject({ withCalibration: true });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 1);
        });

        it('throws when withCalibration is true but subordinate review form template has score disabled', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.DRAFT;
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: false,
            });
            params = {
                templateId: reviewTemplate.templateId!,
                configDetails: {
                    canBeShared: true,
                    reviewsVisibleAfterFinishedReviewCycle: true,
                    canBeSharedBeforeCycleFinish: false,
                    withCalibration: true,
                },
            };
            const template = generateRandomTemplate(params.templateId, reviewCycle.instanceId);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));

            await expect(performanceReviewsService.updateFormTemplate(reviewCycle, reviewTemplate, params)).rejects.toMatchObject({
                message: 'withCalibration can only be enabled when subordinate review has score enabled',
                code: ErrorCodes.REVIEW_FORM_TEMPLATE_INVALID_CONFIG,
            });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throws when changing withCalibration after cycle has started', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: true,
            });
            params = {
                templateId: reviewTemplate.templateId!,
                configDetails: {
                    canBeShared: true,
                    reviewsVisibleAfterFinishedReviewCycle: true,
                    canBeSharedBeforeCycleFinish: false,
                    withCalibration: true,
                },
            };
            const template = generateRandomTemplate(params.templateId, reviewCycle.instanceId);
            template.formTemplate = generateRandomFormTemplate(fakePositiveInteger());
            template.formTemplateId = template.formTemplate.id!;
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(template.formTemplate);

            await expect(performanceReviewsService.updateFormTemplate(reviewCycle, reviewTemplate, params)).rejects.toMatchObject({
                message: 'Cannot change withCalibration when cycle has already started',
                code: ErrorCodes.REVIEW_CYCLE_INVALID_STATUS,
            });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });
    });

    describe('patchUpdateFormTemplate', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewTemplate: PerformanceReviewFormTemplate;
        let params: { withCalibration?: boolean };

        beforeEach(() => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
            });
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: true,
            });
            params = {};
        });

        it('updates withCalibration to true when subordinate review has score enabled', async () => {
            params = { withCalibration: true };
            repositoryMock.saveFormTemplate.mockImplementationOnce((formTemplate) => Promise.resolve(formTemplate));

            const result = await performanceReviewsService.patchUpdateFormTemplate(reviewCycle, reviewTemplate, params);

            expect(result.formReviewConfig).toMatchObject({ withCalibration: true });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 1);
        });

        it('updates withCalibration to false', async () => {
            const currentConfig = reviewTemplate.formReviewConfig as SubordinateReviewConfig;
            reviewTemplate.formReviewConfig = { ...currentConfig, withCalibration: true };
            params = { withCalibration: false };
            repositoryMock.saveFormTemplate.mockImplementationOnce((formTemplate) => Promise.resolve(formTemplate));

            const result = await performanceReviewsService.patchUpdateFormTemplate(reviewCycle, reviewTemplate, params);

            expect(result.formReviewConfig).toMatchObject({ withCalibration: false });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 1);
        });

        it('returns form template unchanged when withCalibration is undefined', async () => {
            params = {};

            const result = await performanceReviewsService.patchUpdateFormTemplate(reviewCycle, reviewTemplate, params);

            expect(result).toBe(reviewTemplate);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throws when evaluation type is not SUBORDINATE_REVIEW', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: PerformanceReviewEvaluationTypes.PEER_REVIEW,
            });
            params = { withCalibration: true };

            await expect(performanceReviewsService.patchUpdateFormTemplate(reviewCycle, reviewTemplate, params)).rejects.toMatchObject({
                message: 'PATCH form template only supports withCalibration for SUBORDINATE_REVIEW',
                code: ErrorCodes.REVIEW_FORM_TEMPLATE_INVALID_CONFIG,
            });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throws when withCalibration is true but subordinate review has score disabled', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: false,
            });
            params = { withCalibration: true };

            await expect(performanceReviewsService.patchUpdateFormTemplate(reviewCycle, reviewTemplate, params)).rejects.toMatchObject({
                message: 'withCalibration can only be enabled when subordinate review has score enabled',
                code: ErrorCodes.REVIEW_FORM_TEMPLATE_INVALID_CONFIG,
            });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throws when cycle has already started and withCalibration changes', async () => {
            const startedCycle = generatePerformanceReviewCycleWithValues(reviewCycle.id, faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            params = { withCalibration: true };

            await expect(performanceReviewsService.patchUpdateFormTemplate(startedCycle, reviewTemplate, params)).rejects.toMatchObject({
                message: 'Cannot change withCalibration when cycle has already started',
                code: ErrorCodes.REVIEW_CYCLE_INVALID_STATUS,
            });
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });
    });

    describe('findCycleByPk', () => {
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            const fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate);
        });

        it('find cycle successfully without form templates', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            const result = await performanceReviewsService.findCycleByPk(reviewCycle.id, reviewCycle.instanceId, false);

            expect(result).toBe(reviewCycle);
        });

        it('find cycle successfully with form templates', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            const result = await performanceReviewsService.findCycleByPk(reviewCycle.id, reviewCycle.instanceId, true);

            expect(result).toBe(reviewCycle);
        });

        it('throw not found error if cycle does not exist', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findCycleByPk(reviewCycle.id, reviewCycle.instanceId)).rejects.toThrow(NotFoundError);
        });
    });

    describe('findVisibleCycleByPk', () => {
        let user: User;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            user = generateRandomUser();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            reviewCycle.formTemplates = [generateRandomPerformanceReviewFormTemplate(reviewCycle.id)];
        });

        it('find visible cycle successfully if user is a shared target user', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(
                generateRandomPerformanceReviewTargetUser(
                    reviewCycle.id,
                    user.id,
                    fakePositiveInteger(),
                    faker.helpers.arrayElement(isFormSharedStatuses),
                ),
            );
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockResolvedValueOnce([]);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);
            const userTargetUser = result.userTargetUser as PerformanceReviewTargetUser;

            expect(result).toBe(reviewCycle);
            expect(result.userTargetUser).not.toBeNull();
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.userTargetUser!.reviewedFormReviews).not.toBeNull();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(userTargetUser.userId).toBe(user.id);
            expect(isFormSharedStatuses.includes(userTargetUser.shareStatus)).toBeTrue();

            checkCycleFormReviewsHaveUsers(result.reviewedFormReviews ?? []);
            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews ?? []);

            checkMockCallsCount(repositoryMock.getCycleById, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewerId, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 1);
        });

        it('find visible cycle successfully if user is a shared target user and has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            repositoryMock.getTargetUser.mockResolvedValueOnce(
                generateRandomPerformanceReviewTargetUser(
                    reviewCycle.id,
                    user.id,
                    fakePositiveInteger(),
                    faker.helpers.arrayElement(isFormSharedStatuses),
                ),
            );
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockResolvedValueOnce([]);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);
            const userTargetUser = result.userTargetUser as PerformanceReviewTargetUser;

            expect(result).toBe(reviewCycle);
            expect(result.userTargetUser).not.toBeNull();
            expect(result.isResponsible).not.toBeNull();
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.userTargetUser!.reviewedFormReviews).not.toBeNull();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(userTargetUser.userId).toBe(user.id);
            expect(isFormSharedStatuses.includes(userTargetUser.shareStatus)).toBeTrue();

            checkCycleFormReviewsHaveUsers(result.reviewedFormReviews ?? []);
            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews ?? []);

            checkMockCallsCount(repositoryMock.getCycleById, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.isUserResponsibleOfCycle, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewerId, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 1);
        });

        it('find visible cycle successfully if user is responsible and has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(true);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockResolvedValueOnce([]);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);

            expect(result).toBe(reviewCycle);
            expect(result.userTargetUser).toBeNull();
            expect(result.isResponsible).not.toBeNull();
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(result.isResponsible).toBeTrue();

            checkCycleFormReviewsHaveUsers(result.reviewedFormReviews ?? []);
            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews ?? []);

            checkMockCallsCount(repositoryMock.getCycleById, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.isUserResponsibleOfCycle, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewerId, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 0);
        });

        it('find visible cycle successfully if user is a non-shared target user in subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            reviewCycle.reviewedUsersMode = PerformanceReviewCycleReviewedUsersMode.ALL_USERS;
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(
                generateRandomPerformanceReviewTargetUser(
                    reviewCycle.id,
                    user.id,
                    fakePositiveInteger(),
                    faker.helpers.arrayElement([PerformanceReviewFormShareStatus.NOT_SHARED]),
                ),
            );
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockImplementationOnce((cycleId, reviewerId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, { reviewerId });
                formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
                return Promise.resolve([formReview]);
            });
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);
            usersPortMock.getSubordinateIds.mockResolvedValueOnce([fakePositiveInteger()]);

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);

            expect(result).toBe(reviewCycle);
            expect(result.userTargetUser).not.toBeNull();
            expect(result.isResponsible).not.toBeNull();
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.userTargetUser!.reviewedFormReviews).toBeEmpty();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(result.isResponsible).toBeTrue();
            expect(result.reviewedFormReviews?.length).toBe(0);

            checkCycleFormReviewsHaveUsers(result.reviewedFormReviews ?? []);
            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews ?? []);

            checkMockCallsCount(repositoryMock.getCycleById, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(usersPortMock.getSubordinateIds, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 1);
        });

        it('find visible cycle successfully if user is reviewer', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockImplementationOnce((cycleId, reviewerId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewerId,
                    type: faker.helpers.arrayElement(
                        Object.values(PerformanceReviewEvaluationTypes).filter(
                            (t) => t !== PerformanceReviewEvaluationTypes.CALIBRATION_REVIEW,
                        ),
                    ),
                });
                formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
                return Promise.resolve([formReview]);
            });
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);

            expect(result).toBe(reviewCycle);
            expect(result.userTargetUser).toBeNull();
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(result.reviewerFormReviews?.length).toBeGreaterThan(0);

            checkCycleFormReviewsHaveUsers(result.reviewedFormReviews ?? []);
            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews ?? []);

            checkMockCallsCount(repositoryMock.getCycleById, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewerId, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 0);
        });

        it('find visible cycle successfully if user is all', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(
                generateRandomPerformanceReviewTargetUser(
                    reviewCycle.id,
                    user.id,
                    fakePositiveInteger(),
                    faker.helpers.arrayElement(isFormSharedStatuses),
                ),
            );
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(true);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockImplementationOnce((cycleId, reviewerId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewerId,
                    type: faker.helpers.arrayElement(
                        Object.values(PerformanceReviewEvaluationTypes).filter(
                            (t) => t !== PerformanceReviewEvaluationTypes.CALIBRATION_REVIEW,
                        ),
                    ),
                });
                formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
                return Promise.resolve([formReview]);
            });
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockImplementationOnce((cycleId, reviewedId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, { reviewedId });
                formReview.reviewer = generateRandomPerformanceReviewUser(formReview.reviewerId!);
                return Promise.resolve([formReview]);
            });

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);
            const userTargetUser = result.userTargetUser as PerformanceReviewTargetUser;

            expect(result).toBe(reviewCycle);
            expect(result.userTargetUser).not.toBeNull();
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.userTargetUser!.reviewedFormReviews).not.toBeNull();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(userTargetUser.userId).toBe(user.id);
            expect(isFormSharedStatuses.includes(userTargetUser.shareStatus)).toBeTrue();
            expect(result.reviewerFormReviews?.length).toBeGreaterThan(0);

            checkCycleFormReviewsHaveUsers(result.reviewedFormReviews ?? []);
            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews ?? []);

            checkMockCallsCount(repositoryMock.getCycleById, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewerId, 1);
            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 1);
        });

        it('check isResponsible of cycle is null when has not have subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: Object.values(PerformanceReviewEvaluationTypes).filter(
                    (type) => type !== PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                ),
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockImplementationOnce((cycleId, reviewerId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewerId,
                    type: faker.helpers.arrayElement(
                        Object.values(PerformanceReviewEvaluationTypes).filter(
                            (t) => t !== PerformanceReviewEvaluationTypes.CALIBRATION_REVIEW,
                        ),
                    ),
                });
                formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
                return Promise.resolve([formReview]);
            });
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);

            expect(result).toBe(reviewCycle);
            expect(result.isResponsible).toBeNull();
        });

        it('check isResponsible of cycle if user is all and has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(
                generateRandomPerformanceReviewTargetUser(
                    reviewCycle.id,
                    user.id,
                    fakePositiveInteger(),
                    faker.helpers.arrayElement(isFormSharedStatuses),
                ),
            );
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(true);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockImplementationOnce((cycleId, reviewerId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, { reviewerId });
                formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
                return Promise.resolve([formReview]);
            });
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockImplementationOnce((cycleId, reviewedId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, { reviewedId });
                formReview.reviewer = generateRandomPerformanceReviewUser(formReview.reviewerId!);
                return Promise.resolve([formReview]);
            });

            const result = await performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user);

            expect(result.isResponsible).not.toBeNull();
            expect(result.isResponsible).toBeTrue();

            checkMockCallsCount(repositoryMock.isUserResponsibleOfCycle, 1);
        });

        it('throw not found error if cycle does not exist', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user)).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if cycle has not started', async () => {
            reviewCycle.status = faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user)).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if user is target user and the cycle has share disabled', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(
                generateRandomPerformanceReviewTargetUser(
                    reviewCycle.id,
                    user.id,
                    fakePositiveInteger(),
                    faker.helpers.arrayElement([PerformanceReviewFormShareStatus.SHARE_DISABLED]),
                ),
            );
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockResolvedValueOnce([]);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            await expect(performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user)).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if user is not related to cycle', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockResolvedValueOnce([]);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            await expect(performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user)).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if user is only responsible and not has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: Object.values(PerformanceReviewEvaluationTypes).filter(
                    (type) => type !== PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                ),
                instanceId: user.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(true);
            repositoryMock.getFormReviewsOfCycleForReviewerId.mockResolvedValueOnce([]);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);

            await expect(performanceReviewsService.findVisibleCycleByPk(reviewCycle.id, reviewCycle.instanceId, user)).rejects.toThrow(NotFoundError);
        });
    });

    describe('getScoreDisplayDataForReviews', () => {
        it('uses review.score when no calibrationReviewByReviewId is passed', () => {
            const cycleId = fakePositiveInteger();
            const reviewId = fakePositiveInteger();
            const review = generatePerformanceReviewFormReviewWithValues(reviewId, cycleId, {
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                score: 80,
                coreFormAnswerId: fakePositiveInteger(),
            });
            const template = generatePerformanceReviewFormTemplateWithValues(cycleId, { withScore: true });
            const formTemplatesMap = new Map([[PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW, template]]);

            const result = performanceReviewsService.getScoreDisplayDataForReviews([review], formTemplatesMap);

            expect(result.get(reviewId)?.score).toBe(80);
        });

        it('uses calibration review score for SUBORDINATE_REVIEW when calibrationReviewByReviewId has an entry', () => {
            const cycleId = fakePositiveInteger();
            const reviewId = fakePositiveInteger();
            const review = generatePerformanceReviewFormReviewWithValues(reviewId, cycleId, {
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                score: 80,
                coreFormAnswerId: fakePositiveInteger(),
            });
            const calibrationReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                score: 50,
                coreFormAnswerId: fakePositiveInteger(),
            });
            const template = generatePerformanceReviewFormTemplateWithValues(cycleId, { withScore: true });
            const formTemplatesMap = new Map([[PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW, template]]);
            const calibrationReviewByReviewId = new Map([[reviewId, calibrationReview]]);

            const result = performanceReviewsService.getScoreDisplayDataForReviews([review], formTemplatesMap, calibrationReviewByReviewId);

            expect(result.get(reviewId)?.score).toBe(50);
        });

        it('falls back to review.score when calibration review score is null (COALESCE semantics)', () => {
            const cycleId = fakePositiveInteger();
            const reviewId = fakePositiveInteger();
            const review = generatePerformanceReviewFormReviewWithValues(reviewId, cycleId, {
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                score: 80,
                coreFormAnswerId: fakePositiveInteger(),
            });
            const calibrationReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                coreFormAnswerId: null,
            });
            const template = generatePerformanceReviewFormTemplateWithValues(cycleId, { withScore: true });
            const formTemplatesMap = new Map([[PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW, template]]);
            const calibrationReviewByReviewId = new Map([[reviewId, calibrationReview]]);

            const result = performanceReviewsService.getScoreDisplayDataForReviews([review], formTemplatesMap, calibrationReviewByReviewId);

            expect(result.get(reviewId)?.score).toBe(80);
        });

        it('ignores calibrationReviewByReviewId entry when review type is not SUBORDINATE_REVIEW', () => {
            const cycleId = fakePositiveInteger();
            const reviewId = fakePositiveInteger();
            const review = generatePerformanceReviewFormReviewWithValues(reviewId, cycleId, {
                type: PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                score: 80,
                coreFormAnswerId: fakePositiveInteger(),
            });
            const calibrationReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                score: 50,
                coreFormAnswerId: fakePositiveInteger(),
            });
            const template = generatePerformanceReviewFormTemplateWithValues(cycleId, { withScore: true });
            const formTemplatesMap = new Map([[PerformanceReviewEvaluationTypes.BOSS_REVIEW, template]]);
            const calibrationReviewByReviewId = new Map([[reviewId, calibrationReview]]);

            const result = performanceReviewsService.getScoreDisplayDataForReviews([review], formTemplatesMap, calibrationReviewByReviewId);

            expect(result.get(reviewId)?.score).toBe(80);
        });
    });

    describe('validateIfUserIsResponsibleOfCycle', () => {
        let cycleId: number;
        let userId: number;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            userId = fakePositiveInteger();
        });

        it('do nothing if user is responsible of cycle', async () => {
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(true);

            await expect(performanceReviewsService.validateIfUserIsResponsibleOfCycle(cycleId, userId)).resolves.not.toThrow();
        });

        it('throw forbidden error if user is not responsible of cycle', async () => {
            repositoryMock.isUserResponsibleOfCycle.mockResolvedValueOnce(false);

            await expect(performanceReviewsService.validateIfUserIsResponsibleOfCycle(cycleId, userId)).rejects.toThrow(ForbiddenError);
        });
    });

    describe('listCycles', () => {
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            const fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate);
        });

        it('successful if no cycles exist', async () => {
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [], totalCount: 0 });

            const result = await performanceReviewsService.listCycles(reviewCycle.instanceId, {});

            expect(result.totalCount).toBe(0);
            expect(result.reviewCycles).toHaveLength(0);
        });

        it('successful if one cycle exists', async () => {
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [reviewCycle], totalCount: 1 });

            const result = await performanceReviewsService.listCycles(reviewCycle.instanceId, {});

            expect(result.totalCount).toBe(1);
            expect(result.reviewCycles).toHaveLength(1);
            expect(result.reviewCycles[0]).toBe(reviewCycle);
        });
    });

    describe('listVisibleCycles', () => {
        let userId: number;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            userId = fakePositiveInteger();
            const fakeId = fakePositiveInteger();
            const fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate);
        });

        it('successful if no cycles exist for user', async () => {
            repositoryMock.getUserCycleIds.mockResolvedValueOnce([]);
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [], totalCount: 0 });
            repositoryMock.getCyclesWithPendingReviewsForReviewer.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.listVisibleCycles(userId, reviewCycle.instanceId, {});

            expect(result.totalCount).toBe(0);
            expect(result.reviewCycles).toHaveLength(0);
        });

        it('successful if one cycle exists for user but it is not listed', async () => {
            repositoryMock.getUserCycleIds.mockResolvedValueOnce([reviewCycle.id]);
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [], totalCount: 0 });
            repositoryMock.getCyclesWithPendingReviewsForReviewer.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.listVisibleCycles(userId, reviewCycle.instanceId, {});

            expect(result.totalCount).toBe(0);
            expect(result.reviewCycles).toHaveLength(0);
        });

        it('successful if one cycle exists for responsible', async () => {
            const cyclesWithPendingReviewsForReviewer = reviewCycle.hasPendingReviews ? [reviewCycle.id] : [];
            repositoryMock.getUserCycleIds.mockResolvedValueOnce([]);
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [reviewCycle], totalCount: 1 });
            repositoryMock.getCyclesWithPendingReviewsForReviewer.mockResolvedValueOnce(cyclesWithPendingReviewsForReviewer);

            const result = await performanceReviewsService.listVisibleCycles(userId, reviewCycle.instanceId, {}, true);

            expect(result.totalCount).toBe(1);
            expect(result.reviewCycles).toHaveLength(1);
            expect(result.reviewCycles[0]).toBe(reviewCycle);
        });

        it('successful if one cycle exists for user', async () => {
            const cyclesWithPendingReviewsForReviewer = reviewCycle.hasPendingReviews ? [reviewCycle.id] : [];
            repositoryMock.getUserCycleIds.mockResolvedValueOnce([reviewCycle.id]);
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [reviewCycle], totalCount: 1 });
            repositoryMock.getCyclesWithPendingReviewsForReviewer.mockResolvedValueOnce(cyclesWithPendingReviewsForReviewer);

            const result = await performanceReviewsService.listCycles(reviewCycle.instanceId, {});

            expect(result.totalCount).toBe(1);
            expect(result.reviewCycles).toHaveLength(1);
            expect(result.reviewCycles[0]).toBe(reviewCycle);
        });

        it('successful if one cycle exists for user and options are provided', async () => {
            const cyclesWithPendingReviewsForReviewer = reviewCycle.hasPendingReviews ? [reviewCycle.id] : [];
            repositoryMock.getUserCycleIds.mockResolvedValueOnce([reviewCycle.id]);
            repositoryMock.getAllCycles.mockResolvedValueOnce({ reviewCycles: [reviewCycle], totalCount: 1 });
            repositoryMock.getCyclesWithPendingReviewsForReviewer.mockResolvedValueOnce(cyclesWithPendingReviewsForReviewer);

            const result = await performanceReviewsService.listCycles(reviewCycle.instanceId, {
                pagination: {
                    limit: fakePositiveInteger(),
                    offset: fakePositiveInteger(),
                },
                q: reviewCycle.name,
                sortingOptions: [['name', 'desc']],
            });

            expect(result.totalCount).toBe(1);
            expect(result.reviewCycles).toHaveLength(1);
            expect(result.reviewCycles[0]).toBe(reviewCycle);
        });
    });

    describe('deleteCycle', () => {
        let fakeId: number;
        let fakeDate: Date;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
        });

        it('delete cycle that has not started', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
            });
            reviewCycle.formTemplates = [];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.deleteFormReviews.mockResolvedValueOnce();
            repositoryMock.deleteCycle.mockResolvedValueOnce();

            await expect(performanceReviewsService.deleteCycle(reviewCycle)).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteFormTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteFormReviews, 1);
            checkMockCallsCount(repositoryMock.deleteCycle, 1);
            checkMockCallsCount(repositoryMock.stopUsingTemplates, 0);
            checkMockCallsCount(repositoryMock.countTemplateUsages, 0);
            checkMockCallsCount(formTemplatesPortMock.stopUsingFormTemplates, 0);
        });

        it('delete cycle that has already started without form templates', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.deleteFormReviews.mockResolvedValueOnce();
            repositoryMock.getTemplatesByIds.mockResolvedValue([]);
            repositoryMock.deleteCycle.mockResolvedValueOnce();
            repositoryMock.countTemplateUsages.mockResolvedValueOnce({});

            await expect(performanceReviewsService.deleteCycle(reviewCycle)).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteFormTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteFormReviews, 1);
            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(repositoryMock.stopUsingTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteCycle, 1);
            checkMockCallsCount(repositoryMock.countTemplateUsages, 1);
            checkMockCallsCount(formTemplatesPortMock.stopUsingFormTemplates, 1);
        });

        it('delete cycle that has already started with still used form templates', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [generateRandomPerformanceReviewFormTemplate(reviewCycle.id)];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.deleteFormReviews.mockResolvedValueOnce();
            repositoryMock.getTemplatesByIds.mockResolvedValue([generateRandomTemplate()]);
            repositoryMock.deleteCycle.mockResolvedValueOnce();
            repositoryMock.countTemplateUsages.mockResolvedValueOnce({ [reviewCycle.formTemplates[0].templateId!]: faker.number.int({ min: 1 }) });

            await expect(performanceReviewsService.deleteCycle(reviewCycle)).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteFormTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteFormReviews, 1);
            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(repositoryMock.stopUsingTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteCycle, 1);
            checkMockCallsCount(repositoryMock.countTemplateUsages, 1);
            checkMockCallsCount(formTemplatesPortMock.stopUsingFormTemplates, 1);
        });

        it('delete cycle that has already started with unused form templates', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [generateRandomPerformanceReviewFormTemplate(reviewCycle.id)];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.deleteFormReviews.mockResolvedValueOnce();
            repositoryMock.getTemplatesByIds.mockResolvedValue([generateRandomTemplate()]);
            repositoryMock.deleteCycle.mockResolvedValueOnce();
            repositoryMock.countTemplateUsages.mockResolvedValueOnce({ [reviewCycle.formTemplates[0].templateId!]: 0 });

            await expect(performanceReviewsService.deleteCycle(reviewCycle)).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteFormTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteFormReviews, 1);
            checkMockCallsCount(repositoryMock.stopUsingTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteCycle, 1);
            checkMockCallsCount(repositoryMock.countTemplateUsages, 1);
            checkMockCallsCount(formTemplatesPortMock.stopUsingFormTemplates, 1);
        });

        it('delete cycle that has already started with missing form template usages', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [generateRandomPerformanceReviewFormTemplate(reviewCycle.id)];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.deleteFormReviews.mockResolvedValueOnce();
            repositoryMock.getTemplatesByIds.mockResolvedValue([generateRandomTemplate()]);
            repositoryMock.deleteCycle.mockResolvedValueOnce();
            repositoryMock.countTemplateUsages.mockResolvedValueOnce({});

            await expect(performanceReviewsService.deleteCycle(reviewCycle)).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.deleteTargetUsers, 1);
            checkMockCallsCount(repositoryMock.deleteFormTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteFormReviews, 1);
            checkMockCallsCount(repositoryMock.stopUsingTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteCycle, 1);
            checkMockCallsCount(repositoryMock.countTemplateUsages, 1);
            checkMockCallsCount(formTemplatesPortMock.stopUsingFormTemplates, 1);
        });

        it('throw illegal state error if cycle does not have form templates loaded', async () => {
            reviewCycle.formTemplates = null;

            await expect(performanceReviewsService.deleteCycle(reviewCycle)).rejects.toThrow(IllegalStateError);
        });
    });

    describe('addTemplateDetailsToCycle', () => {
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            const fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate);
        });

        it('do nothing if cycle has no form templates', async () => {
            await performanceReviewsService.addTemplateDetailsToCycle(reviewCycle);

            expect(reviewCycle.formTemplates).toBeNull();

            checkMockCallsCount(formTemplatesPortMock.findFormTemplatesById, 0);
        });

        it('successfully set form template details', async () => {
            const formTemplatesCount = faker.number.int({ max: 3, min: 1 });
            reviewCycle.formTemplates = Array(formTemplatesCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewFormTemplate(reviewCycle.id));
            const formTemplates = reviewCycle.formTemplates.map((ft) => generateRandomFormTemplate(ft.template!.formTemplateId!));
            const formTemplatesById = formTemplates.reduce((records, formTemplate) => {
                records[formTemplate.id as number] = formTemplate;
                return records;
            }, {} as { [formTemplateid: number]: FormTemplate });
            repositoryMock.getTemplatesByIds.mockResolvedValue(reviewCycle.formTemplates.map((ft) => ft.template!));
            formTemplatesPortMock.findFormTemplatesById.mockResolvedValue(formTemplatesById);
            formTemplatesPortMock.getRatingQuestionsOfFormTemplates.mockResolvedValue(new Map());
            await performanceReviewsService.addTemplateDetailsToCycle(reviewCycle);

            expect(reviewCycle.formTemplates).toHaveLength(formTemplatesCount);

            reviewCycle.formTemplates.forEach((reviewTemplate) => {
                expect(reviewTemplate.template).not.toBeNull();
                expect(reviewTemplate.template).toBeInstanceOf(PerformanceReviewTemplate);
                expect(reviewTemplate.template!.formTemplate).not.toBeNull();
                expect(reviewTemplate.template?.id).toBe(reviewTemplate.templateId);
            });

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.findFormTemplatesById, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplates, 1);
        });

        it('successfully set form template details if templates do not exist', async () => {
            const formTemplatesCount = faker.number.int({ max: 3, min: 1 });
            reviewCycle.formTemplates = Array(formTemplatesCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewFormTemplate(reviewCycle.id));
            repositoryMock.getTemplatesByIds.mockResolvedValue([]);
            formTemplatesPortMock.findFormTemplatesById.mockResolvedValue([]);

            await performanceReviewsService.addTemplateDetailsToCycle(reviewCycle);

            expect(reviewCycle.formTemplates).toHaveLength(formTemplatesCount);

            reviewCycle.formTemplates.forEach((reviewTemplate) => {
                expect(reviewTemplate.template).toBeNull();
            });

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.findFormTemplatesById, 1);
        });
    });

    describe('addNeedsChangesToCycle', () => {
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            const fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, { status: PerformanceReviewCycleStatus.DRAFT });
        });

        it('should set needs changes to false if withScore is false and templates are ok', async () => {
            const reviewFormTemplate = generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycle.id, reviewCycle.instanceId, {
                withScore: false,
            });
            reviewCycle.formTemplates = [reviewFormTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBe(false);

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true if cycle templates have no form template details loaded', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, { templateId: null });
            reviewCycle.formTemplates = [reviewFormTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBe(true);

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true when template has no formTemplate, no goalsProgressMode and no competenciesQuestionTemplateId', async () => {
            const reviewFormTemplate = generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycle.id, reviewCycle.instanceId, {
                withScore: false,
            });
            reviewFormTemplate.template!.formTemplate = null;
            reviewFormTemplate.template!.goalsProgressMode = null;
            reviewFormTemplate.template!.competenciesQuestionTemplateId = null;
            reviewCycle.formTemplates = [reviewFormTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBe(true);
        });

        it('should set needs changes to false if withScore is true and weights are correct', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            reviewFormTemplate.scoreConfig!.goalsWeight = 0;
            reviewFormTemplate.scoreConfig!.formWeight = 100;
            const referenceIds = getFormDraftContentReferenceIds(reviewFormTemplate.template!.formTemplate!.draft!.content);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(referenceIds);
            reviewCycle.formTemplates = [reviewFormTemplate];
            const questionTemplates = referenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, reviewCycle.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            reviewFormTemplate.ratingQuestionTemplates = questionTemplates;
            repositoryMock.getTemplateById.mockResolvedValueOnce(
                generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL),
            );

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeFalse();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to false if withScore is true and weights are correct with multiple templates', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            reviewFormTemplate.scoreConfig!.goalsWeight = 0;
            reviewFormTemplate.scoreConfig!.formWeight = 100;
            const referenceIds = getFormDraftContentReferenceIds(reviewFormTemplate.template!.formTemplate!.draft!.content);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(referenceIds);
            const otherTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            otherTemplate.scoreConfig!.goalsWeight = 0;
            otherTemplate.scoreConfig!.formWeight = 100;
            const otherReferenceIds = getFormDraftContentReferenceIds(otherTemplate.template!.formTemplate!.draft!.content);
            otherTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(otherReferenceIds);
            reviewCycle.formTemplates = [reviewFormTemplate, otherTemplate];
            const questionTemplates = referenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, reviewCycle.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            const otherQuestionTemplates = otherReferenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, reviewCycle.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            repositoryMock.getTemplateById.mockResolvedValueOnce(
                generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL),
            );
            repositoryMock.getTemplateById.mockResolvedValueOnce(
                generateRandomTemplate(otherTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL),
            );
            reviewFormTemplate.ratingQuestionTemplates = questionTemplates;
            otherTemplate.ratingQuestionTemplates = otherQuestionTemplates;

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeFalse();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true if withScore is true and goals or form weights are invalid', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            //sum exceeds 100
            reviewFormTemplate.scoreConfig!.goalsWeight = 70;
            reviewFormTemplate.scoreConfig!.formWeight = 70;
            const referenceIds = getFormDraftContentReferenceIds(reviewFormTemplate.template!.formTemplate!.draft!.content);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(referenceIds);
            reviewCycle.formTemplates = [reviewFormTemplate];
            const questionTemplates = referenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, reviewCycle.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            reviewFormTemplate.ratingQuestionTemplates = questionTemplates;
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeTrue();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true if withScore is true and weights are empty', async () => {
            const reviewFormTemplate = generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = {};
            reviewFormTemplate.ratingQuestionTemplates = [];
            reviewCycle.formTemplates = [reviewFormTemplate];

            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));
            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeTrue();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true if withScore is true and weights point to non-rating or non-existing questions', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            const referenceIds = getFormDraftContentReferenceIds(reviewFormTemplate.template!.formTemplate!.draft!.content);
            const maxReferenceId = referenceIds.reduce((max, curr) => (curr > max ? curr : max), 0);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(referenceIds);
            reviewCycle.formTemplates = [reviewFormTemplate];
            const missingQuestionTemplate = generateQuestionTemplateWithValues(
                fakePositiveInteger({ min: maxReferenceId + 1 }),
                reviewCycle.instanceId,
                {
                    type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                },
            );
            reviewFormTemplate.ratingQuestionTemplates = [missingQuestionTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeTrue();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true if withScore is true and weights have missing rating questions', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            const referenceIds = getFormDraftContentReferenceIds(reviewFormTemplate.template!.formTemplate!.draft!.content);
            const maxReferenceId = referenceIds.reduce((max, curr) => (curr > max ? curr : max), 0);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(referenceIds);
            reviewCycle.formTemplates = [reviewFormTemplate];
            const questionTemplates = referenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, reviewCycle.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            const missingQuestionTemplate = generateQuestionTemplateWithValues(
                fakePositiveInteger({ min: maxReferenceId + 1 }),
                reviewCycle.instanceId,
                {
                    type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                },
            );
            reviewFormTemplate.ratingQuestionTemplates = questionTemplates.concat(missingQuestionTemplate);
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeTrue();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true if withScore is true and weights add up more than 100', async () => {
            const reviewFormTemplate = generatePerformanceReviewFormTemplateWithDraft(reviewCycle.id, reviewCycle.instanceId, {
                withScore: true,
            });
            const referenceIds = getFormDraftContentReferenceIds(reviewFormTemplate.template!.formTemplate!.draft!.content);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights = generateQuestionWeightsFromIds(referenceIds);
            reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights[referenceIds[0]] = reviewFormTemplate.scoreConfig!.formTemplateQuestionWeights[referenceIds[0]] + 1;
            reviewCycle.formTemplates = [reviewFormTemplate];
            const questionTemplates = referenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, reviewCycle.instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            reviewFormTemplate.ratingQuestionTemplates = questionTemplates;
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));
            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBeTrue();

            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('should set needs changes to true when form template has score but no scoreConfig', async () => {
            const reviewFormTemplate = generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycle.id, reviewCycle.instanceId, {
                withScore: false,
            });
            reviewFormTemplate.scoreConfig = null!;
            const ratingQuestion = generateQuestionTemplateWithValues(fakePositiveInteger(), reviewCycle.instanceId, {
                type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
            });
            reviewFormTemplate.ratingQuestionTemplates = [ratingQuestion];
            reviewCycle.formTemplates = [reviewFormTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBe(true);
        });

        it('should set needs changes to true when template has goalsProgressMode set but cycle has no goalCycleId', async () => {
            reviewCycle.goalCycleId = null;
            const reviewFormTemplate = generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycle.id, reviewCycle.instanceId, {
                withScore: false,
            });
            reviewFormTemplate.template!.goalsProgressMode = PerformanceReviewTemplateGoalsProgressMode.MANUAL;
            reviewCycle.formTemplates = [reviewFormTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(
                generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL),
            );

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBe(true);
        });

        it('should set needs changes to true when subordinate review has withCalibration true but score is disabled', async () => {
            const reviewFormTemplate = generateRandomPerformanceReviewFormTemplateWithTemplate(reviewCycle.id, reviewCycle.instanceId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: false,
            });
            (reviewFormTemplate.formReviewConfig as SubordinateReviewConfig).withCalibration = true;
            reviewCycle.formTemplates = [reviewFormTemplate];
            repositoryMock.getTemplateById.mockResolvedValueOnce(generateRandomTemplate(reviewFormTemplate.templateId!, reviewCycle.instanceId));

            await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

            expect(reviewCycle.needsChanges).toBe(true);
        });

        it.each([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED])(
            'should set needs changes to false when review cycle is in progress or finished',
            async (status: PerformanceReviewCycleStatusValues) => {
                const fakeId = fakePositiveInteger();
                const fakeDate = faker.date.recent();
                const reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, { status });

                await performanceReviewsService.addNeedsChangesToCycle(reviewCycle);

                expect(reviewCycle.needsChanges).toBeFalse();
            },
        );
    });

    describe('addTargetUsersToCycle', () => {
        let cycleId: number;
        let targetUsers: PerformanceReviewTargetUser[];

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            const targetUsersCount = faker.number.int({ max: 3, min: 1 });
            targetUsers = Array(targetUsersCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewTargetUser(cycleId));
        });

        it('do nothing if cycle targets all users', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });

            await performanceReviewsService.addTargetUsersToCycle(reviewCycle);

            expect(reviewCycle.targetUsers).toBeNull();

            checkMockCallsCount(repositoryMock.getTargetUsers, 0);
        });

        it('successfully set target users', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            repositoryMock.getTargetUsers.mockResolvedValueOnce(targetUsers);

            await performanceReviewsService.addTargetUsersToCycle(reviewCycle);

            expect(reviewCycle.targetUsers).toHaveLength(targetUsers.length);

            reviewCycle.targetUsers?.forEach((targetUser, index) => {
                expect(targetUser.id).toBe(targetUsers[index].id);
                expect(targetUser.createdAt).toBe(targetUsers[index].createdAt);
                expect(targetUser.updatedAt).toBe(targetUsers[index].updatedAt);
                expect(targetUser.deletedAt).toBe(targetUsers[index].deletedAt);
                expect(targetUser.cycleId).toBe(targetUsers[index].cycleId);
                expect(targetUser.userId).toBe(targetUsers[index].userId);
            });

            checkMockCallsCount(repositoryMock.getTargetUsers, 1);
        });
    });

    describe('countFormReviewsOfCycleByTypeAndStatus', () => {
        let numberOfStatuses: number;
        let types: PerformanceReviewEvaluationTypesValues[];
        let statuses: string[];
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            numberOfStatuses = Object.values(PerformanceReviewFormStatus).length;
            types = faker.helpers.arrayElements(Object.values(PerformanceReviewEvaluationTypes), {
                max: Object.values(PerformanceReviewEvaluationTypes).length,
                min: 1,
            });
            statuses = faker.helpers.arrayElements(Object.values(PerformanceReviewFormStatus), {
                max: numberOfStatuses,
                min: 1,
            });
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
            });
            reviewCycle.formTemplates = types.map((evaluationType) =>
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, { evaluationType }),
            );
        });

        it('successful if all statuses are present', async () => {
            repositoryMock.countFormReviewsOfCycleByTypeAndStatus.mockResolvedValueOnce(
                generateRandomReviewCountsForTypesAndStatuses(types, statuses),
            );

            const result = await performanceReviewsService.countFormReviewsOfCycleByTypeAndStatus(reviewCycle);

            expect(Object.keys(result)).toHaveLength(reviewCycle.formTemplates?.length as number);

            (Object.keys(result) as PerformanceReviewEvaluationTypesValues[]).forEach((type) => {
                expect(Object.keys(result[type])).toHaveLength(numberOfStatuses);

                Object.keys(result[type]).forEach((status) => {
                    expect(result[type][status]).toBeNumber();
                });
            });
        });

        it('successful if a status value is missing', async () => {
            statuses = statuses.slice(0, faker.number.int({ max: numberOfStatuses - 1, min: 1 }));
            repositoryMock.countFormReviewsOfCycleByTypeAndStatus.mockResolvedValueOnce(
                generateRandomReviewCountsForTypesAndStatuses(types, statuses),
            );

            const result = await performanceReviewsService.countFormReviewsOfCycleByTypeAndStatus(reviewCycle);

            expect(Object.keys(result)).toHaveLength(reviewCycle.formTemplates?.length as number);

            (Object.keys(result) as PerformanceReviewEvaluationTypesValues[]).forEach((type) => {
                expect(Object.keys(result[type])).toHaveLength(numberOfStatuses);

                Object.keys(result[type]).forEach((status) => {
                    expect(result[type][status]).toBeNumber();
                });
            });
        });

        it('successful if no reviews exist', async () => {
            repositoryMock.countFormReviewsOfCycleByTypeAndStatus.mockResolvedValueOnce({} as ReviewStatsByTypeAndStatus);

            const result = await performanceReviewsService.countFormReviewsOfCycleByTypeAndStatus(reviewCycle);

            expect(Object.keys(result)).toHaveLength(reviewCycle.formTemplates?.length as number);

            (Object.keys(result) as PerformanceReviewEvaluationTypesValues[]).forEach((type) => {
                expect(Object.keys(result[type])).toHaveLength(numberOfStatuses);

                Object.keys(result[type]).forEach((status) => {
                    expect(result[type][status]).toBe(0);
                });
            });
        });

        it('successful if filtering by responsible', async () => {
            repositoryMock.countFormReviewsOfCycleByTypeAndStatus.mockResolvedValueOnce(
                generateRandomReviewCountsForTypesAndStatuses(types, statuses),
            );

            const result = await performanceReviewsService.countFormReviewsOfCycleByTypeAndStatus(reviewCycle, fakePositiveInteger());

            expect(Object.keys(result)).toHaveLength(reviewCycle.formTemplates?.length as number);

            (Object.keys(result) as PerformanceReviewEvaluationTypesValues[]).forEach((type) => {
                expect(Object.keys(result[type])).toHaveLength(numberOfStatuses);

                Object.keys(result[type]).forEach((status) => {
                    expect(result[type][status]).toBeNumber();
                });
            });
        });

        it('throw illegal state error if review cycle has no form templates loaded', async () => {
            reviewCycle.formTemplates = null;

            await expect(performanceReviewsService.countFormReviewsOfCycleByTypeAndStatus(reviewCycle)).rejects.toThrow(IllegalStateError);
        });
    });

    describe('countFormReviewsByShareStatus', () => {
        let numberOfStatuses: number;
        let cycleId: number;
        let statuses: string[];

        beforeEach(() => {
            numberOfStatuses = Object.values(PerformanceReviewFormShareStatus).length;
            cycleId = fakePositiveInteger();
        });

        it('successful if all statuses are present', async () => {
            statuses = Object.values(PerformanceReviewFormShareStatus);
            repositoryMock.countFormReviewsByShareStatus.mockResolvedValueOnce(generateRandomReviewCountsForShareStatuses(statuses));

            const result = await performanceReviewsService.countFormReviewsByShareStatus(cycleId);

            expect(Object.keys(result)).toHaveLength(numberOfStatuses);

            Object.keys(result).forEach((shareStatus) => {
                expect(result[shareStatus]).toBeNumber();
            });
        });

        it('successful if a status value is missing', async () => {
            statuses = faker.helpers.arrayElements(Object.values(PerformanceReviewFormShareStatus), {
                max: numberOfStatuses - 1,
                min: 1,
            });
            repositoryMock.countFormReviewsByShareStatus.mockResolvedValueOnce(generateRandomReviewCountsForShareStatuses(statuses));

            const result = await performanceReviewsService.countFormReviewsByShareStatus(cycleId);

            expect(Object.keys(result)).toHaveLength(numberOfStatuses);

            Object.keys(result).forEach((shareStatus) => {
                expect(result[shareStatus]).toBeNumber();
            });
        });

        it('successful if no statuses are present', async () => {
            repositoryMock.countFormReviewsByShareStatus.mockResolvedValueOnce({});

            const result = await performanceReviewsService.countFormReviewsByShareStatus(cycleId);

            expect(Object.keys(result)).toHaveLength(numberOfStatuses);

            Object.keys(result).forEach((shareStatus) => {
                expect(result[shareStatus]).toBeNumber();
            });
        });
    });

    describe('countTargetUsersByShareStatus', () => {
        let numberOfStatuses: number;
        let cycleId: number;
        let statuses: string[];

        beforeEach(() => {
            numberOfStatuses = Object.values(PerformanceReviewFormShareStatus).length;
            cycleId = fakePositiveInteger();
        });

        it('successful if all statuses are present', async () => {
            statuses = Object.values(PerformanceReviewFormShareStatus);
            repositoryMock.countTargetUsersByShareStatus.mockResolvedValueOnce(generateRandomReviewCountsForShareStatuses(statuses));

            const result = await performanceReviewsService.countTargetUsersByShareStatus(cycleId);

            expect(Object.keys(result)).toHaveLength(numberOfStatuses);

            Object.keys(result).forEach((shareStatus) => {
                expect(result[shareStatus]).toBeNumber();
            });
        });

        it('successful if a status value is missing', async () => {
            statuses = faker.helpers.arrayElements(Object.values(PerformanceReviewFormShareStatus), {
                max: numberOfStatuses - 1,
                min: 1,
            });
            repositoryMock.countTargetUsersByShareStatus.mockResolvedValueOnce(generateRandomReviewCountsForShareStatuses(statuses));

            const result = await performanceReviewsService.countTargetUsersByShareStatus(cycleId);

            expect(Object.keys(result)).toHaveLength(numberOfStatuses);

            Object.keys(result).forEach((shareStatus) => {
                expect(result[shareStatus]).toBeNumber();
            });
        });

        it('successful if no statuses are present', async () => {
            repositoryMock.countTargetUsersByShareStatus.mockResolvedValueOnce({});

            const result = await performanceReviewsService.countTargetUsersByShareStatus(cycleId);

            expect(Object.keys(result)).toHaveLength(numberOfStatuses);

            Object.keys(result).forEach((shareStatus) => {
                expect(result[shareStatus]).toBeNumber();
            });
        });
    });

    describe('listCycleReviewerUsers', () => {
        let cycleId: number;
        let reviewUser: PerformanceReviewUserWithStats;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewUser = addStatsToPerformanceReviewUser(generateRandomPerformanceReviewUser(fakePositiveInteger()));
        });

        it('successful if no reviewer users exist', async () => {
            repositoryMock.getReviewerUsersOfCycle.mockResolvedValueOnce({ reviewUsers: [], totalCount: 0 });

            const result = await performanceReviewsService.listCycleReviewerUsers(cycleId, {});

            expect(result.totalCount).toBe(0);
            expect(result.reviewUsers).toHaveLength(0);

            checkMockCallsCount(repositoryMock.getReviewerUsersOfCycle, 1);
        });

        it('successful if one reviewer user exists', async () => {
            repositoryMock.getReviewerUsersOfCycle.mockResolvedValueOnce({ reviewUsers: [reviewUser], totalCount: 1 });

            const result = await performanceReviewsService.listCycleReviewerUsers(cycleId, {});

            expect(result.totalCount).toBe(1);
            expect(result.reviewUsers).toHaveLength(1);
            expect(result.reviewUsers[0]).toBe(reviewUser);

            checkMockCallsCount(repositoryMock.getReviewerUsersOfCycle, 1);
        });
    });

    describe('listCycleExternalReviewerUsers', () => {
        let cycleId: number;
        let reviewUser: PerformanceReviewUserWithStats;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewUser = addStatsToPerformanceReviewUser(generateRandomPerformanceReviewUser(fakePositiveInteger()));
        });

        it('successful if no reviewer users exist', async () => {
            repositoryMock.getExternalReviewerUsersOfCycle.mockResolvedValueOnce({ reviewUsers: [], totalCount: 0 });

            const result = await performanceReviewsService.listCycleExternalReviewerUsers(cycleId, {});

            expect(result.totalCount).toBe(0);
            expect(result.reviewUsers).toHaveLength(0);

            checkMockCallsCount(repositoryMock.getExternalReviewerUsersOfCycle, 1);
        });

        it('successful if one reviewer user exists', async () => {
            repositoryMock.getExternalReviewerUsersOfCycle.mockResolvedValueOnce({ reviewUsers: [reviewUser], totalCount: 1 });

            const result = await performanceReviewsService.listCycleExternalReviewerUsers(cycleId, {});

            expect(result.totalCount).toBe(1);
            expect(result.reviewUsers).toHaveLength(1);
            expect(result.reviewUsers[0]).toBe(reviewUser);

            checkMockCallsCount(repositoryMock.getExternalReviewerUsersOfCycle, 1);
        });
    });

    describe('listCycleReviewedUsers', () => {
        let cycleId: number;
        let reviewUser: PerformanceReviewUserWithStats;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewUser = addStatsToPerformanceReviewUser(generateRandomPerformanceReviewUser(fakePositiveInteger()));
        });

        it('successful if no reviewed users exist', async () => {
            repositoryMock.getReviewedUsersOfCycle.mockResolvedValueOnce({ reviewUsers: [], totalCount: 0 });

            const result = await performanceReviewsService.listCycleReviewedUsers(cycleId, {});

            expect(result.totalCount).toBe(0);
            expect(result.reviewUsers).toHaveLength(0);

            checkMockCallsCount(repositoryMock.getReviewedUsersOfCycle, 1);
        });

        it('successful if one reviewed user exists', async () => {
            repositoryMock.getReviewedUsersOfCycle.mockResolvedValueOnce({ reviewUsers: [reviewUser], totalCount: 1 });

            const result = await performanceReviewsService.listCycleReviewedUsers(cycleId, {});

            expect(result.totalCount).toBe(1);
            expect(result.reviewUsers).toHaveLength(1);
            expect(result.reviewUsers[0]).toBe(reviewUser);

            checkMockCallsCount(repositoryMock.getReviewedUsersOfCycle, 1);
        });
    });

    describe('listCycleTargetUsers', () => {
        let cycleId: number;
        let cycle: PerformanceReviewCycle;
        let targetUser: PerformanceReviewTargetUser;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            cycle = generateRandomPerformanceReviewCycle({ id: cycleId });
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId);
        });

        it('successful if no reviewed users exist', async () => {
            repositoryMock.getTargetUsersByFilters.mockResolvedValueOnce({ targetUsers: [], totalCount: 0 });

            const result = await performanceReviewsService.listCycleTargetUsers(cycle, {});

            expect(result.totalCount).toBe(0);
            expect(result.targetUsers).toHaveLength(0);

            checkMockCallsCount(repositoryMock.getTargetUsersByFilters, 1);
        });

        it('successful if one reviewed user exists', async () => {
            repositoryMock.getTargetUsersByFilters.mockResolvedValueOnce({ targetUsers: [targetUser], totalCount: 1 });

            const result = await performanceReviewsService.listCycleTargetUsers(cycle, {});

            expect(result.totalCount).toBe(1);
            expect(result.targetUsers).toHaveLength(1);
            expect(result.targetUsers[0]).toBe(targetUser);

            checkMockCallsCount(repositoryMock.getTargetUsersByFilters, 1);
        });

        it('should get reviewed user with stats by evaluation type', async () => {
            repositoryMock.getTargetUsersByFilters.mockResolvedValueOnce({ targetUsers: [targetUser], totalCount: 1 });
            const performanceReviewFormTemplate = generateRandomPerformanceReviewFormTemplate(cycle.id);
            const template = generateRandomTemplate(performanceReviewFormTemplate.templateId!, cycle.instanceId);
            template.goalsQuestionTemplateId = null;
            cycle.formTemplates = [performanceReviewFormTemplate];
            const evaluationType = faker.helpers.arrayElements(Object.values(PerformanceReviewEvaluationTypes));
            const mockStatsByType = generateRandomReviewUserStatsByType(evaluationType);
            repositoryMock.getUsersStatsByEvaluationType.mockResolvedValueOnce({ [targetUser.userId]: mockStatsByType });
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplatesById.mockResolvedValueOnce({ [template.formTemplateId!]: template.formTemplate! });
            formTemplatesPortMock.getRatingQuestionsOfFormTemplates.mockResolvedValueOnce(new Map());
            const result = await performanceReviewsService.listCycleTargetUsers(cycle, { withStatsByType: true });
            expect(result.targetUsers[0].stats?.statsPerEvaluationType).toBeDefined();
            expect(result.targetUsers[0].stats?.statsPerEvaluationType).toStrictEqual(mockStatsByType);
        });
    });

    describe('getTargetUser', () => {
        let targetUser: PerformanceReviewTargetUser;

        beforeEach(() => {
            const cycleId = fakePositiveInteger();
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId, fakePositiveInteger(), fakePositiveInteger());
        });

        it('get target user successfully without user details', async () => {
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);

            const result = await performanceReviewsService.getTargetUser(targetUser.cycleId, targetUser.userId, false);

            expect(result).toBe(targetUser);
            expect(result.user).toBeNull();
            expect(result.responsible).toBeNull();
        });

        it('get target user successfully with user details', async () => {
            targetUser.user = generateRandomPerformanceReviewUser(targetUser.userId);
            targetUser.responsible = targetUser.responsibleId !== null ? generateRandomPerformanceReviewUser(targetUser.responsibleId) : null;
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);

            const result = await performanceReviewsService.getTargetUser(targetUser.cycleId, targetUser.userId, true);

            expect(result).toBe(targetUser);
            expect(result.user).toBe(targetUser.user);
            expect(result.responsible).toBe(targetUser.responsible);
        });

        it('throw not found error if target user does not exist', async () => {
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.getTargetUser(targetUser.cycleId, targetUser.userId)).rejects.toThrow(NotFoundError);
        });
    });

    describe('getVisibleTargetUser', () => {
        let userId: number;
        let responsibleId: number;
        let targetUser: PerformanceReviewTargetUser;
        let instanceId: number;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            const cycleId = fakePositiveInteger();
            userId = fakePositiveInteger();
            responsibleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId, userId, responsibleId, PerformanceReviewFormShareStatus.NOT_SHARED);
            reviewCycle = generateRandomPerformanceReviewCycle({ reviewsVisibleAfterFinishedReviewCycle: false });
        });

        it('get target user successfully if logged user is responsible and the cycle has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                reviewsVisibleAfterFinishedReviewCycle: false,
            });

            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            const result = await performanceReviewsService.getVisibleTargetUser(
                targetUser.cycleId,
                targetUser.userId,
                responsibleId,
                instanceId,
                false,
            );

            expect(result).toBe(targetUser);
        });

        it('get target user successfully if logged user is target user and it is shared', async () => {
            targetUser.shareStatus = faker.helpers.arrayElement(isFormSharedStatuses);
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            const result = await performanceReviewsService.getVisibleTargetUser(targetUser.cycleId, targetUser.userId, userId, instanceId, false);

            expect(result).toBe(targetUser);
        });

        it('get target user successfully with user details when the cycle has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                reviewsVisibleAfterFinishedReviewCycle: false,
            });
            targetUser.user = generateRandomPerformanceReviewUser(userId);
            targetUser.responsible = generateRandomPerformanceReviewUser(responsibleId);
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            const result = await performanceReviewsService.getVisibleTargetUser(
                targetUser.cycleId,
                targetUser.userId,
                responsibleId,
                instanceId,
                true,
            );

            expect(result).toBe(targetUser);
            expect(result.user).toBe(targetUser.user);
            expect(result.responsible).toBe(targetUser.responsible);
        });

        it('throw not found error if logged user is responsible and the cycle has not subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: Object.values(PerformanceReviewEvaluationTypes).filter(
                    (type) => type !== PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                ),
                reviewsVisibleAfterFinishedReviewCycle: false,
            });

            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(performanceReviewsService.getVisibleTargetUser).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if target user does not exist', async () => {
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(
                performanceReviewsService.getVisibleTargetUser(targetUser.cycleId, targetUser.userId, fakePositiveInteger(), instanceId, false),
            ).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if logged user is target user, but it is not shared', async () => {
            targetUser.shareStatus = faker.helpers.arrayElement([
                PerformanceReviewFormShareStatus.SHARE_DISABLED,
                PerformanceReviewFormShareStatus.NOT_SHARED,
            ]);
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(
                performanceReviewsService.getVisibleTargetUser(targetUser.cycleId, targetUser.userId, userId, instanceId, false),
            ).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if logged user is not related to target user', async () => {
            const loggedUserId = faker.number.int({ min: Math.max(userId, responsibleId) + 1 });
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(
                performanceReviewsService.getVisibleTargetUser(targetUser.cycleId, targetUser.userId, loggedUserId, instanceId, false),
            ).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if cycle does not finished and has reviewsVisibleAfterFinishedReviewCycle true', async () => {
            const loggedUserId = faker.number.int({ min: Math.max(userId, responsibleId) + 1 });
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);
            reviewCycle.reviewsVisibleAfterFinishedReviewCycle = true;
            reviewCycle.status = PerformanceReviewCycleStatus.IN_PROGRESS;
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(
                performanceReviewsService.getVisibleTargetUser(targetUser.cycleId, targetUser.userId, loggedUserId, instanceId, false),
            ).rejects.toThrow(NotFoundError);
        });
    });

    describe('addUserReviewsToTargetUser', () => {
        let targetUser: PerformanceReviewTargetUser;
        let cycleId: number;
        let cycle: PerformanceReviewCycle;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId, fakePositiveInteger(), fakePositiveInteger());
            targetUser.user = generateRandomPerformanceReviewUser(targetUser.userId);
            targetUser.responsible = targetUser.responsibleId !== null ? generateRandomPerformanceReviewUser(targetUser.responsibleId) : null;
            cycle = generateRandomPerformanceReviewCycle({ id: cycleId });
        });

        it('add user reviews to target user successfully', async () => {
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockImplementationOnce((cycleId, reviewedId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, { reviewedId });
                formReview.reviewer = generateRandomPerformanceReviewUser(formReview.reviewerId!);
                return Promise.resolve([formReview]);
            });
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                peerExternalReviewAnonymousAnswers: false,
                peerReviewAnonymousAnswers: false,
                upwardReviewAnonymousAnswers: false,
            });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            await performanceReviewsService.addUserReviewsToTargetUser(reviewCycle, targetUser, false);

            expect(targetUser.reviewedFormReviews).not.toBeNull();

            targetUser.reviewedFormReviews?.forEach((formReview) => {
                expect(formReview.reviewed).not.toBeNull();
                expect(formReview.reviewer).not.toBeNull();
            });
        });

        it('add user reviews to target user successfully with anonymous reviewer in boss review', async () => {
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockImplementationOnce((cycleId, reviewedId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewedId,
                    type: PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                });
                formReview.reviewer = generateRandomPerformanceReviewUser(formReview.reviewerId!);
                return Promise.resolve([formReview]);
            });

            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), { upwardReviewAnonymousAnswers: true });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await performanceReviewsService.addUserReviewsToTargetUser(reviewCycle, targetUser, false);

            expect(targetUser.reviewedFormReviews).not.toBeNull();

            targetUser.reviewedFormReviews?.forEach((formReview) => {
                expect(formReview.reviewed).not.toBeNull();
                expect(formReview.reviewer).toBeNull();
            });
        });

        it('add user reviews to target user successfully with reviewer in boss review', async () => {
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockImplementationOnce((cycleId, reviewedId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewedId,
                    type: PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                });
                formReview.reviewer = generateRandomPerformanceReviewUser(formReview.reviewerId!);
                return Promise.resolve([formReview]);
            });
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), { upwardReviewAnonymousAnswers: false });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await performanceReviewsService.addUserReviewsToTargetUser(reviewCycle, targetUser, false);

            expect(targetUser.reviewedFormReviews).not.toBeNull();

            targetUser.reviewedFormReviews?.forEach((formReview) => {
                expect(formReview.reviewed).not.toBeNull();
                expect(formReview.reviewer).not.toBeNull();
            });
        });

        it('throw illegal state error if target user has no user loaded', async () => {
            targetUser.user = null;
            repositoryMock.getTargetUser.mockResolvedValueOnce(targetUser);

            await expect(performanceReviewsService.addUserReviewsToTargetUser(cycle, targetUser, false)).rejects.toThrow(IllegalStateError);
        });

        describe('calibration filtering via applyCalibrationFilterToFormReviews', () => {
            const reviewedId = fakePositiveInteger();
            const cycleId = fakePositiveInteger();

            const buildSubordinateReview = (id: number, reviewerId: number) =>
                generatePerformanceReviewFormReviewWithValues(id, cycleId, {
                    reviewedId,
                    type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                    reviewerId,
                    coreFormAnswerId: fakePositiveInteger(),
                    score: 80,
                });

            const buildCalibrationReview = (id: number, calibratorId: number | null, responsibleId: number) =>
                generatePerformanceReviewFormReviewWithValues(id, cycleId, {
                    reviewedId,
                    type: PerformanceReviewEvaluationTypes.CALIBRATION_REVIEW,
                    reviewerId: calibratorId,
                    responsibleId,
                    coreFormAnswerId: calibratorId !== null ? fakePositiveInteger() : null,
                    score: calibratorId !== null ? 50 : undefined,
                });

            beforeEach(() => {
                targetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId, fakePositiveInteger());
                targetUser.user = generateRandomPerformanceReviewUser(reviewedId);
                cycle = generateRandomPerformanceReviewCycle({ id: cycleId });
            });

            it('maskCalibration=false, calibration filled: returns SUBORDINATE only; map has subordinate.id → calibration', async () => {
                const bossId = fakePositiveInteger();
                const subordinate = buildSubordinateReview(fakePositiveInteger(), bossId);
                const calibratorId = fakePositiveInteger();
                const calibration = buildCalibrationReview(fakePositiveInteger(), calibratorId, bossId);

                repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([subordinate, calibration]);
                calibrationServiceMock.isFilledCalibration.mockReturnValueOnce(true);

                const map = await performanceReviewsService.addUserReviewsToTargetUser(cycle, targetUser, false);

                expect(targetUser.reviewedFormReviews).toHaveLength(1);
                expect(targetUser.reviewedFormReviews![0].type).toBe(PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
                expect(map.get(subordinate.id)?.type).toBe(PerformanceReviewEvaluationTypes.CALIBRATION_REVIEW);
                expect(map.size).toBe(1);
            });

            it('maskCalibration=false, no calibration filled: returns SUBORDINATE; empty map', async () => {
                const bossId = fakePositiveInteger();
                const subordinate = buildSubordinateReview(fakePositiveInteger(), bossId);
                const calibration = buildCalibrationReview(fakePositiveInteger(), null, bossId);

                repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([subordinate, calibration]);
                calibrationServiceMock.isFilledCalibration.mockReturnValueOnce(false);

                const map = await performanceReviewsService.addUserReviewsToTargetUser(cycle, targetUser, false);

                expect(targetUser.reviewedFormReviews).toHaveLength(1);
                expect(targetUser.reviewedFormReviews![0].type).toBe(PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
                expect(map.size).toBe(0);
            });

            it('maskCalibration=true, calibration filled: returns one SUBORDINATE_REVIEW with boss reviewer and calibration score', async () => {
                const bossId = fakePositiveInteger();
                const subordinate = buildSubordinateReview(fakePositiveInteger(), bossId);
                subordinate.reviewer = generateRandomPerformanceReviewUser(bossId);
                const calibratorId = fakePositiveInteger();
                const calibration = buildCalibrationReview(fakePositiveInteger(), calibratorId, bossId);

                repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([subordinate, calibration]);
                calibrationServiceMock.isFilledCalibration.mockReturnValueOnce(true);

                const map = await performanceReviewsService.addUserReviewsToTargetUser(cycle, targetUser, true);

                expect(targetUser.reviewedFormReviews).toHaveLength(1);
                const result = targetUser.reviewedFormReviews![0];
                expect(result.id).toBe(subordinate.id);
                expect(result.type).toBe(PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
                expect(result.reviewerId).toBe(bossId);
                expect(result.score).toBe(50);
                expect(map.size).toBe(0);
            });

            it('maskCalibration=true, calibration filled, multi-type cycle: preserves AUTO and PEER alongside masked SUBORDINATE', async () => {
                const bossId = fakePositiveInteger();
                const autoReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewedId,
                    type: PerformanceReviewEvaluationTypes.AUTO_REVIEW,
                    reviewerId: reviewedId,
                    coreFormAnswerId: fakePositiveInteger(),
                    score: 70,
                });
                const peerReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                    reviewedId,
                    type: PerformanceReviewEvaluationTypes.PEER_REVIEW,
                    reviewerId: fakePositiveInteger(),
                    coreFormAnswerId: fakePositiveInteger(),
                    score: 60,
                });
                const subordinate = buildSubordinateReview(fakePositiveInteger(), bossId);
                subordinate.reviewer = generateRandomPerformanceReviewUser(bossId);
                const calibratorId = fakePositiveInteger();
                const calibration = buildCalibrationReview(fakePositiveInteger(), calibratorId, bossId);

                repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([autoReview, peerReview, subordinate, calibration]);
                calibrationServiceMock.isFilledCalibration.mockReturnValueOnce(true);

                const map = await performanceReviewsService.addUserReviewsToTargetUser(cycle, targetUser, true);

                expect(targetUser.reviewedFormReviews).toHaveLength(3);
                const types = targetUser.reviewedFormReviews!.map((r) => r.type);
                expect(types).toContain(PerformanceReviewEvaluationTypes.AUTO_REVIEW);
                expect(types).toContain(PerformanceReviewEvaluationTypes.PEER_REVIEW);
                expect(types).toContain(PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
                expect(types).not.toContain(PerformanceReviewEvaluationTypes.CALIBRATION_REVIEW);
                const maskedSubordinate = targetUser.reviewedFormReviews!.find(
                    (r) => r.type === PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                );
                expect(maskedSubordinate!.id).toBe(subordinate.id);
                expect(maskedSubordinate!.score).toBe(50);
                expect(maskedSubordinate!.reviewerId).toBe(bossId);
                expect(map.size).toBe(0);
            });

            it('maskCalibration=true, no calibration filled: returns SUBORDINATE unchanged; empty map', async () => {
                const bossId = fakePositiveInteger();
                const subordinate = buildSubordinateReview(fakePositiveInteger(), bossId);
                const calibration = buildCalibrationReview(fakePositiveInteger(), null, bossId);

                repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([subordinate, calibration]);
                calibrationServiceMock.isFilledCalibration.mockReturnValueOnce(false);

                const map = await performanceReviewsService.addUserReviewsToTargetUser(cycle, targetUser, true);

                expect(targetUser.reviewedFormReviews).toHaveLength(1);
                expect(targetUser.reviewedFormReviews![0].type).toBe(PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
                expect(targetUser.reviewedFormReviews![0].score).toBe(80);
                expect(map.size).toBe(0);
            });
        });
    });

    describe('deleteTargetUser', () => {
        let targetUser: PerformanceReviewTargetUser;

        beforeEach(() => {
            const cycleId = fakePositiveInteger();
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId, fakePositiveInteger(), fakePositiveInteger());
        });

        it('successfully delete a target user', async () => {
            repositoryMock.deleteGoalQuestionsWithAnswerOfCycleForReviewedId.mockResolvedValueOnce();
            repositoryMock.deleteFormReviewsOfCycleForReviewedId.mockResolvedValueOnce();
            repositoryMock.deleteCompetencyQuestionsWithAnswerForReviewedId.mockResolvedValueOnce();
            repositoryMock.deletePeerReviewersOfTargetUser.mockResolvedValueOnce();
            repositoryMock.deleteTargetUsersOfCycleByIds.mockResolvedValueOnce();
            await expect(performanceReviewsService.deleteTargetUser(targetUser)).resolves.not.toThrow();
            checkMockCallsCount(repositoryMock.deleteGoalQuestionsWithAnswerOfCycleForReviewedId, 1);
            expect(repositoryMock.deleteGoalQuestionsWithAnswerOfCycleForReviewedId).toHaveBeenCalledWith(targetUser.cycleId, targetUser.userId);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewedId, 1);
            expect(repositoryMock.deleteFormReviewsOfCycleForReviewedId).toHaveBeenCalledWith(targetUser.cycleId, targetUser.userId);
            checkMockCallsCount(repositoryMock.deleteCompetencyQuestionsWithAnswerForReviewedId, 1);
            expect(repositoryMock.deleteCompetencyQuestionsWithAnswerForReviewedId).toHaveBeenCalledWith(targetUser.cycleId, targetUser.userId);
            checkMockCallsCount(repositoryMock.deleteTargetUsersOfCycleByIds, 1);
            expect(repositoryMock.deleteTargetUsersOfCycleByIds).toHaveBeenCalledWith(targetUser.cycleId, [targetUser.userId]);
            checkMockCallsCount(repositoryMock.deletePeerReviewersOfTargetUser, 1);
            expect(repositoryMock.deletePeerReviewersOfTargetUser).toHaveBeenCalledWith(targetUser.cycleId, targetUser.userId);
        });
    });

    describe('addTargetUserToCycle', () => {
        let params: UserInformationForReviews;
        let reviewCycle: PerformanceReviewCycle;
        let fakeId: number;
        let fakeInstanceId: number;
        let fakeDate: Date;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeInstanceId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.AUTO_REVIEW],
                instanceId: fakeInstanceId,
                status: PerformanceReviewCycleStatus.PENDING,
                targetUsers: [1, 2, 3, 4, 5],
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            params = {
                userBossRelations: { bossIds: [1], subordinateIds: [] },
                userId: 6,
            };
        });

        it('add a target user successfully if cycle has not started', async () => {
            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.upsertTargetUsers.mockImplementationOnce((targetUsers) => {
                const transformedTargetUsers = targetUsers.map((targetUser) => getPerformanceReviewTargetUserFromCreation(targetUser));
                return Promise.resolve(transformedTargetUsers);
            });

            const result = await performanceReviewsService.addTargetUserToCycle(reviewCycle, params);

            expect(result).toBeInstanceOf(PerformanceReviewTargetUser);
            expect(result.userId).toBe(params.userId);
            expect(result.cycleId).toBe(reviewCycle.id);
            expect(result.responsibleId).toBe(params.userBossRelations.bossIds.length > 0 ? params.userBossRelations.bossIds[0] : null);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARE_DISABLED);
            expect(result.reviewedFormReviews).toBeNull();

            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.upsertTargetUsers, 1);
            checkMockCallsCount(repositoryMock.createFormReviews, 0);
        });

        it('add a target user successfully if cycle is in progress', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.IN_PROGRESS;

            repositoryMock.getTargetUser.mockResolvedValueOnce(null);
            repositoryMock.upsertTargetUsers.mockImplementationOnce((targetUsers) => {
                const transformedTargetUsers = targetUsers.map((targetUser) => getPerformanceReviewTargetUserFromCreation(targetUser));
                return Promise.resolve(transformedTargetUsers);
            });
            repositoryMock.createFormReviews.mockImplementationOnce((reviewForms) => {
                const transformedReviewForms = reviewForms.map((reviewForm) => getPerformanceReviewFormReviewFromCreation(reviewForm));
                return Promise.resolve(transformedReviewForms);
            });
            repositoryMock.getAllReviewers.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.addTargetUserToCycle(reviewCycle, params);

            expect(result).toBeInstanceOf(PerformanceReviewTargetUser);
            expect(result.userId).toBe(params.userId);
            expect(result.cycleId).toBe(reviewCycle.id);
            expect(result.responsibleId).toBe(params.userBossRelations.bossIds.length > 0 ? params.userBossRelations.bossIds[0] : null);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARE_DISABLED);
            expect(result.reviewedFormReviews).not.toBeNull();
            expect(result.reviewedFormReviews).toHaveLength(1);

            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(repositoryMock.upsertTargetUsers, 1);
            checkMockCallsCount(repositoryMock.createFormReviews, 1);
        });

        it('throw illegal state error if cycle does not have form templates loaded', async () => {
            reviewCycle.formTemplates = null;

            await expect(performanceReviewsService.addTargetUserToCycle(reviewCycle, params)).rejects.toThrow(IllegalStateError);
        });

        it('throw bad request error if cycle is not targeted', async () => {
            reviewCycle.reviewedUsersMode = PerformanceReviewCycleReviewedUsersMode.ALL_USERS;

            await expect(performanceReviewsService.addTargetUserToCycle(reviewCycle, params)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if cycle is finished', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.FINISHED;

            await expect(performanceReviewsService.addTargetUserToCycle(reviewCycle, params)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if target user already exists', async () => {
            const userId = fakePositiveInteger();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.AUTO_REVIEW],
                instanceId: fakeInstanceId,
                status: PerformanceReviewCycleStatus.PENDING,
                targetUsers: [userId],
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            params = {
                userBossRelations: { bossIds: [], subordinateIds: [] },
                userId,
            };

            repositoryMock.getTargetUser.mockResolvedValueOnce((reviewCycle.targetUsers as PerformanceReviewTargetUser[])[0]);

            await expect(performanceReviewsService.addTargetUserToCycle(reviewCycle, params)).rejects.toThrow(BadRequestError);
        });
    });

    describe('startCycle', () => {
        let startParams: InstanceInformationForReviews;
        let reviewCycle: PerformanceReviewCycle;
        let fakeId: number;
        let fakeInstanceId: number;
        let fakeDate: Date;
        let ignoredReviewerIds: number[];

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeInstanceId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                formTemplateTypes: [],
                instanceId: fakeInstanceId,
                needsChanges: false,
                status: PerformanceReviewCycleStatus.PENDING,
                targetUsers: [],
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            startParams = {
                instanceBossRelations: [],
                instanceUserIds: faker.helpers.multiple(faker.number.int),
            };
        });

        describe('successfully start cycle under different circumstances', () => {
            const testCases: [string, PerformanceReviewCycle, InstanceInformationForReviews, PerformanceReviewReviewer[]][] = [
                [
                    'successfully start cycle if it targets all users',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
                    }),
                    {
                        instanceBossRelations: [],
                        instanceUserIds: faker.helpers.multiple(faker.number.int),
                    },
                    [],
                ],
                [
                    'successfully start cycle with auto-review evaluation',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.AUTO_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [],
                ],
                [
                    'successfully start cycle with subordinate-review evaluation',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [],
                ],
                [
                    'successfully start cycle with boss-review evaluation',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.BOSS_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [],
                ],
                [
                    'successfully start cycle with boss-review evaluation and limited reviewers by hiring date',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.BOSS_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        reviewerLimitByHiringDate: faker.date.recent(),
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [],
                ],
                [
                    'successfully start cycle with peer-review evaluation',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [
                        [1, 2],
                        [1, 3],
                        [2, 3],
                        [3, 4],
                        [4, 5],
                        [5, 1],
                    ].map(
                        (pair) =>
                            new PerformanceReviewReviewer({
                                createdAt: new Date(),
                                cycleId: fakeId,
                                evaluationType: PerformanceReviewEvaluationTypes.PEER_REVIEW,
                                id: fakePositiveInteger(),
                                reviewedId: pair[0],
                                reviewerId: pair[1],
                                updatedAt: new Date(),
                            }),
                    ),
                ],
                [
                    'successfully start cycle with peer-external-review evaluation',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [
                        [1, 2],
                        [1, 3],
                        [2, 3],
                        [3, 4],
                        [4, 5],
                        [5, 1],
                    ].map(
                        (pair) =>
                            new PerformanceReviewReviewer({
                                createdAt: new Date(),
                                cycleId: fakeId,
                                evaluationType: PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                                externalReviewerId: pair[1],
                                id: fakePositiveInteger(),
                                reviewedId: pair[0],
                                updatedAt: new Date(),
                            }),
                    ),
                ],
                [
                    'successfully start cycle with others-review evaluation',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [PerformanceReviewEvaluationTypes.OTHERS_REVIEW],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [
                        [1, 2],
                        [1, 3],
                        [2, 3],
                        [3, 4],
                        [4, 5],
                        [5, 1],
                    ].map(
                        (pair) =>
                            new PerformanceReviewReviewer({
                                createdAt: new Date(),
                                cycleId: fakeId,
                                evaluationType: PerformanceReviewEvaluationTypes.OTHERS_REVIEW,
                                externalReviewerId: pair[1],
                                id: fakePositiveInteger(),
                                reviewedId: pair[0],
                                updatedAt: new Date(),
                            }),
                    ),
                ],
                [
                    'successfully start cycle with multiple evaluation types',
                    generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                        formTemplateTypes: [
                            PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                            PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                            PerformanceReviewEvaluationTypes.AUTO_REVIEW,
                            PerformanceReviewEvaluationTypes.PEER_REVIEW,
                            PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                            PerformanceReviewEvaluationTypes.OTHERS_REVIEW,
                        ],
                        instanceId: fakeInstanceId,
                        needsChanges: false,
                        status: PerformanceReviewCycleStatus.PENDING,
                        targetUsers: [1, 2, 3, 4, 5],
                        usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
                    }),
                    {
                        instanceBossRelations: [
                            { bossId: 1, subordinateId: 2 },
                            { bossId: 1, subordinateId: 3 },
                            { bossId: 1, subordinateId: 4 },
                            { bossId: 2, subordinateId: 5 },
                        ],
                        instanceUserIds: [1, 2, 3, 4, 5],
                    },
                    [
                        [1, 2],
                        [1, 3],
                        [2, 3],
                        [3, 4],
                        [4, 5],
                        [5, 1],
                    ].map((pair, index) => {
                        const randomEvaluationType = faker.helpers.arrayElement([
                            PerformanceReviewEvaluationTypes.PEER_REVIEW,
                            PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                            PerformanceReviewEvaluationTypes.OTHERS_REVIEW,
                        ]);
                        const isExternal = randomEvaluationType === PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW;
                        return new PerformanceReviewReviewer({
                            createdAt: new Date(),
                            cycleId: fakeId,
                            evaluationType: randomEvaluationType,
                            externalReviewerId: isExternal ? pair[1] : null,
                            id: fakePositiveInteger(),
                            reviewedId: pair[0],
                            reviewerId: !isExternal ? pair[1] : null,
                            updatedAt: new Date(),
                        });
                    }),
                ],
            ];

            it.each(testCases)(`%s`, async (_, reviewCycle, startParams, reviewers) => {
                repositoryMock.upsertTargetUsers.mockImplementationOnce((targetUsers) => {
                    const transformedTargetUsers = targetUsers.map((targetUser) => getPerformanceReviewTargetUserFromCreation(targetUser));
                    return Promise.resolve(transformedTargetUsers);
                });
                repositoryMock.getTemplatesByIds.mockImplementationOnce((templateIds) => {
                    const templates = templateIds.map((id) => {
                        const template = generateRandomTemplate();
                        template.formTemplateId = id;
                        return template;
                    });
                    return Promise.resolve(templates);
                });
                formTemplatesPortMock.publishFormTemplates.mockImplementationOnce((reviewFormTemplates) => {
                    reviewFormTemplates.forEach((reviewFormTemplate) => {
                        reviewFormTemplate.coreForm = new CoreForm(
                            fakePositiveInteger(),
                            faker.string.uuid(),
                            fakeInstanceId,
                            { description: null, steps: [], title: faker.word.words() },
                            [],
                            faker.date.recent(),
                            faker.date.recent(),
                            null,
                            faker.string.uuid(),
                        );
                    });
                    return Promise.resolve();
                });
                coreFormsPortMock.convertReferenceIdRecordsToQuestionIdRecords.mockImplementationOnce((referenceIdRecords, coreForm) => {
                    const convertedRecords: { [id: string]: number } = {};
                    coreForm.content.steps
                        .flatMap((step) => step.questions)
                        .forEach((question) => {
                            if (question.referenceId !== undefined && referenceIdRecords[question.referenceId] !== undefined) {
                                convertedRecords[question.id] = referenceIdRecords[question.referenceId];
                            }
                        });
                    return convertedRecords;
                });
                mockImplementationTimes(
                    repositoryMock.saveFormTemplate,
                    (formTemplate: FormTemplate) => Promise.resolve(formTemplate),
                    reviewCycle.formTemplates?.length ?? 0,
                );
                repositoryMock.createFormReviews.mockImplementationOnce((reviewForms) => {
                    const transformedReviewForms = reviewForms.map((reviewForm) => getPerformanceReviewFormReviewFromCreation(reviewForm));
                    return Promise.resolve(transformedReviewForms);
                });
                repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));
                repositoryMock.getAllReviewers.mockResolvedValueOnce(reviewers);

                ignoredReviewerIds = [2]; // for boss-review evaluation
                usersPortMock.getReviewersIdsFilteredByHiringDate.mockResolvedValueOnce(ignoredReviewerIds);

                const result = await performanceReviewsService.startCycle(reviewCycle, startParams);

                expect(result).toBeDefined();

                checkMockCallsCount(repositoryMock.upsertTargetUsers, 1);
                checkMockCallsCount(formTemplatesPortMock.publishFormTemplates, 1);
                checkMockCallsCount(repositoryMock.saveFormTemplate, reviewCycle.formTemplates?.length ?? 0);
                checkMockCallsCount(repositoryMock.createFormReviews, 1);
                checkMockCallsCount(repositoryMock.saveCycle, 1);
                checkMockCallsCount(repositoryMock.getAllReviewers, 1);
                checkStartedPerformanceReviewCycle(reviewCycle, result, startParams, reviewers, ignoredReviewerIds);
            });
        });

        it('throw request conflict error if cycle needs changes', async () => {
            reviewCycle.needsChanges = true;

            await expect(performanceReviewsService.startCycle(reviewCycle, startParams)).rejects.toThrow(RequestConflictError);
        });

        it('throw illegal state error if cycle does not have form templates loaded', async () => {
            reviewCycle.formTemplates = null;

            await expect(performanceReviewsService.startCycle(reviewCycle, startParams)).rejects.toThrow(IllegalStateError);
        });

        it('throw illegal state error if cycle does not have form template details loaded', async () => {
            reviewCycle.formTemplates = null;
            await expect(performanceReviewsService.startCycle(reviewCycle, startParams)).rejects.toThrow(IllegalStateError);
        });

        it('throw illegal state error if cycle does not have target users loaded', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                formTemplateTypes: [],
                instanceId: fakeInstanceId,
                needsChanges: false,
                status: PerformanceReviewCycleStatus.PENDING,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            await expect(performanceReviewsService.startCycle(reviewCycle, startParams)).rejects.toThrow(IllegalStateError);
        });

        it('throw bad request error if cycle has already started', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                formTemplateTypes: [],
                instanceId: fakeInstanceId,
                needsChanges: false,
                status: faker.helpers.arrayElement([
                    PerformanceReviewCycleStatus.FINISHED,
                    PerformanceReviewCycleStatus.IN_PROGRESS,
                    PerformanceReviewCycleStatus.DRAFT,
                ]),
                targetUsers: [],
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            await expect(performanceReviewsService.startCycle(reviewCycle, startParams)).rejects.toThrow(BadRequestError);
        });
    });

    describe('finishCycle', () => {
        let fakeId: number;
        let fakeDate: Date;
        let reviewCycle: PerformanceReviewCycle;
        let affectedTargetUsersCount: number;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
        });

        it('finish cycle that is in progress', async () => {
            affectedTargetUsersCount = faker.number.int({ max: 5, min: 1 });
            repositoryMock.updateStatusOfPendingEditRequests.mockResolvedValueOnce();
            repositoryMock.updateStatusOfFormReviews.mockResolvedValue();
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce([]);
            repositoryMock.updateShareStatusOfTargetUsersByShareStatus.mockImplementationOnce((cycleId, _, shareStatus) => {
                const affectedTargetUsers = Array(affectedTargetUsersCount)
                    .fill(1)
                    .map(() => generateRandomPerformanceReviewTargetUser(cycleId, fakePositiveInteger(), fakePositiveInteger(), shareStatus));
                return Promise.resolve(affectedTargetUsers);
            });
            repositoryMock.saveCycle.mockImplementationOnce((reviewCycle) => Promise.resolve(reviewCycle));

            reviewCycle.formTemplates = [];
            repositoryMock.deleteTargetUsers.mockResolvedValueOnce();
            repositoryMock.deleteFormTemplates.mockResolvedValueOnce();
            repositoryMock.deleteFormReviews.mockResolvedValueOnce();
            repositoryMock.getTemplatesByIds.mockResolvedValue([]);
            repositoryMock.countTemplateUsages.mockResolvedValueOnce({});

            const result = await performanceReviewsService.finishCycle(reviewCycle);

            expect(result).toBeInstanceOf(PerformanceReviewCycle);
            expect(result.id).toBe(fakeId);
            expect(result.status).toBe(PerformanceReviewCycleStatus.FINISHED);
            expect(result.endDate).toBeDate();
            expect(result.targetUsers).not.toBeNull();
            expect(result.targetUsers).toHaveLength(affectedTargetUsersCount);

            checkMockCallsCount(repositoryMock.updateStatusOfPendingEditRequests, 1);
            checkMockCallsCount(repositoryMock.updateStatusOfFormReviews, 2);
            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfTargetUsersByShareStatus, 1);
            checkMockCallsCount(repositoryMock.saveCycle, 1);
        });

        it('throw bad request error if cycle is not in progress', async () => {
            reviewCycle.status = faker.helpers.arrayElement([
                PerformanceReviewCycleStatus.FINISHED,
                PerformanceReviewCycleStatus.DRAFT,
                PerformanceReviewCycleStatus.PENDING,
            ]);

            await expect(performanceReviewsService.finishCycle(reviewCycle)).rejects.toThrow(BadRequestError);
        });
    });

    describe('findEditRequestByPk', () => {
        let editRequest: PerformanceReviewFormReviewEditRequest;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            const fakeCycleId = fakePositiveInteger();
            editRequest = generateRandomEditRequest({ cycleId: fakeCycleId, id: fakeId });
        });

        it('find edit request successfully', async () => {
            repositoryMock.getEditRequestById.mockResolvedValueOnce(editRequest);

            const result = await performanceReviewsService.findEditRequestByPk(editRequest.id, editRequest.cycleId);

            expect(result).toBe(editRequest);
        });

        it('throw not found error if edit request does not exist', async () => {
            repositoryMock.getEditRequestById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findEditRequestByPk(editRequest.id, editRequest.cycleId)).rejects.toThrow(NotFoundError);
        });
    });

    describe('findReviewByPk', () => {
        let reviewForm: PerformanceReviewFormReview;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            const fakeCycleId = fakePositiveInteger();
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId);
        });

        it('find review form successfully without review users', async () => {
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            const result = await performanceReviewsService.findReviewByPk(reviewForm.id, reviewForm.cycleId, false);

            expect(result).toBe(reviewForm);
        });

        it('find review form successfully with review users', async () => {
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            const result = await performanceReviewsService.findReviewByPk(reviewForm.id, reviewForm.cycleId, true);

            expect(result).toBe(reviewForm);
        });

        it('throw not found error if review form does not exist', async () => {
            repositoryMock.getFormReviewById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findReviewByPk(reviewForm.id, reviewForm.cycleId)).rejects.toThrow(NotFoundError);
        });
    });

    describe('findVisibleReviewByPk', () => {
        let fakeId: number;
        let fakeCycleId: number;
        let userId: number;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeCycleId = fakePositiveInteger();
            userId = fakePositiveInteger();
            reviewCycle = generateRandomPerformanceReviewCycle();
        });

        it('find visible review form successfully if userId matches reviewer', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, reviewCycle.id, {
                responsibleId: faker.number.int({ min: userId + 1 }),
                reviewedId: faker.number.int({ min: userId + 1 }),
                reviewerId: userId,
            });
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            const result = await performanceReviewsService.findVisibleReviewByPk(fakeId, reviewCycle, userId);

            expect(result).toBe(reviewForm);
        });

        it('find visible review form successfully if userId matches reviewed and it is shared', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                responsibleId: faker.number.int({ min: userId + 1 }),
                reviewedId: userId,
                reviewerId: faker.number.int({ min: userId + 1 }),
                shareStatus: faker.helpers.arrayElement(isFormSharedStatuses),
            });
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            const result = await performanceReviewsService.findVisibleReviewByPk(fakeId, reviewCycle, userId);

            expect(result).toBe(reviewForm);
        });

        it('find visible review form successfully with review users', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                responsibleId: faker.number.int({ min: userId + 1 }),
                reviewedId: faker.number.int({ min: userId + 1 }),
                reviewerId: userId,
            });
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            const result = await performanceReviewsService.findReviewByPk(reviewForm.id, reviewForm.cycleId, true);

            expect(result).toBe(reviewForm);
        });

        it('throw not found error if cycle does not exist', async () => {
            repositoryMock.getFormReviewById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findVisibleReviewByPk(fakeId, reviewCycle, reviewCycle.instanceId)).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if userId matches reviewed and it is not shared', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                responsibleId: faker.number.int({ min: userId + 1 }),
                reviewedId: userId,
                reviewerId: faker.number.int({ min: userId + 1 }),
                shareStatus: faker.helpers.arrayElement([
                    PerformanceReviewFormShareStatus.NOT_SHARED,
                    PerformanceReviewFormShareStatus.SHARE_DISABLED,
                ]),
            });
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            await expect(performanceReviewsService.findVisibleReviewByPk(fakeId, reviewCycle, userId)).rejects.toThrow(NotFoundError);
        });

        it('throw not found error if userId does not match reviewed nor reviewer', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                responsibleId: faker.number.int({ min: userId + 1 }),
                reviewedId: faker.number.int({ min: userId + 1 }),
                reviewerId: faker.number.int({ min: userId + 1 }),
            });
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            await expect(performanceReviewsService.findVisibleReviewByPk(fakeId, reviewCycle, userId)).rejects.toThrow(NotFoundError);
        });
    });

    describe('addFormDetailsToReview', () => {
        let fakeId: number;
        let fakeCycleId: number;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeCycleId = fakePositiveInteger();
        });

        it('add form details successfully to review form', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, { coreFormAnswerId: null });
            coreFormsPortMock.getForm.mockImplementationOnce((tag, id) => Promise.resolve(generateRandomCoreFormWithQuestions(tag, id)));

            await performanceReviewsService.addFormDetailsToReview(reviewForm);

            expect(reviewForm.coreForm).not.toBeNull();
            expect(reviewForm.coreForm).toBeInstanceOf(CoreForm);
            expect(reviewForm.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(reviewForm.coreForm?.id).toBe(reviewForm.coreFormId);

            checkMockCallsCount(coreFormsPortMock.getForm, 1);
            checkMockCallsCount(coreFormsPortMock.getFormAnswer, 0);
        });

        it('add form details successfully to review form with answer', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, { coreFormAnswerId: fakePositiveInteger() });
            coreFormsPortMock.getForm.mockImplementationOnce((tag, id) => Promise.resolve(generateRandomCoreFormWithQuestions(tag, id)));
            coreFormsPortMock.getFormAnswer.mockResolvedValueOnce(
                generateFormAnswerWithValues(reviewForm.coreFormAnswerId as number, reviewForm.coreFormTag!, reviewForm.coreFormId!, []),
            );

            await performanceReviewsService.addFormDetailsToReview(reviewForm);

            expect(reviewForm.coreForm).not.toBeNull();
            expect(reviewForm.coreForm).toBeInstanceOf(CoreForm);
            expect(reviewForm.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(reviewForm.coreForm?.id).toBe(reviewForm.coreFormId);

            expect(reviewForm.formAnswer).not.toBeNull();
            expect(reviewForm.formAnswer).toBeInstanceOf(FormAnswer);
            expect(reviewForm.formAnswer?.id).toBe(reviewForm.coreFormAnswerId);

            checkMockCallsCount(coreFormsPortMock.getForm, 1);
            checkMockCallsCount(coreFormsPortMock.getFormAnswer, 1);
        });

        it('throw illegal state error if core form does not exist', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, { coreFormAnswerId: null });
            coreFormsPortMock.getForm.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.addFormDetailsToReview(reviewForm)).rejects.toThrow(IllegalStateError);
        });
    });

    describe('addGoalDetailsToReview', () => {
        let evaluationType: PerformanceReviewEvaluationTypesValues;

        beforeEach(() => {
            evaluationType = faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes));
        });

        it('add goal details successfully with right statuses and direction, with existing goal questions', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [evaluationType],
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
            });
            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            const goalQuestions = [generateGoalQuestionWithAnswer()];
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce(goalQuestions);
            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswer);
            expect(reviewForm.goalQuestionsWithAnswer![0]).toBe(goalQuestions[0]);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
        });

        it('add goal details successfully with right statuses and direction, with generated goal questions, weights missing, manual mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType,
                }),
            ];
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: PerformanceReviewFormStatus.PENDING,
            });

            const template = generateRandomTemplate(undefined, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);
            const validGoalStatus = faker.helpers.arrayElement([GoalStatus.FINISHED, GoalStatus.IN_PROGRESS]);
            const firstGoal = generateRandomGoal({ status: validGoalStatus });
            const secondGoal = generateRandomGoal({ status: validGoalStatus });
            goalsPort.getGoalsOfOwnerInCycle.mockResolvedValueOnce([firstGoal, secondGoal]);

            const firstGoalWeight = generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, firstGoal.id, 50);
            const secondGoalWeight = generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, secondGoal.id, 50);
            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([firstGoalWeight, secondGoalWeight]);

            const questionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId);
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValue(questionTemplate);
            const coreFormQuestion = generateRandomCoreFormQuestion();
            formTemplatesPortMock.questionTemplateToCoreFormQuestion.mockReturnValueOnce(coreFormQuestion);

            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer!).toHaveLength(2);
            expect(reviewForm.goalQuestionsWithAnswer![0]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswerDataFields);
            expect(reviewForm.goalQuestionsWithAnswer![0].goal).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0].goalId).toBe(firstGoal.id);
            expect(reviewForm.goalQuestionsWithAnswer![0].questionWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0].weight).toBe(50);
            expect(reviewForm.goalQuestionsWithAnswer![0].userId).toBe(reviewForm.reviewedId);
            expect(reviewForm.goalQuestionsWithAnswer![0].cycleId).toBe(reviewCycle.id);
            expect(reviewForm.goalQuestionsWithAnswer![1]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswerDataFields);
            expect(reviewForm.goalQuestionsWithAnswer![1].goal).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![1].goalId).toBe(secondGoal.id);
            expect(reviewForm.goalQuestionsWithAnswer![1].questionWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![1].weight).toBe(50);
            expect(reviewForm.goalQuestionsWithAnswer![1].userId).toBe(reviewForm.reviewedId);
            expect(reviewForm.goalQuestionsWithAnswer![1].cycleId).toBe(reviewCycle.id);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 1);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 1);
        });

        it('add goal details successfully with right statuses and direction, with generated goal questions, existing weights, manual mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType,
                }),
            ];
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: PerformanceReviewFormStatus.PENDING,
            });

            const template = generateRandomTemplate(undefined, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);

            const firstGoal = generateRandomGoal();
            const secondGoal = generateRandomGoal();
            goalsPort.getGoalsOfOwnerInCycle.mockResolvedValueOnce([firstGoal, secondGoal]);

            const firstGoalWeight = generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, firstGoal.id, 75);
            const secondGoalWeight = generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, secondGoal.id, 25);
            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([firstGoalWeight, secondGoalWeight]);

            const questionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId);
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValue(questionTemplate);

            const coreFormQuestion = generateRandomCoreFormQuestion();
            formTemplatesPortMock.questionTemplateToCoreFormQuestion.mockReturnValueOnce(coreFormQuestion);

            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer!).toHaveLength(2);
            expect(reviewForm.goalQuestionsWithAnswer![0]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswerDataFields);
            expect(reviewForm.goalQuestionsWithAnswer![0].goal).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0].goalId).toBe(firstGoal.id);
            expect(reviewForm.goalQuestionsWithAnswer![0].questionWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0].weight).toBe(firstGoalWeight.weight);
            expect(reviewForm.goalQuestionsWithAnswer![0].userId).toBe(reviewForm.reviewedId);
            expect(reviewForm.goalQuestionsWithAnswer![0].cycleId).toBe(reviewCycle.id);
            expect(reviewForm.goalQuestionsWithAnswer![1]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswerDataFields);
            expect(reviewForm.goalQuestionsWithAnswer![1].goal).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![1].goalId).toBe(secondGoal.id);
            expect(reviewForm.goalQuestionsWithAnswer![1].questionWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![1].weight).toBe(secondGoalWeight.weight);
            expect(reviewForm.goalQuestionsWithAnswer![1].userId).toBe(reviewForm.reviewedId);
            expect(reviewForm.goalQuestionsWithAnswer![1].cycleId).toBe(reviewCycle.id);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 1);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 1);
            checkMockCallsCount(utilsPort.splitInteger, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 1);
        });

        it('add goal details successfully with right statuses and direction, with generated goal questions, existing weights, auto mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            reviewCycle.formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType,
                }),
            ];
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: PerformanceReviewFormStatus.PENDING,
            });

            const template = generateRandomTemplate(undefined, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.AUTO);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);

            const firstGoal = generateRandomGoal();
            const secondGoal = generateRandomGoal();
            goalsPort.getGoalsOfOwnerInCycle.mockResolvedValueOnce([firstGoal, secondGoal]);

            const firstGoalWeight = generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, firstGoal.id, 75);
            const secondGoalWeight = generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, secondGoal.id, 25);
            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([firstGoalWeight, secondGoalWeight]);

            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer!).toHaveLength(2);
            expect(reviewForm.goalQuestionsWithAnswer![0]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswerDataFields);
            expect(reviewForm.goalQuestionsWithAnswer![0].goal).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0].goalId).toBe(firstGoal.id);
            expect(reviewForm.goalQuestionsWithAnswer![0].questionWithAnswer).toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![0].weight).toBe(firstGoalWeight.weight);
            expect(reviewForm.goalQuestionsWithAnswer![0].userId).toBe(reviewForm.reviewedId);
            expect(reviewForm.goalQuestionsWithAnswer![0].cycleId).toBe(reviewCycle.id);
            expect(reviewForm.goalQuestionsWithAnswer![1]).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswerDataFields);
            expect(reviewForm.goalQuestionsWithAnswer![1].goal).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![1].goalId).toBe(secondGoal.id);
            expect(reviewForm.goalQuestionsWithAnswer![1].questionWithAnswer).toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer![1].weight).toBe(secondGoalWeight.weight);
            expect(reviewForm.goalQuestionsWithAnswer![1].userId).toBe(reviewForm.reviewedId);
            expect(reviewForm.goalQuestionsWithAnswer![1].cycleId).toBe(reviewCycle.id);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 1);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 1);
            checkMockCallsCount(utilsPort.splitInteger, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
        });

        it('no addition of goal details to review form for invalid cycle status', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([
                    PerformanceReviewCycleStatus.DRAFT,
                    PerformanceReviewCycleStatus.PENDING,
                    PerformanceReviewCycleStatus.SELECTION,
                ]),
            });
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
            });

            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).toBeNull();

            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(repositoryMock.getTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(utilsPort.getAllIntersectionsOfArrays, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(repositoryMock.deleteGoalQuestionsWithAnswerByIds, 0);
        });

        it.each([
            PerformanceReviewFormStatus.FINISHED,
            PerformanceReviewFormStatus.EDIT_REQUESTED,
            PerformanceReviewFormStatus.EDIT_PENDING,
        ])(
            'returns null goal questions when no saved data and review status is %s (does not recalculate)',
            async (status) => {
                const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                    formTemplateTypes: [evaluationType],
                    status: PerformanceReviewCycleStatus.IN_PROGRESS,
                });
                reviewCycle.formTemplates = [
                    generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, { evaluationType }),
                ];
                const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                    coreFormAnswerId: null,
                    type: evaluationType,
                    status,
                });
                const template = generateRandomTemplate(undefined, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
                repositoryMock.getTemplateById.mockResolvedValueOnce(template);
                repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);

                await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

                expect(reviewForm.goalQuestionsWithAnswer).toBeNull();
                checkMockCallsCount(repositoryMock.getTemplateById, 1);
                checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
                checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
                checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
                checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            },
        );

        it('recalculates goal questions when no saved data and review status is PENDING', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
            reviewCycle.formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, { evaluationType }),
            ];
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: PerformanceReviewFormStatus.PENDING,
            });
            const template = generateRandomTemplate(undefined, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);
            const goal = generateRandomGoal();
            goalsPort.getGoalsOfOwnerInCycle.mockResolvedValueOnce([goal]);
            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([
                generateRandomGoalWeight(reviewCycle.goalCycleId!, reviewForm.reviewedId, goal.id, 100),
            ]);
            const questionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId);
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValue(questionTemplate);
            formTemplatesPortMock.questionTemplateToCoreFormQuestion.mockReturnValue(generateRandomCoreFormQuestion());

            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.goalQuestionsWithAnswer!).toHaveLength(1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 1);
        });

        it('no addition of goal details to review form if there is no goal cycle associated', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
            reviewCycle.goalCycleId = null;
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
            });

            await performanceReviewsService.addGoalDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.goalQuestionsWithAnswer).toBeNull();

            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(repositoryMock.getTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(utilsPort.getAllIntersectionsOfArrays, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(repositoryMock.deleteGoalQuestionsWithAnswerByIds, 0);
        });
    });

    describe('addCompetencyDetailsToReview', () => {
        let evaluationType: PerformanceReviewEvaluationTypesValues;

        beforeEach(() => {
            evaluationType = faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes));
        });

        it('should return null when template has competenciesQuestionTemplateId as null and competencies exist', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [evaluationType],
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: faker.helpers.arrayElement([PerformanceReviewFormStatus.PENDING, PerformanceReviewFormStatus.IN_PROGRESS]),
            });

            // No existing competency questions
            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValueOnce([]);

            // User has competencies
            const competency = new Competency({
                id: faker.string.uuid(),
                instanceId: reviewCycle.instanceId,
                name: faker.word.words(),
                description: faker.word.words(),
                categoryId: null,
                createdAt: faker.date.recent(),
                updatedAt: faker.date.recent(),
                deletedAt: null,
            });
            competenciesPort.getCompetenciesOfUser.mockResolvedValueOnce([competency]);

            // Template has competenciesQuestionTemplateId as null
            const template = generateRandomTemplate(reviewCycle.formTemplates![0].templateId!, reviewCycle.instanceId);
            template.competenciesQuestionTemplateId = null;
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            await performanceReviewsService.addCompetencyDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.competencyQuestionsWithAnswer).toBeNull();

            checkMockCallsCount(repositoryMock.getCompetencyQuestionsWithAnswer, 1);
            checkMockCallsCount(competenciesPort.getCompetenciesOfUser, 1);
            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
        });

        it('should generate competency questions when template has competenciesQuestionTemplateId set', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [evaluationType],
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: faker.helpers.arrayElement([PerformanceReviewFormStatus.PENDING, PerformanceReviewFormStatus.IN_PROGRESS]),
            });

            // No existing competency questions
            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValueOnce([]);

            // User has competencies
            const competency = new Competency({
                id: faker.string.uuid(),
                instanceId: reviewCycle.instanceId,
                name: faker.word.words(),
                description: faker.word.words(),
                categoryId: null,
                createdAt: faker.date.recent(),
                updatedAt: faker.date.recent(),
                deletedAt: null,
            });
            competenciesPort.getCompetenciesOfUser.mockResolvedValueOnce([competency]);

            // Template has competenciesQuestionTemplateId set (not null)
            const template = generateRandomTemplate(reviewCycle.formTemplates![0].templateId!, reviewCycle.instanceId, undefined, true);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const questionTemplate = generateQuestionTemplateWithValues(template.competenciesQuestionTemplateId!, template.instanceId);
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValueOnce(questionTemplate);
            const coreFormQuestion = generateRandomCoreFormQuestion();
            formTemplatesPortMock.questionTemplateToCoreFormQuestion.mockReturnValueOnce(coreFormQuestion);

            await performanceReviewsService.addCompetencyDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.competencyQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.competencyQuestionsWithAnswer!).toHaveLength(1);
            expect(reviewForm.competencyQuestionsWithAnswer![0].competencyId).toBe(competency.id);
            expect(reviewForm.competencyQuestionsWithAnswer![0].cycleId).toBe(reviewCycle.id);
            expect(reviewForm.competencyQuestionsWithAnswer![0].userId).toBe(reviewForm.reviewedId);

            checkMockCallsCount(repositoryMock.getCompetencyQuestionsWithAnswer, 1);
            checkMockCallsCount(competenciesPort.getCompetenciesOfUser, 1);
            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 1);
        });

        it.each([
            PerformanceReviewFormStatus.FINISHED,
            PerformanceReviewFormStatus.EDIT_REQUESTED,
            PerformanceReviewFormStatus.EDIT_PENDING,
        ])(
            'returns null competency questions when no saved data and review status is %s (does not recalculate)',
            async (status) => {
                const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                    formTemplateTypes: [evaluationType],
                    status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
                });
                reviewCycle.formTemplates = [
                    generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, { evaluationType }),
                ];
                const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                    coreFormAnswerId: null,
                    type: evaluationType,
                    status,
                });
                repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValueOnce([]);

                await performanceReviewsService.addCompetencyDetailsToReview(reviewCycle, reviewForm);

                expect(reviewForm.competencyQuestionsWithAnswer).toBeNull();
                checkMockCallsCount(repositoryMock.getCompetencyQuestionsWithAnswer, 1);
                checkMockCallsCount(competenciesPort.getCompetenciesOfUser, 0);
                checkMockCallsCount(repositoryMock.getTemplateById, 0);
            },
        );

        it('recalculates competency questions when no saved data and review status is PENDING', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [evaluationType],
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
                status: PerformanceReviewFormStatus.PENDING,
            });
            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValueOnce([]);
            const competency = new Competency({
                id: faker.string.uuid(),
                instanceId: reviewCycle.instanceId,
                name: faker.word.words(),
                description: faker.word.words(),
                categoryId: null,
                createdAt: faker.date.recent(),
                updatedAt: faker.date.recent(),
                deletedAt: null,
            });
            competenciesPort.getCompetenciesOfUser.mockResolvedValueOnce([competency]);
            const template = generateRandomTemplate(reviewCycle.formTemplates![0].templateId!, reviewCycle.instanceId, undefined, true);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            const questionTemplate = generateQuestionTemplateWithValues(template.competenciesQuestionTemplateId!, template.instanceId);
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValueOnce(questionTemplate);
            formTemplatesPortMock.questionTemplateToCoreFormQuestion.mockReturnValueOnce(generateRandomCoreFormQuestion());

            await performanceReviewsService.addCompetencyDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.competencyQuestionsWithAnswer).not.toBeNull();
            expect(reviewForm.competencyQuestionsWithAnswer!).toHaveLength(1);
            checkMockCallsCount(repositoryMock.getCompetencyQuestionsWithAnswer, 1);
            checkMockCallsCount(competenciesPort.getCompetenciesOfUser, 1);
            checkMockCallsCount(repositoryMock.getTemplateById, 1);
        });

        it('should not add competency details for invalid cycle status', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([
                    PerformanceReviewCycleStatus.DRAFT,
                    PerformanceReviewCycleStatus.PENDING,
                    PerformanceReviewCycleStatus.SELECTION,
                ]),
            });
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                coreFormAnswerId: null,
                type: evaluationType,
            });

            await performanceReviewsService.addCompetencyDetailsToReview(reviewCycle, reviewForm);

            expect(reviewForm.competencyQuestionsWithAnswer).toBeNull();

            checkMockCallsCount(repositoryMock.getCompetencyQuestionsWithAnswer, 0);
            checkMockCallsCount(competenciesPort.getCompetenciesOfUser, 0);
            checkMockCallsCount(repositoryMock.getTemplateById, 0);
        });
    });

    describe('getPossibleDirectionsWithScore', () => {
        it('should include direction with score when template has only competenciesQuestionTemplateId set', async () => {
            const instanceId = fakePositiveInteger();
            const evaluationType = faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes));

            // Create a template with competenciesQuestionTemplateId set, no goalsProgressMode, no rating questions
            const template = generateRandomTemplate(undefined, instanceId, undefined, true);
            template.goalsProgressMode = null;
            template.goalsQuestionTemplateId = null;
            template.goalsQuestionTemplate = null;
            template.competenciesQuestionTemplate!.type = QUESTION_TEMPLATES_QUESTION_TYPES.RATING;

            const formTemplate = generatePerformanceReviewFormTemplateWithValues(fakePositiveInteger(), {
                evaluationType,
            });
            formTemplate.template = template;
            formTemplate.templateId = template.id;

            // Mock addFormDetailsToFormTemplates dependencies
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.addDraftsToFormTemplates.mockResolvedValueOnce(undefined);
            // No rating questions
            formTemplatesPortMock.getRatingQuestionsOfFormTemplates.mockResolvedValueOnce(new Map());

            const result = await performanceReviewsService.getPossibleDirectionsWithScore(instanceId, [formTemplate]);

            expect(result.possibleDirectionsWithScore).toHaveLength(1);
            expect(result.possibleDirectionsWithScore[0].evaluationType).toBe(evaluationType);
            expect(result.possibleDirectionsWithScore[0].templateId).toBe(template.id);
            expect(result.excludedDirections).toHaveLength(0);
        });

        it('should exclude direction when template has no rating questions, no goalsProgressMode, and no competenciesQuestionTemplateId', async () => {
            const instanceId = fakePositiveInteger();
            const evaluationType = faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes));

            // Create a template without competenciesQuestionTemplateId, no goalsProgressMode, no rating questions
            const template = generateRandomTemplate(undefined, instanceId);
            template.goalsProgressMode = null;
            template.goalsQuestionTemplateId = null;
            template.goalsQuestionTemplate = null;
            template.competenciesQuestionTemplateId = null;
            template.competenciesQuestionTemplate = null;

            const formTemplate = generatePerformanceReviewFormTemplateWithValues(fakePositiveInteger(), {
                evaluationType,
            });
            formTemplate.template = template;
            formTemplate.templateId = template.id;

            // Mock addFormDetailsToFormTemplates dependencies
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.addDraftsToFormTemplates.mockResolvedValueOnce(undefined);
            // No rating questions
            formTemplatesPortMock.getRatingQuestionsOfFormTemplates.mockResolvedValueOnce(new Map());

            const result = await performanceReviewsService.getPossibleDirectionsWithScore(instanceId, [formTemplate]);

            expect(result.possibleDirectionsWithScore).toHaveLength(0);
            expect(result.excludedDirections).toHaveLength(1);
            expect(result.excludedDirections[0]).toBe(evaluationType);
        });

        it('should return empty arrays when formTemplates is empty', async () => {
            const instanceId = fakePositiveInteger();

            const result = await performanceReviewsService.getPossibleDirectionsWithScore(instanceId, []);

            expect(result.possibleDirectionsWithScore).toHaveLength(0);
            expect(result.excludedDirections).toHaveLength(0);
        });
    });

    describe('findFormTemplateByEvaluationType', () => {
        let reviewTemplate: PerformanceReviewFormTemplate;

        beforeEach(() => {
            const fakeCycleId = fakePositiveInteger();
            reviewTemplate = generateRandomPerformanceReviewFormTemplate(fakeCycleId);
        });

        it('find review template successfully', async () => {
            repositoryMock.getFormTemplateByEvaluationType.mockResolvedValueOnce(reviewTemplate);

            const result = await performanceReviewsService.findFormTemplateByEvaluationType(reviewTemplate.cycleId, reviewTemplate.evaluationType);

            expect(result).toBe(reviewTemplate);
        });

        it('throw not found error if review template does not exist', async () => {
            repositoryMock.getFormTemplateByEvaluationType.mockResolvedValueOnce(null);

            await expect(
                performanceReviewsService.findFormTemplateByEvaluationType(reviewTemplate.cycleId, reviewTemplate.evaluationType),
            ).rejects.toThrow(NotFoundError);
        });
    });

    describe('addFormDetailsToFormTemplate', () => {
        let instanceId: number;
        let reviewTemplate: PerformanceReviewFormTemplate;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            const fakeCycleId = fakePositiveInteger();
            reviewTemplate = generateRandomPerformanceReviewFormTemplate(fakeCycleId);
            reviewTemplate.template = null;
        });

        it('add form details successfully to form template with null reference', async () => {
            reviewTemplate.template = null;
            reviewTemplate.templateId = null;

            await performanceReviewsService.addFormDetailsToFormTemplate(instanceId, reviewTemplate);

            expect(reviewTemplate.template).toBeNull();
            expect(reviewTemplate.ratingQuestionTemplates).toBeNull();

            checkMockCallsCount(repositoryMock.getTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('add form details successfully to form template with deleted form template', async () => {
            repositoryMock.getTemplateById.mockResolvedValueOnce(null);

            await performanceReviewsService.addFormDetailsToFormTemplate(instanceId, reviewTemplate);

            expect(reviewTemplate.template).toBeNull();
            expect(reviewTemplate.ratingQuestionTemplates).toBeNull();

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
        });

        it('add form details successfully to form template with non deleted form template', async () => {
            const template = generateRandomTemplate(reviewTemplate.templateId!, instanceId);
            const formTemplate = generateFormTemplateWithValues(template.formTemplateId!, instanceId);
            formTemplate.draft = generateFormDraftWithQuestions(formTemplate.draftId!);
            template.formTemplate = formTemplate;
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            const referenceIds = getFormDraftContentReferenceIds(formTemplate!.draft!.content);
            const questionTemplates = referenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);

            await performanceReviewsService.addFormDetailsToFormTemplate(instanceId, reviewTemplate);

            expect(reviewTemplate.template).not.toBeNull();
            expect(reviewTemplate.template!.formTemplate).not.toBeNull();
            expect(reviewTemplate.template!.id).toBe(reviewTemplate.templateId);
            expect(reviewTemplate.ratingQuestionTemplates).not.toBeNull();
            expect(reviewTemplate.ratingQuestionTemplates).toHaveLength(referenceIds.length);

            reviewTemplate.ratingQuestionTemplates?.forEach((questionTemplate, index) => {
                expect(questionTemplate).toBeInstanceOf(QuestionTemplate);
                expect(questionTemplate.id).toBe(referenceIds[index]);
            });

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
        });
    });

    describe('addFormDetailsToFormTemplates', () => {
        let instanceId: number;
        let reviewTemplates: PerformanceReviewFormTemplate[];

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            const fakeCycleId = fakePositiveInteger();
            reviewTemplates = [generateRandomPerformanceReviewFormTemplate(fakeCycleId), generateRandomPerformanceReviewFormTemplate(fakeCycleId)];
            reviewTemplates.forEach((reviewTemplate) => {
                reviewTemplate.template = null;
            });
        });

        it('add form details successfully to form template with null reference', async () => {
            reviewTemplates.forEach((reviewTemplate) => {
                reviewTemplate.template = null;
                reviewTemplate.templateId = null;
            });

            await performanceReviewsService.addFormDetailsToFormTemplates(instanceId, reviewTemplates);

            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.template === null)).toBeTrue();
            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.ratingQuestionTemplates === null)).toBeTrue();

            checkMockCallsCount(formTemplatesPortMock.findFormTemplatesById, 0);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplates, 0);
        });

        it('add form details successfully to form template with deleted form template', async () => {
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([]);

            await performanceReviewsService.addFormDetailsToFormTemplates(instanceId, reviewTemplates);

            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.template === null)).toBeTrue();
            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.ratingQuestionTemplates === null)).toBeTrue();

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplates, 0);
        });

        it('add form details successfully to form template with non deleted form template', async () => {
            const templates = [
                generateRandomTemplate(reviewTemplates[0].templateId!, instanceId),
                generateRandomTemplate(reviewTemplates[1].templateId!, instanceId),
            ];
            const formTemplates = [
                generateFormTemplateWithValues(templates[0].formTemplateId!, instanceId),
                generateFormTemplateWithValues(templates[1].formTemplateId!, instanceId),
            ];
            formTemplates.forEach((formTemplate) => {
                formTemplate.draft = generateFormDraftWithQuestions(formTemplate.draftId!);
            });
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce(templates);
            const referenceIdsByFormTemplateId = new Map<number, number[]>();
            formTemplates.forEach((formTemplate) => {
                const referenceIds = getFormDraftContentReferenceIds(formTemplate.draft!.content);
                referenceIdsByFormTemplateId.set(formTemplate.id!, referenceIds);
            });
            const allReferenceIds = Array.from(referenceIdsByFormTemplateId.values()).flat();
            const questionTemplates = allReferenceIds.map((id) =>
                generateQuestionTemplateWithValues(id, instanceId, { type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING }),
            );
            formTemplatesPortMock.findFormTemplatesById.mockResolvedValueOnce({
                [formTemplates[0].id!]: formTemplates[0],
                [formTemplates[1].id!]: formTemplates[1],
            });
            formTemplatesPortMock.getRatingQuestionsOfFormTemplates.mockResolvedValueOnce(
                new Map([
                    [
                        formTemplates[0].id!,
                        questionTemplates.filter((questionTemplate) =>
                            referenceIdsByFormTemplateId.get(formTemplates[0].id!)!.includes(questionTemplate.id!),
                        ),
                    ],
                    [
                        formTemplates[1].id!,
                        questionTemplates.filter((questionTemplate) =>
                            referenceIdsByFormTemplateId.get(formTemplates[1].id!)!.includes(questionTemplate.id!),
                        ),
                    ],
                ]),
            );

            await performanceReviewsService.addFormDetailsToFormTemplates(instanceId, reviewTemplates);

            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.template !== null)).toBeTrue();
            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.template!.formTemplate !== null)).toBeTrue();
            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.template!.id === reviewTemplate.templateId)).toBeTrue();
            expect(reviewTemplates.every((reviewTemplate) => reviewTemplate.ratingQuestionTemplates !== null)).toBeTrue();
            expect(
                reviewTemplates.every(
                    (reviewTemplate) =>
                        reviewTemplate.ratingQuestionTemplates!.length ===
                        referenceIdsByFormTemplateId.get(reviewTemplate.template!.formTemplateId!)!.length,
                ),
            ).toBeTrue();

            reviewTemplates.forEach((reviewTemplate) => {
                reviewTemplate.ratingQuestionTemplates?.forEach((questionTemplate) => {
                    expect(questionTemplate).toBeInstanceOf(QuestionTemplate);
                    expect(referenceIdsByFormTemplateId.get(reviewTemplate.template!.formTemplateId!)!.includes(questionTemplate.id!)).toBeTrue();
                });
            });

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplates, 1);
        });
    });

    describe('fillReview', () => {
        let fillParams: FillPerformanceReviewFormReviewInterface;
        let fakeId: number;
        let fakeCycleId: number;
        let fakeGoalCycleId: number;
        let evaluationType: PerformanceReviewEvaluationTypesValues;
        let status: string;
        let coreFormAnswerId: number | null;
        let reviewCycle: PerformanceReviewCycle;
        let reviewTemplate: PerformanceReviewFormTemplate;
        let reviewForm: PerformanceReviewFormReview;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            evaluationType = faker.helpers.arrayElement([
                PerformanceReviewEvaluationTypes.AUTO_REVIEW,
                PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                PerformanceReviewEvaluationTypes.PEER_REVIEW,
            ]);
            fakeCycleId = fakePositiveInteger();
            fakeGoalCycleId = fakePositiveInteger();
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, { evaluationType, withScore: false });
            status = faker.helpers.arrayElement([
                PerformanceReviewFormStatus.PENDING,
                PerformanceReviewFormStatus.IN_PROGRESS,
                PerformanceReviewFormStatus.EDIT_PENDING,
            ]);
            coreFormAnswerId = status === PerformanceReviewFormStatus.EDIT_PENDING ? fakePositiveInteger() : null;
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId,
                status,
                type: reviewTemplate.evaluationType,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                questionAnswers: [],
            };
            calibrationServiceMock.getClaimedCalibrationReviewForReviewed.mockResolvedValue(null);
        });

        it('fill form successfully without score and without goals and without competencies', async () => {
            reviewCycle.goalCycleId = null;
            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });
            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.score).toBe(reviewForm.score);
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 0);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 0);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 0);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill subordinate form successfully without score, with goals, existing goal questions and manual mode (no weights)', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                withScore: false,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId,
                status,
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                questionAnswers: [{ answer: '0', comment: null, questionId: '123' }],
            };

            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const goalQuestionWithAnswer = generateGoalQuestionWithAnswer(fillParams.questionAnswers[0].questionId);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([goalQuestionWithAnswer]);

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });
            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.score).toBe(reviewForm.score);
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 1);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 1);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 1);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill subordinate form successfully with score calculated from goals and form, existing goal questions, manual mode and weights obtained from db', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                formWeight: 50,
                goalsWeight: 50,
                scaleEnd: 10,
                scaleStart: 0,
                withGoalScore: true,
                withScore: true,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId,
                status,
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                goalCycleId: fakeGoalCycleId,
                questionAnswers: [{ answer: '0', comment: null, questionId: '123' }],
            };

            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const goalQuestionWithAnswer = generateGoalQuestionWithAnswer(fillParams.questionAnswers[0].questionId);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([goalQuestionWithAnswer]);

            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([
                generateRandomGoalWeight(fakeGoalCycleId, goalQuestionWithAnswer.userId, goalQuestionWithAnswer.goalId, 100),
            ]);

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });

            const formScore = 100;
            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(formScore);
            const goalsScore = 50;
            coreFormsPortMock.calculateGoalScore.mockReturnValueOnce({
                partialGoalScoreMap: { [goalQuestionWithAnswer.goalId]: fakePositiveInteger() },
                score: goalsScore,
            });

            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.formScore).toBe(formScore);
            expect(result.goalsScore).toBe(goalsScore);
            expect(result.score).toBe(75);
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 1);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 1);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 1);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(coreFormsPortMock.calculateGoalScore, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill subordinate form successfully with score calculated from goals and form, existing goal questions, AUTO mode and generated obtained from db. Empty progress map', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                formWeight: 50,
                goalsWeight: 50,
                scaleEnd: 10,
                scaleStart: 0,
                withGoalScore: true,
                withScore: true,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId,
                status,
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                goalCycleId: fakeGoalCycleId,
                questionAnswers: [{ answer: '0', comment: null, questionId: '123' }],
            };

            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.AUTO);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const goalQuestionWithAnswer = generateGoalQuestionWithAnswer(
                fillParams.questionAnswers[0].questionId,
                fakePositiveInteger(),
                QUESTION_TYPES.RATING,
                '50.00',
                100,
            );
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([goalQuestionWithAnswer]);

            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([
                generateRandomGoalWeight(fakeGoalCycleId, goalQuestionWithAnswer.userId, goalQuestionWithAnswer.goalId, 100),
            ]);

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });

            const formScore = 100;
            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(formScore);

            goalsPort.getGoalQuestionsProgressUsingFormulas.mockResolvedValueOnce({});

            const goalsScore = (parseFloat(goalQuestionWithAnswer.progress) * goalQuestionWithAnswer.weight) / 100;

            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.formScore).toBe(formScore);
            expect(result.goalsScore).toBe(goalsScore);
            expect(result.score).toBe(75);
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 0);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 0);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 0);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(coreFormsPortMock.calculateGoalScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill subordinate form successfully with score calculated from goals and form, generated goal questions (should save), AUTO mode and generated weights. Empty progress map', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                formWeight: 50,
                goalsWeight: 50,
                scaleEnd: 10,
                scaleStart: 0,
                withGoalScore: true,
                withScore: true,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId: null,
                // In edit pending status, the form not autogenerated goals questions
                status: faker.helpers.arrayElement([PerformanceReviewFormStatus.PENDING, PerformanceReviewFormStatus.IN_PROGRESS]),
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                goalCycleId: fakeGoalCycleId,
                questionAnswers: [{ answer: '0', comment: null, questionId: '123' }],
            };

            reviewCycle.goalCycleId = fakeGoalCycleId;

            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.AUTO);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            const validGoalStatuses = [GoalStatus.FINISHED, GoalStatus.IN_PROGRESS];
            const goal = generateRandomGoal({ progress: '50.00', status: faker.helpers.arrayElement(validGoalStatuses) });
            goalsPort.getGoalsOfOwnerInCycle.mockResolvedValueOnce([goal]);
            const goalWeight = generateRandomGoalWeight(fakeGoalCycleId, reviewForm.reviewedId, goal.id, 100);
            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([goalWeight]);

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });

            goalsPort.getGoalQuestionsProgressUsingFormulas.mockResolvedValueOnce({});

            const formScore = 100;
            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(formScore);

            const goalsScore = parseFloat(goal.progress);

            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.formScore).toBe(formScore);
            expect(result.goalsScore).toBe(goalsScore);
            expect(result.score).toBe(75);
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 0);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 0);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 0);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 1);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(coreFormsPortMock.calculateGoalScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill subordinate form successfully with score, with both form and goals score null, existing goal questions, manual mode and generated weights (no db)', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                formWeight: 50,
                goalsWeight: 50,
                scaleEnd: 10,
                scaleStart: 1,
                withGoalScore: true,
                withScore: true,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId,
                status,
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                goalCycleId: fakeGoalCycleId,
                questionAnswers: [{ answer: '0', comment: null, questionId: '123' }],
            };

            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const goalQuestionWithAnswer = generateGoalQuestionWithAnswer(fillParams.questionAnswers[0].questionId);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([goalQuestionWithAnswer]);

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([]);

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(null);
            coreFormsPortMock.calculateGoalScore.mockReturnValueOnce({
                partialGoalScoreMap: {},
                score: null,
            });

            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.score).toBeNull();
            expect(result.goalsScore).toBeNull();
            expect(result.formScore).toBeNull();
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 1);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 1);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 1);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(coreFormsPortMock.calculateGoalScore, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill subordinate form successfully with score, with goalsScore null, existing goal questions, manual mode and generated weights (no db)', async () => {
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                formWeight: 50,
                goalsWeight: 50,
                scaleEnd: 10,
                scaleStart: 1,
                withGoalScore: true,
                withScore: true,
            });
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId,
                status,
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            fillParams = {
                fillingTime: fakePositiveInteger(),
                goalCycleId: fakeGoalCycleId,
                questionAnswers: [{ answer: '0', comment: null, questionId: '123' }],
            };

            const template = generateRandomTemplate(fakePositiveInteger(), reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const goalQuestionWithAnswer = generateGoalQuestionWithAnswer(fillParams.questionAnswers[0].questionId);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([goalQuestionWithAnswer]);

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            goalsPort.getGoalsWeightForOwnerInCycle.mockResolvedValueOnce([]);

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });

            const formScore = 100;

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(formScore);
            coreFormsPortMock.calculateGoalScore.mockReturnValueOnce({
                partialGoalScoreMap: {},
                score: null,
            });

            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.score).toBe(100);
            expect(result.goalsScore).toBeNull();
            expect(result.formScore).toBe(formScore);
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 1);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 1);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 1);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 1);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 1);
            checkMockCallsCount(goalsPort.getGoalsWeightForOwnerInCycle, 0);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(coreFormsPortMock.calculateGoalScore, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('fill form successfully with score (no subordinate review, score is equal to form score)', async () => {
            reviewCycle.goalCycleId = null;
            reviewTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                formWeight: 50,
                goalsWeight: 50,
                scaleEnd: 10,
                scaleStart: 0,
                withGoalScore: false,
                withScore: true,
            });
            reviewForm.goalsScore = null;
            const formScore = 50;
            reviewTemplate.scoreConfig!.withScore = true;

            coreFormsPortMock.submitFormAnswer.mockImplementationOnce((formTag, formId) => {
                const formAnswer = generateFormAnswerWithValues(fakePositiveInteger(), formTag, formId, []);
                formAnswer.questionAnswers = Array(faker.number.int({ max: 3, min: 1 }))
                    .fill(1)
                    .map(() => generateRandomFormQuestionAnswers(formAnswer.id));
                formAnswer.coreForm = generateRandomCoreFormWithQuestions(formTag, formId);
                return Promise.resolve(formAnswer);
            });

            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValue([]);
            competenciesPort.getCompetenciesOfUser.mockResolvedValue([]);

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(formScore);

            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.FINISHED);
            expect(result.formScore).toBe(50);
            expect(result.score).toBe(50);
            expect(result.goalsScore).toBeNull();
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.coreForm).not.toBeNull();
            expect(result.coreForm).toBeInstanceOf(CoreForm);
            expect(result.coreForm?.tag).toBe(reviewForm.coreFormTag);
            expect(result.coreForm?.id).toBe(reviewForm.coreFormId);
            expect(result.formAnswer).not.toBeNull();
            expect(result.formAnswer).toBeInstanceOf(FormAnswer);
            expect(result.formAnswer?.id).toBe(result.coreFormAnswerId);
            expect(result.formAnswer?.formId).toBe(reviewForm.coreFormId);
            expect(result.formAnswer?.tag).toBe(reviewForm.coreFormTag);

            checkMockCallsCount(repositoryMock.getTemplateById, 0);
            checkMockCallsCount(repositoryMock.getGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(goalsPort.getGoalsOfOwnerInCycle, 0);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(formTemplatesPortMock.questionTemplateToCoreFormQuestion, 0);
            checkMockCallsCount(coreFormsPortMock.generateAnswerString, 0);
            checkMockCallsCount(coreFormsPortMock.generateCommentString, 0);
            checkMockCallsCount(coreFormsPortMock.validateQuestionRequired, 0);
            checkMockCallsCount(repositoryMock.upsertGoalQuestionsWithAnswer, 0);
            checkMockCallsCount(coreFormsPortMock.submitFormAnswer, 1);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('throw bad request error if user is not reviewer', async () => {
            const userId = faker.number.int({ min: reviewForm.reviewerId! + 1 });

            await expect(performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, userId, fillParams)).rejects.toThrow(
                BadRequestError,
            );
        });

        it('throw bad request error if form was already completed and not in edit pending state', async () => {
            reviewForm = generatePerformanceReviewFormReviewWithValues(reviewForm.id, reviewForm.cycleId, {
                coreFormAnswerId: fakePositiveInteger(),
                status: PerformanceReviewFormStatus.FINISHED,
            });

            await expect(
                performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams),
            ).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if form is not pending, in progress or edit requested', async () => {
            reviewForm = generatePerformanceReviewFormReviewWithValues(reviewForm.id, reviewForm.cycleId, {
                coreFormAnswerId: null,
                status: faker.helpers.arrayElement([PerformanceReviewFormStatus.FINISHED, PerformanceReviewFormStatus.OMITTED]),
            });

            await expect(
                performanceReviewsService.fillReview(reviewCycle, reviewForm, reviewTemplate, reviewForm.reviewerId!, fillParams),
            ).rejects.toThrow(BadRequestError);
        });
    });

    describe('requestReviewEdit', () => {
        let formReviewEditParams: RequestPerformanceReviewFormReviewEditInterface;
        let reviewForm: PerformanceReviewFormReview;
        let fakeEditRequestId: number;
        let fakeDate: Date;

        beforeEach(() => {
            fakeEditRequestId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            const fakeId = fakePositiveInteger();
            const fakeCycleId = fakePositiveInteger();
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId: fakePositiveInteger(),
                status: PerformanceReviewFormStatus.FINISHED,
                type: faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes)),
            });
            formReviewEditParams = {
                description: faker.word.words(),
            };
            calibrationServiceMock.getClaimedCalibrationReviewForReviewed.mockResolvedValue(null);
        });

        it('edit request successfully', async () => {
            repositoryMock.createEditRequest.mockImplementationOnce((params) => {
                const newEditRequest = new PerformanceReviewFormReviewEditRequest({
                    createdAt: fakeDate,
                    cycleId: params.cycleId,
                    deletedAt: null,
                    id: fakeEditRequestId,
                    requestDescription: params.requestDescription,
                    reviewFormId: params.reviewFormId,
                    reviewType: params.reviewType,
                    reviewedId: params.reviewedId,
                    reviewerId: params.reviewerId,
                    status: params.status,
                    updatedAt: fakeDate,
                });
                return Promise.resolve(newEditRequest);
            });
            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.requestReviewEdit(reviewForm, reviewForm.reviewerId!, formReviewEditParams);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.status).toBe(PerformanceReviewFormStatus.EDIT_REQUESTED);
            expect(result.coreFormAnswerId).not.toBeNull();
            expect(result.score).not.toBeNull();

            checkMockCallsCount(repositoryMock.createEditRequest, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('throw bad request error if user is not reviewer', async () => {
            const userId = faker.number.int({ min: reviewForm.reviewerId! + 1 });

            await expect(performanceReviewsService.requestReviewEdit(reviewForm, userId, formReviewEditParams)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if form is not finished', async () => {
            reviewForm = generatePerformanceReviewFormReviewWithValues(reviewForm.id, reviewForm.cycleId, {
                coreFormAnswerId: fakePositiveInteger(),
                status: faker.helpers.arrayElement([PerformanceReviewFormStatus.IN_PROGRESS, PerformanceReviewFormStatus.PENDING]),
            });
            coreFormsPortMock.getForm.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.requestReviewEdit(reviewForm, reviewForm.reviewerId!, formReviewEditParams)).rejects.toThrow(
                BadRequestError,
            );
        });
    });

    describe('reviewEditRequest', () => {
        let fakeId: number;
        let fakeCycleId: number;
        let reviewForm: PerformanceReviewFormReview;
        let editRequest: PerformanceReviewFormReviewEditRequest;
        let params: ReviewPerformanceReviewFormReviewEditRequestInterface;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeCycleId = fakePositiveInteger();
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, fakeCycleId, {
                coreFormAnswerId: fakePositiveInteger(),
                status: PerformanceReviewFormStatus.EDIT_REQUESTED,
                type: faker.helpers.arrayElement(Object.values(PerformanceReviewEvaluationTypes)),
            });
            editRequest = generateRandomEditRequest({
                cycleId: fakeCycleId,
                reviewFormId: reviewForm.id,
                reviewType: reviewForm.type,
                reviewedId: reviewForm.reviewedId,
                reviewerId: fakeId,
                status: PerformanceReviewFormEditRequestStatus.PENDING,
            });
            params = {
                description: faker.word.words(),
                status: faker.helpers.arrayElement(Object.values(PerformanceReviewFormEditRequestFinalStatus)),
            };
        });

        it('resolve edit request successfully', async () => {
            repositoryMock.saveEditRequest.mockImplementationOnce((editRequest) => Promise.resolve(editRequest));
            repositoryMock.saveFormReview.mockImplementationOnce((formReview) => Promise.resolve(formReview));

            const result = await performanceReviewsService.reviewEditRequest(reviewForm, editRequest, params);

            expect(result).toBeInstanceOf(PerformanceReviewFormReviewEditRequest);
            expect(result.id).toBe(editRequest.id);
            expect(result.status).toBe(params.status);
            expect(result.resolutionDescription).toBe(params.description);

            checkMockCallsCount(repositoryMock.saveEditRequest, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('throw bad request error if form is not on edit requested state', async () => {
            reviewForm = generatePerformanceReviewFormReviewWithValues(reviewForm.id, reviewForm.cycleId, {
                coreFormAnswerId: fakePositiveInteger(),
                status: faker.helpers.arrayElement([
                    PerformanceReviewFormStatus.IN_PROGRESS,
                    PerformanceReviewFormStatus.PENDING,
                    PerformanceReviewFormStatus.FINISHED,
                    PerformanceReviewFormStatus.EDIT_PENDING,
                ]),
            });

            await expect(performanceReviewsService.reviewEditRequest(reviewForm, editRequest, params)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if edit request is not on pending', async () => {
            editRequest = generateRandomEditRequest({
                cycleId: fakeCycleId,
                reviewFormId: reviewForm.id,
                reviewerId: fakeId,
                status: faker.helpers.arrayElement([
                    PerformanceReviewFormEditRequestStatus.APPROVED,
                    PerformanceReviewFormEditRequestStatus.REJECTED,
                ]),
            });

            await expect(performanceReviewsService.reviewEditRequest(reviewForm, editRequest, params)).rejects.toThrow(BadRequestError);
        });
    });

    describe('listVisibleEditRequests', () => {
        let editRequest: PerformanceReviewFormReviewEditRequest;

        beforeEach(() => {
            const fakeCycleId = fakePositiveInteger();
            editRequest = generateRandomEditRequest({ cycleId: fakeCycleId });
        });

        it('successful if no edit requests exist', async () => {
            repositoryMock.getAllEditRequests.mockResolvedValueOnce({ editRequests: [], totalCount: 0 });

            const result = await performanceReviewsService.listVisibleEditRequests(editRequest.cycleId, {});

            expect(result.totalCount).toBe(0);
            expect(result.editRequests).toHaveLength(0);
        });

        it('successful if one edit request exists', async () => {
            repositoryMock.getAllEditRequests.mockResolvedValueOnce({ editRequests: [editRequest], totalCount: 1 });

            const result = await performanceReviewsService.listVisibleEditRequests(editRequest.cycleId, {});

            expect(result.totalCount).toBe(1);
            expect(result.editRequests).toHaveLength(1);
            expect(result.editRequests[0]).toBe(editRequest);
        });

        it('successful if one edit request exists and options are provided', async () => {
            repositoryMock.getAllEditRequests.mockResolvedValueOnce({ editRequests: [editRequest], totalCount: 1 });

            const result = await performanceReviewsService.listVisibleEditRequests(editRequest.cycleId, {
                pagination: {
                    limit: fakePositiveInteger(),
                    offset: fakePositiveInteger(),
                },
                sortingOptions: [['id', 'desc']],
                statuses: [editRequest.status],
            });

            expect(result.totalCount).toBe(1);
            expect(result.editRequests).toHaveLength(1);
            expect(result.editRequests[0]).toBe(editRequest);
        });
    });

    describe('shareReview', () => {
        let cycleId: number;
        let reviewForm: PerformanceReviewFormReview;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                shareStatus: PerformanceReviewFormShareStatus.NOT_SHARED,
                status: PerformanceReviewFormStatus.FINISHED,
            });
        });

        it('share review if user is reviewer', async () => {
            repositoryMock.saveFormReview.mockImplementationOnce((reviewForm) => Promise.resolve(reviewForm));

            const result = await performanceReviewsService.shareReview(reviewForm, reviewForm.reviewerId!, false);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);

            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('share review if user is not reviewer, but is admin', async () => {
            repositoryMock.saveFormReview.mockImplementationOnce((reviewForm) => Promise.resolve(reviewForm));

            const userId = faker.number.int({ min: reviewForm.reviewerId! + 1 });
            const result = await performanceReviewsService.shareReview(reviewForm, userId, true);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);

            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('throw bad request error if user is not reviewer and not admin', async () => {
            const userId = faker.number.int({ min: reviewForm.reviewerId! + 1 });

            await expect(performanceReviewsService.shareReview(reviewForm, userId, false)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if form is in an invalid share status', async () => {
            reviewForm.shareStatus = faker.helpers.arrayElement([
                PerformanceReviewFormShareStatus.SHARE_DISABLED,
                PerformanceReviewFormShareStatus.SHARED_CONFIRMED,
                PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED,
            ]);

            await expect(performanceReviewsService.shareReview(reviewForm, reviewForm.reviewerId!, false)).rejects.toThrow(BadRequestError);
        });
    });

    describe('shareReviewsWithTargetUser', () => {
        let cycleId: number;
        let userId: number;
        let responsibleId: number;
        let targetUser: PerformanceReviewTargetUser;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            userId = fakePositiveInteger();
            responsibleId = fakePositiveInteger();
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId, userId, responsibleId, PerformanceReviewFormShareStatus.NOT_SHARED);
            reviewCycle = generateRandomPerformanceReviewCycle();
        });

        it('share reviews if user is responsible and cycle has subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                status: PerformanceReviewCycleStatus.FINISHED,
            });
            repositoryMock.saveTargetUser.mockImplementationOnce((targetUser) => Promise.resolve(targetUser));
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.shareReviewsWithTargetUser(targetUser, responsibleId, false, reviewCycle, null);

            expect(result).toBeInstanceOf(PerformanceReviewTargetUser);
            expect(result.id).toBe(targetUser.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);

            checkMockCallsCount(repositoryMock.saveTargetUser, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
        });

        it('share reviews if user is responsible with summary in subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                status: PerformanceReviewCycleStatus.FINISHED,
                withSummary: true,
            });
            repositoryMock.saveTargetUser.mockImplementationOnce((targetUser) => Promise.resolve(targetUser));
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce([]);
            reviewCycle.withSummary = true;

            const summary = 'hello';
            const result = await performanceReviewsService.shareReviewsWithTargetUser(targetUser, responsibleId, false, reviewCycle, summary);

            expect(result).toBeInstanceOf(PerformanceReviewTargetUser);
            expect(result.id).toBe(targetUser.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);
            expect(result.summary).toBe(summary);

            checkMockCallsCount(repositoryMock.saveTargetUser, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
        });

        it('share reviews if user is not responsible, but is admin', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW],
                status: PerformanceReviewCycleStatus.FINISHED,
                withSummary: true,
            });
            repositoryMock.saveTargetUser.mockImplementationOnce((targetUser) => Promise.resolve(targetUser));
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce([]);

            const loggedUserId = faker.number.int({ min: responsibleId + 1 });
            const result = await performanceReviewsService.shareReviewsWithTargetUser(targetUser, loggedUserId, true, reviewCycle, null);

            expect(result).toBeInstanceOf(PerformanceReviewTargetUser);
            expect(result.id).toBe(targetUser.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);

            checkMockCallsCount(repositoryMock.saveTargetUser, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
        });

        it('throw bad request error if user is not responsible and not admin', async () => {
            const loggedUserId = faker.number.int({ min: responsibleId + 1 });

            await expect(performanceReviewsService.shareReviewsWithTargetUser(targetUser, loggedUserId, false, reviewCycle, null)).rejects.toThrow(
                BadRequestError,
            );
        });

        it('throw bad request error if is responsible and cycle has not subordinate review', async () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: Object.values(PerformanceReviewEvaluationTypes).filter(
                    (type) => type !== PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                ),
            });

            await expect(performanceReviewsService.shareReviewsWithTargetUser(targetUser, responsibleId, false, reviewCycle, null)).rejects.toThrow(
                BadRequestError,
            );
        });

        it('throw bad request error if target user is in an invalid share status', async () => {
            targetUser.shareStatus = faker.helpers.arrayElement([
                PerformanceReviewFormShareStatus.SHARE_DISABLED,
                PerformanceReviewFormShareStatus.SHARED_CONFIRMED,
                PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED,
            ]);

            await expect(performanceReviewsService.shareReviewsWithTargetUser(targetUser, responsibleId, false, reviewCycle, null)).rejects.toThrow(
                BadRequestError,
            );
        });

        it('throw bad request error if summary is received but it does not accept a summary', async () => {
            reviewCycle.withSummary = false;

            await expect(
                performanceReviewsService.shareReviewsWithTargetUser(targetUser, responsibleId, false, reviewCycle, 'hello'),
            ).rejects.toThrow(BadRequestError);
        });
    });

    describe('sharePendingReviewsOfCycle', () => {
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: PerformanceReviewCycleStatus.FINISHED,
            });
        });

        it('share pending reviews of a finished cycle', async () => {
            const affectedTargetUsersCount = faker.number.int({ max: 5, min: 1 });
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce([]);
            repositoryMock.updateShareStatusOfTargetUsersByShareStatus.mockImplementationOnce((cycleId, _, shareStatus) => {
                const affectedTargetUsers = Array(affectedTargetUsersCount)
                    .fill(1)
                    .map(() => generateRandomPerformanceReviewTargetUser(cycleId, fakePositiveInteger(), fakePositiveInteger(), shareStatus));
                return Promise.resolve(affectedTargetUsers);
            });

            const result = await performanceReviewsService.sharePendingReviewsOfCycle(reviewCycle);

            expect(result).toHaveLength(affectedTargetUsersCount);

            result.forEach((targetUser) => {
                expect(targetUser.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);
            });

            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfTargetUsersByShareStatus, 1);
        });

        it('share pending reviews of a finished cycle restricting target users', async () => {
            const affectedTargetUsersCount = faker.number.int({ max: 5, min: 1 });
            const formReviewsCount = faker.number.int({ max: affectedTargetUsersCount, min: 1 });
            const affectedFormReviews = Array(formReviewsCount)
                .fill(1)
                .map(() =>
                    generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, {
                        reviewedId: fakePositiveInteger(),
                        shareStatus: PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED,
                        status: PerformanceReviewFormStatus.FINISHED,
                    }),
                );
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce(affectedFormReviews);
            repositoryMock.updateShareStatusOfTargetUsersByShareStatus.mockImplementationOnce((cycleId, _, shareStatus, userIds) => {
                const affectedTargetUsers = Array(affectedTargetUsersCount)
                    .fill(1)
                    .map(() => {
                        const userId = userIds !== undefined ? faker.helpers.arrayElement(userIds) : fakePositiveInteger();
                        return generateRandomPerformanceReviewTargetUser(cycleId, userId, fakePositiveInteger(), shareStatus);
                    });
                return Promise.resolve(affectedTargetUsers);
            });

            const result = await performanceReviewsService.sharePendingReviewsOfCycle(reviewCycle, true);

            expect(result).toHaveLength(affectedTargetUsersCount);

            const userIdsSet = new Set(affectedFormReviews.map((formReview) => formReview.reviewedId));
            result.forEach((targetUser) => {
                expect(targetUser.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED);
                expect(userIdsSet.has(targetUser.userId)).toBe(true);
            });

            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfTargetUsersByShareStatus, 1);
        });

        it('throw bad request error if cycle is not finished', async () => {
            reviewCycle.status = faker.helpers.arrayElement([
                PerformanceReviewCycleStatus.IN_PROGRESS,
                PerformanceReviewCycleStatus.DRAFT,
                PerformanceReviewCycleStatus.PENDING,
            ]);

            await expect(performanceReviewsService.sharePendingReviewsOfCycle(reviewCycle)).rejects.toThrow(BadRequestError);
        });
    });

    describe('confirmReview', () => {
        let cycleId: number;
        let reviewForm: PerformanceReviewFormReview;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewForm = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, {
                shareStatus: PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED,
                status: PerformanceReviewFormStatus.FINISHED,
            });
        });

        it('share review if user is reviewed user', async () => {
            repositoryMock.saveFormReview.mockImplementationOnce((reviewForm) => Promise.resolve(reviewForm));

            const result = await performanceReviewsService.confirmReview(reviewForm, reviewForm.reviewedId);

            expect(result).toBeInstanceOf(PerformanceReviewFormReview);
            expect(result.id).toBe(reviewForm.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_CONFIRMED);

            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('throw bad request error if user is not reviewed user', async () => {
            const userId = faker.number.int({ min: reviewForm.reviewedId + 1 });

            await expect(performanceReviewsService.confirmReview(reviewForm, userId)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if form is in an invalid share status', async () => {
            reviewForm.shareStatus = faker.helpers.arrayElement([
                PerformanceReviewFormShareStatus.SHARE_DISABLED,
                PerformanceReviewFormShareStatus.NOT_SHARED,
                PerformanceReviewFormShareStatus.SHARED_CONFIRMED,
            ]);

            await expect(performanceReviewsService.confirmReview(reviewForm, reviewForm.reviewedId)).rejects.toThrow(BadRequestError);
        });
    });

    describe('confirmReviewOfTargetUser', () => {
        let cycleId: number;
        let targetUser: PerformanceReviewTargetUser;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const responsibleId = fakePositiveInteger();
            targetUser = generateRandomPerformanceReviewTargetUser(
                cycleId,
                userId,
                responsibleId,
                PerformanceReviewFormShareStatus.SHARED_UNCONFIRMED,
            );
        });

        it('share review if user is target user', async () => {
            repositoryMock.saveTargetUser.mockImplementationOnce((targetUser) => Promise.resolve(targetUser));
            repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.confirmReviewOfTargetUser(targetUser, targetUser.userId);

            expect(result).toBeInstanceOf(PerformanceReviewTargetUser);
            expect(result.id).toBe(targetUser.id);
            expect(result.shareStatus).toBe(PerformanceReviewFormShareStatus.SHARED_CONFIRMED);

            checkMockCallsCount(repositoryMock.saveTargetUser, 1);
            checkMockCallsCount(repositoryMock.updateShareStatusOfFinishedFormReviewsByShareStatus, 1);
        });

        it('throw bad request error if user is not target user', async () => {
            const userId = faker.number.int({ min: targetUser.userId + 1 });

            await expect(performanceReviewsService.confirmReviewOfTargetUser(targetUser, userId)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if target user is in an invalid share status', async () => {
            targetUser.shareStatus = faker.helpers.arrayElement([
                PerformanceReviewFormShareStatus.SHARE_DISABLED,
                PerformanceReviewFormShareStatus.NOT_SHARED,
                PerformanceReviewFormShareStatus.SHARED_CONFIRMED,
            ]);

            await expect(performanceReviewsService.confirmReviewOfTargetUser(targetUser, targetUser.userId)).rejects.toThrow(BadRequestError);
        });
    });

    describe('syncReviewUsers', () => {
        let users: User[];

        beforeEach(() => {
            const userCount = faker.number.int({ max: 3, min: 1 });
            users = Array(userCount)
                .fill(1)
                .map(() => generateRandomUser());
        });

        it('sync review users if none are provided', async () => {
            repositoryMock.upsertReviewUsers.mockResolvedValueOnce([]);

            await expect(performanceReviewsService.syncReviewUsers([])).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.upsertReviewUsers, 1);
        });

        it('sync review users correctly', async () => {
            repositoryMock.upsertReviewUsers.mockImplementationOnce((reviewUsers) => Promise.resolve(reviewUsers));

            await expect(performanceReviewsService.syncReviewUsers(users)).resolves.not.toThrow();

            checkMockCallsCount(repositoryMock.upsertReviewUsers, 1);
        });
    });

    describe('getReviewerIdsWithPendingReviews', () => {
        let cycleId: number;
        let reviewerId: number;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewerId = fakePositiveInteger();
        });

        it('successful if no reviewer ids exist with pending reviews', async () => {
            repositoryMock.getReviewerIdsWithPendingReviews.mockResolvedValueOnce([]);

            const result = await performanceReviewsService.getReviewerIdsWithPendingReviews(cycleId);

            expect(result).toHaveLength(0);

            checkMockCallsCount(repositoryMock.getReviewerIdsWithPendingReviews, 1);
        });

        it('successful if one reviewer id exists with pending reviews', async () => {
            repositoryMock.getReviewerIdsWithPendingReviews.mockResolvedValueOnce([reviewerId]);

            const result = await performanceReviewsService.getReviewerIdsWithPendingReviews(cycleId);

            expect(result).toHaveLength(1);
            expect(result[0]).toBe(reviewerId);

            checkMockCallsCount(repositoryMock.getReviewerIdsWithPendingReviews, 1);
        });
    });

    describe('doesUserHavePendingReviews', () => {
        let cycleId: number;
        let userId: number;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            userId = fakePositiveInteger();
        });

        it('returns false if user has pending reviews', async () => {
            repositoryMock.getReviewerIdsWithPendingReviews.mockResolvedValueOnce({ reviewerIds: [], externalReviewerIds: [] });

            const result = await performanceReviewsService.doesUserHavePendingReviews(cycleId, userId);

            expect(result).toBe(false);
        });

        it('returns true if user does not have pending reviews', async () => {
            repositoryMock.getReviewerIdsWithPendingReviews.mockResolvedValueOnce({ reviewerIds: [userId], externalReviewerIds: [] });

            const result = await performanceReviewsService.doesUserHavePendingReviews(cycleId, userId);

            expect(result).toBe(true);
        });
    });

    describe('getCoreFormOfTemplate', () => {
        let formTemplate: PerformanceReviewFormTemplate;

        beforeEach(() => {
            const cycleId = fakePositiveInteger();
            formTemplate = generateRandomPerformanceReviewFormTemplate(cycleId);
        });

        it('return null if form template has no core form associated', async () => {
            formTemplate.coreFormTag = null;
            formTemplate.coreFormId = null;

            const result = await performanceReviewsService.getCoreFormOfTemplate(formTemplate);

            expect(result).toBeNull();

            checkMockCallsCount(coreFormsPortMock.getForm, 0);
        });

        it('return null if core form does not exist', async () => {
            coreFormsPortMock.getForm.mockResolvedValueOnce(null);
            const result = await performanceReviewsService.getCoreFormOfTemplate(formTemplate);

            expect(result).toBeNull();

            checkMockCallsCount(coreFormsPortMock.getForm, 1);
        });

        it('return core form if associated and exists', async () => {
            coreFormsPortMock.getForm.mockImplementationOnce((tag, id) => Promise.resolve(generateRandomCoreFormWithQuestions(tag, id)));
            const result = await performanceReviewsService.getCoreFormOfTemplate(formTemplate);

            expect(result).not.toBeNull();
            expect(result).toBeInstanceOf(CoreForm);
            expect(result?.tag).toBe(formTemplate.coreFormTag);
            expect(result?.id).toBe(formTemplate.coreFormId);

            checkMockCallsCount(coreFormsPortMock.getForm, 1);
        });
    });

    describe('listFormReviewsOfCycle', () => {
        let formReview: PerformanceReviewFormReview;
        let options: ListPerformanceReviewFormReviewsOptions;
        let instanceId: number;

        beforeEach(() => {
            const cycleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
            formReview = generateRandomPerformanceReviewFormReview(cycleId);
            options = {
                pagination: generateRandomCursorPagination(),
                withFormAnswers: false,
                withUserDetails: false,
            };
        });

        it('successful if no reviews exist', async () => {
            repositoryMock.getFormReviewsOfCycleByFilters.mockResolvedValueOnce({ nextCursorId: null, reviewForms: [] });
            repositoryMock.getCycleById.mockResolvedValueOnce(generatePerformanceReviewCycleWithValues(formReview.cycleId, faker.date.recent()));
            const result = await performanceReviewsService.listFormReviewsOfCycle(formReview.cycleId, instanceId, options);

            expect(result.nextCursorId).toBeNull();
            expect(result.reviewForms).toHaveLength(0);

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleByFilters, 1);
            checkMockCallsCount(coreFormsPortMock.getFormAnswers, 0);
        });

        it('successful if one review exists and no form answers are needed', async () => {
            options.withFormAnswers = false;
            repositoryMock.getFormReviewsOfCycleByFilters.mockResolvedValueOnce({ nextCursorId: null, reviewForms: [formReview] });
            repositoryMock.getCycleById.mockResolvedValueOnce(generatePerformanceReviewCycleWithValues(formReview.cycleId, faker.date.recent()));

            const result = await performanceReviewsService.listFormReviewsOfCycle(formReview.cycleId, instanceId, options);

            expect(result.nextCursorId).toBeNull();
            expect(result.reviewForms).toHaveLength(1);
            expect(result.reviewForms[0].id).toBe(formReview.id);
            expect(result.reviewForms[0].formAnswer).toBeNull();

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleByFilters, 1);
            checkMockCallsCount(coreFormsPortMock.getFormAnswers, 0);
        });

        it('successful if one review exists and form answers are needed, but no review has been answered', async () => {
            formReview.coreFormAnswerId = null;
            options.withFormAnswers = true;
            repositoryMock.getFormReviewsOfCycleByFilters.mockResolvedValueOnce({ nextCursorId: null, reviewForms: [formReview] });
            repositoryMock.getCycleById.mockResolvedValueOnce(generatePerformanceReviewCycleWithValues(formReview.cycleId, faker.date.recent()));

            const result = await performanceReviewsService.listFormReviewsOfCycle(formReview.cycleId, instanceId, options);

            expect(result.nextCursorId).toBeNull();
            expect(result.reviewForms).toHaveLength(1);
            expect(result.reviewForms[0].id).toBe(formReview.id);
            expect(result.reviewForms[0].formAnswer).toBeNull();

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleByFilters, 1);
            checkMockCallsCount(coreFormsPortMock.getFormAnswers, 0);
        });

        it('if exist boss review and isAnonymus do not return reviewers', async () => {
            formReview = generatePerformanceReviewFormReviewWithValues(formReview.id, formReview.cycleId, {
                type: PerformanceReviewEvaluationTypes.BOSS_REVIEW,
            });
            options.withFormAnswers = true;
            formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
            formReview.reviewer = generateRandomPerformanceReviewUser(formReview.reviewerId!);

            repositoryMock.getFormReviewsOfCycleByFilters.mockResolvedValueOnce({ nextCursorId: null, reviewForms: [formReview] });
            repositoryMock.getCycleById.mockResolvedValueOnce(
                generatePerformanceReviewCycleWithValues(formReview.cycleId, faker.date.recent(), { upwardReviewAnonymousAnswers: true }),
            );

            const result = await performanceReviewsService.listFormReviewsOfCycle(formReview.cycleId, instanceId, options);

            expect(result.reviewForms[0].reviewer).toBeNull();
            expect(result.reviewForms[0].reviewed).not.toBeNull();
        });

        it('successful if one review exists, form answers are needed and review has been answered', async () => {
            formReview.coreFormAnswerId = fakePositiveInteger();
            options.withFormAnswers = true;
            repositoryMock.getFormReviewsOfCycleByFilters.mockResolvedValueOnce({ nextCursorId: null, reviewForms: [formReview] });
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId as number, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);
            repositoryMock.getCycleById.mockResolvedValueOnce(generatePerformanceReviewCycleWithValues(formReview.cycleId, faker.date.recent()));

            const result = await performanceReviewsService.listFormReviewsOfCycle(formReview.cycleId, instanceId, options);

            expect(result.nextCursorId).toBeNull();
            expect(result.reviewForms).toHaveLength(1);
            expect(result.reviewForms[0].id).toBe(formReview.id);
            expect(result.reviewForms[0].formAnswer).not.toBeNull();
            expect(result.reviewForms[0].formAnswer?.id).toBe(formReview.coreFormAnswerId);

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleByFilters, 1);
            checkMockCallsCount(coreFormsPortMock.getFormAnswers, 1);
        });
    });

    describe('createFormTemplatesOnly', () => {
        let creationInterfaces: DuplicatePerformanceReviewFormTemplateInterface[];

        beforeEach(() => {
            const formTemplatesCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(formTemplatesCount)
                .fill(1)
                .map(() => generateFormTemplateDuplication());
        });

        it('should create a list of form templates correctly', async () => {
            repositoryMock.duplicateFormTemplates.mockImplementationOnce((formTemplates: DuplicatePerformanceReviewFormTemplateInterface[]) => {
                const newFormTemplates = formTemplates.map((ft) => {
                    return getPerformanceReviewFormTemplateFromDuplication(ft);
                });
                return Promise.resolve(newFormTemplates);
            });
            const result = await performanceReviewsService.createFormTemplatesOnly(creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((formTemplate, index) => {
                expect(formTemplate).toBeInstanceOf(PerformanceReviewFormTemplate);
                expect(formTemplate.cycleId).toBe(creationInterfaces[index].cycleId);
                expect(formTemplate.evaluationType).toBe(creationInterfaces[index].evaluationType);
                expect(formTemplate.templateId).toBe(creationInterfaces[index].templateId);
                expect(formTemplate.coreFormId).toBe(creationInterfaces[index].coreFormId);
                expect(formTemplate.coreFormTag).toBe(creationInterfaces[index].coreFormTag);
                expect(formTemplate.formReviewConfig).toEqual(creationInterfaces[index].formReviewConfig);
                expect(formTemplate.scoreConfig).toEqual(creationInterfaces[index].scoreConfig);
                expect(formTemplate.createdAt).not.toBeNull();
                expect(formTemplate.updatedAt).not.toBeNull();
                expect(formTemplate.id).not.toBeNull();
                expect(formTemplate.deletedAt).toBeNull();
            });
        });

        it('should throw database error if createFormTemplates of the repository port throws database error', async () => {
            repositoryMock.duplicateFormTemplates.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.createFormTemplatesOnly(creationInterfaces)).rejects.toThrow(Error);
        });
    });

    describe('createReviewsOnly', () => {
        let creationInterfaces: DuplicatePerformanceReviewFormReviewInterface[];

        beforeEach(() => {
            const reviewCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(reviewCount)
                .fill(1)
                .map(() => generateRandomReviewDuplication());
        });

        it('should create a list of reviews correctly', async () => {
            repositoryMock.duplicateFormReviews.mockImplementationOnce(
                (reviewDuplicateInterfaces: DuplicatePerformanceReviewFormReviewInterface[]) => {
                    const newReviews = reviewDuplicateInterfaces.map((reviewDuplicateInterface) => {
                        return getPerformanceReviewFormReviewFromDuplication(reviewDuplicateInterface);
                    });
                    return Promise.resolve(newReviews);
                },
            );
            const result = await performanceReviewsService.createReviewsOnly(creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((review, index) => {
                expect(review).toBeInstanceOf(PerformanceReviewFormReview);
                expect(review.status).toBe(creationInterfaces[index].status);
                expect(review.reviewedId).toBe(creationInterfaces[index].reviewedId);
                expect(review.reviewerId).toBe(creationInterfaces[index].reviewerId);
                expect(review.score).toBe(creationInterfaces[index].score);
                expect(review.createdAt).not.toBeNull();
                expect(review.updatedAt).not.toBeNull();
                expect(review.id).not.toBeNull();
                expect(review.deletedAt).toBeNull();
            });
        });

        it('should throw database error if createReviewsOnly of the repository port throws database error', async () => {
            repositoryMock.duplicateFormReviews.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.createReviewsOnly(creationInterfaces)).rejects.toThrow(Error);
        });
    });

    describe('createGoalQuestionsWithAnswerOnly', () => {
        let creationInterfaces: PerformanceReviewGoalQuestionWithAnswerForCreation[];

        beforeEach(() => {
            const goalQuestionCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(goalQuestionCount)
                .fill(1)
                .map(() => generateRandomGoalQuestionWithAnswerForCreation(fakePositiveInteger()));
        });

        it('should create a list of goal questions correctly', async () => {
            repositoryMock.upsertGoalQuestionsWithAnswer.mockImplementationOnce(
                (goalQuestionsForCreation: PerformanceReviewGoalQuestionWithAnswerForCreation[]) => {
                    const newGoalQuestions = goalQuestionsForCreation.map((goalQuestionForCreation) => {
                        return generateGoalQuestionWithAnswerFromCreation(goalQuestionForCreation);
                    });
                    return Promise.resolve(newGoalQuestions);
                },
            );
            const result = await performanceReviewsService.createGoalQuestionsWithAnswerOnly(creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((goalQuestion, index) => {
                expect(goalQuestion).toBeInstanceOf(PerformanceReviewGoalQuestionWithAnswer);
                expect(goalQuestion.goalId).toBe(creationInterfaces[index].goalId);
                expect(goalQuestion.cycleId).toBe(creationInterfaces[index].cycleId);
                expect(goalQuestion.progress).toBe(creationInterfaces[index].progress);
                expect(goalQuestion.weight).toBe(creationInterfaces[index].weight);
                expect(goalQuestion.createdAt).not.toBeNull();
                expect(goalQuestion.updatedAt).not.toBeNull();
                expect(goalQuestion.id).not.toBeNull();
                expect(goalQuestion.deletedAt).toBeNull();
            });
        });

        it('should throw database error if createReviewsOnly of the repository port throws database error', async () => {
            repositoryMock.upsertGoalQuestionsWithAnswer.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.createGoalQuestionsWithAnswerOnly(creationInterfaces)).rejects.toThrow(Error);
        });
    });

    describe('createCyclesOnly', () => {
        let fakeInstanceId: number;
        let creationInterfaces: DuplicatePerformanceReviewCycleInterface[];

        beforeEach(() => {
            fakeInstanceId = fakePositiveInteger();
            const cyclesCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(cyclesCount)
                .fill(1)
                .map(() => generateRandomReviewCycleDuplication());
        });

        it('should create a list of cycles correctly', async () => {
            repositoryMock.createCycles.mockImplementationOnce((reviewCyclesForCreation: PerformanceReviewCycleForCreation[]) => {
                const newCycles = reviewCyclesForCreation.map((reviewCycleForCreation) => {
                    return generatePerformanceReviewCycleFromCreation(fakeInstanceId, reviewCycleForCreation);
                });
                return Promise.resolve(newCycles);
            });
            const result = await performanceReviewsService.createCyclesOnly(fakeInstanceId, creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((reviewCycle, index) => {
                expect(reviewCycle).toBeInstanceOf(PerformanceReviewCycle);
                expect(reviewCycle.instanceId).toBe(fakeInstanceId);
                expect(reviewCycle.name).toBe(creationInterfaces[index].name);
                expect(reviewCycle.status).toBe(creationInterfaces[index].status);
                expect(reviewCycle.reviewedUsersMode).toBe(creationInterfaces[index].reviewedUsersMode);
                expect(reviewCycle.startDate).toBe(creationInterfaces[index].startDate);
                expect(reviewCycle.endDate).toBe(creationInterfaces[index].endDate);
                expect(reviewCycle.goalCycleId).toBe(creationInterfaces[index].goalCycleId);
                expect(reviewCycle.withSummary).toBe(creationInterfaces[index].withSummary);
                expect(reviewCycle.duplicationId).toBe(creationInterfaces[index].duplicationId);
                expect(reviewCycle.createdAt).not.toBeNull();
                expect(reviewCycle.updatedAt).not.toBeNull();
                expect(reviewCycle.id).not.toBeNull();
                expect(reviewCycle.deletedAt).toBeNull();
                expect(reviewCycle.peerReviewExternalReviewerSelectionVisibleForBosses).toBe(
                    creationInterfaces[index].peerReviewExternalReviewerSelectionVisibleForBosses,
                );
                expect(reviewCycle.peerReviewReviewerSelectionVisibleForBosses).toBe(
                    creationInterfaces[index].peerReviewReviewerSelectionVisibleForBosses,
                );
            });
        });

        it('should throw database error if createCycles of the repository port throws database error', async () => {
            repositoryMock.createCycles.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.createCyclesOnly(fakeInstanceId, creationInterfaces)).rejects.toThrow(Error);
        });
    });

    describe('createExternalReviewersOnly', () => {
        let fakeInstanceId: number;
        let creationInterfaces: DuplicatePerformanceReviewExternalReviewerInterface[];

        beforeEach(() => {
            fakeInstanceId = fakePositiveInteger();
            const externalReviewerCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(externalReviewerCount)
                .fill(1)
                .map(() => generateRandomExternalReviewer({ instanceId: fakeInstanceId }));
        });

        it('should create a list of external reviewers correctly', async () => {
            repositoryMock.createExternalReviewers.mockImplementationOnce(
                (externalReviewersForCreation: PerformanceReviewExternalReviewerForCreation[]) => {
                    const newExternalReviewers = externalReviewersForCreation.map((externalForCreation) => {
                        return generateExternalReviewerFromCreation(fakeInstanceId, externalForCreation);
                    });
                    return Promise.resolve(newExternalReviewers);
                },
            );
            const result = await performanceReviewsService.createExternalReviewersOnly(fakeInstanceId, creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((externalReviewer, index) => {
                expect(externalReviewer).toBeInstanceOf(PerformanceReviewExternalReviewer);
                expect(externalReviewer.instanceId).toBe(fakeInstanceId);
                expect(externalReviewer.email).toBe(creationInterfaces[index].email);
                expect(externalReviewer.firstName).toBe(creationInterfaces[index].firstName);
                expect(externalReviewer.lastName).toBe(creationInterfaces[index].lastName);
                expect(externalReviewer.instanceId).toBe(fakeInstanceId);
                expect(externalReviewer.instanceId).toBe(fakeInstanceId);
                expect(externalReviewer.duplicationId).toBe(creationInterfaces[index].duplicationId);
                expect(externalReviewer.createdAt).not.toBeNull();
                expect(externalReviewer.updatedAt).not.toBeNull();
                expect(externalReviewer.id).not.toBeNull();
                expect(externalReviewer.deletedAt).toBeNull();
            });
        });

        it('should throw database error if external reviewer creation of the repository port throws database error', async () => {
            repositoryMock.createExternalReviewers.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.createExternalReviewersOnly(fakeInstanceId, creationInterfaces)).rejects.toThrow(Error);
        });
    });

    describe('listCyclesOfInstance', () => {
        let instanceId: number;
        let reviewCycle: PerformanceReviewCycle;
        let pagination: CursorIdPagination;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                instanceId,
            });
            pagination = generateRandomCursorPagination();
        });

        it('should return an empty list of cycles', async () => {
            repositoryMock.listCyclesOfInstance.mockResolvedValueOnce({
                nextCursorId: null,
                reviewCycles: [],
            });
            const { nextCursorId, reviewCycles } = await performanceReviewsService.listCyclesOfInstance(reviewCycle.instanceId, pagination);

            expect(nextCursorId).toBeNull();
            expect(reviewCycles).toHaveLength(0);
        });

        it('should return a list of cycles of instance', async () => {
            pagination.limit = 1;
            const secondId = fakePositiveInteger();
            repositoryMock.listCyclesOfInstance.mockResolvedValueOnce({
                nextCursorId: secondId,
                reviewCycles: [reviewCycle],
            });
            const { reviewCycles, nextCursorId } = await performanceReviewsService.listCyclesOfInstance(reviewCycle.instanceId, pagination);

            expect(nextCursorId).toBe(secondId);
            expect(reviewCycles).toHaveLength(1);
            expect(reviewCycles[0].id).toBe(reviewCycle.id);
        });

        it('should return a list of cycles of instance matching status', async () => {
            pagination.limit = 1;
            const secondId = fakePositiveInteger();
            reviewCycle.status = PerformanceReviewCycleStatus.DRAFT;
            repositoryMock.listCyclesOfInstance.mockResolvedValueOnce({
                nextCursorId: secondId,
                reviewCycles: [reviewCycle],
            });
            const { reviewCycles, nextCursorId } = await performanceReviewsService.listCyclesOfInstance(reviewCycle.instanceId, pagination, [
                PerformanceReviewCycleStatus.DRAFT,
            ]);

            expect(nextCursorId).toBe(secondId);
            expect(reviewCycles).toHaveLength(1);
            expect(reviewCycles[0].id).toBe(reviewCycle.id);
            expect(reviewCycles[0].status).toBe(PerformanceReviewCycleStatus.DRAFT);
        });

        it('should fail because database error', async () => {
            repositoryMock.listCyclesOfInstance.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.listCyclesOfInstance(reviewCycle.instanceId, pagination)).rejects.toThrow(Error);
        });
    });

    describe('listTemplatesOfInstance', () => {
        let instanceId: number;
        let template: PerformanceReviewTemplate;
        let pagination: CursorIdPagination;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            template = generateRandomTemplate(undefined, instanceId);
            pagination = generateRandomCursorPagination();
        });

        it('should return an empty list of templates', async () => {
            repositoryMock.listTemplatesOfInstance.mockResolvedValueOnce({
                nextCursorId: null,
                templates: [],
            });
            const { nextCursorId, templates } = await performanceReviewsService.listTemplatesOfInstance(template.instanceId, pagination);

            expect(nextCursorId).toBeNull();
            expect(templates).toHaveLength(0);
        });

        it('should return a list of templates of instance', async () => {
            pagination.limit = 1;
            const secondId = fakePositiveInteger();
            repositoryMock.listTemplatesOfInstance.mockResolvedValueOnce({
                nextCursorId: secondId,
                templates: [template],
            });
            const { templates, nextCursorId } = await performanceReviewsService.listTemplatesOfInstance(template.instanceId, pagination);

            expect(nextCursorId).toBe(secondId);
            expect(templates).toHaveLength(1);
            expect(templates[0].id).toBe(template.id);
        });

        it('should fail because database error', async () => {
            repositoryMock.listTemplatesOfInstance.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.listTemplatesOfInstance(template.instanceId, pagination)).rejects.toThrow(Error);
        });
    });

    describe('listExternalReviewersOfInstance', () => {
        let instanceId: number;
        let externalReviewer: PerformanceReviewExternalReviewer;
        let pagination: CursorIdPagination;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            externalReviewer = generateRandomExternalReviewer({
                instanceId,
            });
            pagination = generateRandomCursorPagination();
        });

        it('should return an empty list of external reviewers', async () => {
            repositoryMock.listExternalReviewersOfInstance.mockResolvedValueOnce({
                externalReviewers: [],
                nextCursorId: null,
            });
            const { nextCursorId, externalReviewers } = await performanceReviewsService.listExternalReviewersOfInstance(
                externalReviewer.instanceId,
                pagination,
            );

            expect(nextCursorId).toBeNull();
            expect(externalReviewers).toHaveLength(0);
        });

        it('should return a list of external reviewers', async () => {
            pagination.limit = 1;
            const secondId = fakePositiveInteger();
            repositoryMock.listExternalReviewersOfInstance.mockResolvedValueOnce({
                externalReviewers: [externalReviewer],
                nextCursorId: secondId,
            });
            const { externalReviewers, nextCursorId } = await performanceReviewsService.listExternalReviewersOfInstance(
                externalReviewer.instanceId,
                pagination,
            );

            expect(nextCursorId).toBe(secondId);
            expect(externalReviewers).toHaveLength(1);
            expect(externalReviewers[0].id).toBe(externalReviewer.id);
        });

        it('should fail because database error', async () => {
            repositoryMock.listExternalReviewersOfInstance.mockRejectedValueOnce(new Error('Error'));

            await expect(performanceReviewsService.listExternalReviewersOfInstance(externalReviewer.instanceId, pagination)).rejects.toThrow(Error);
        });
    });

    describe('createTemplateOnly', () => {
        let formTemplateId: number;
        let goalsQuestionTemplateId: number;
        let competenciesQuestionTemplateId: number;
        let questionsCount!: number;
        let creatorId: number;
        let description: string;
        let title: string;
        let status: FormTemplateStatusValues;
        let instanceId: number;
        let fakeId: number;
        let fakeDate: Date;

        beforeEach(() => {
            formTemplateId = fakePositiveInteger();
            goalsQuestionTemplateId = fakePositiveInteger();
            competenciesQuestionTemplateId = fakePositiveInteger();
            creatorId = fakePositiveInteger();
            title = faker.word.words();
            description = faker.word.words();
            status = faker.helpers.arrayElement(Object.values(FORM_TEMPLATE_STATUS));
            instanceId = fakePositiveInteger();
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
        });

        it('create performance review template successfully', async () => {
            repositoryMock.createTemplate.mockImplementationOnce((template) => {
                const newTemplate = new PerformanceReviewTemplate({
                    createdAt: fakeDate,
                    creatorId: template.creatorId,
                    deletedAt: null,
                    duplicationId: template.duplicationId,
                    formTemplateId: template.formTemplateId,
                    goalsProgressMode: template.goalsProgressMode,
                    id: fakeId,
                    instanceId: template.instanceId,
                    goalsQuestionTemplateId: template.goalsQuestionTemplateId,
                    competenciesQuestionTemplateId: template.competenciesQuestionTemplateId,
                    questionsCount: template.questionsCount,
                    status: template.status,
                    title: template.title,
                    updatedAt: fakeDate,
                });
                return Promise.resolve(newTemplate);
            });

            const goalsProgressMode = faker.helpers.arrayElement(Object.values(PerformanceReviewTemplateGoalsProgressMode));

            const result = await performanceReviewsService.createTemplateOnly(instanceId, {
                creatorId,
                description,
                formTemplateId,
                goalsProgressMode,
                goalsQuestionTemplateId,
                competenciesQuestionTemplateId,
                questionsCount,
                status,
                title,
            });

            expect(result).toBeInstanceOf(PerformanceReviewTemplate);
            expect(result.formTemplateId).toBe(formTemplateId);
            expect(result.goalsQuestionTemplateId).toBe(goalsQuestionTemplateId);
            expect(result.competenciesQuestionTemplateId).toBe(competenciesQuestionTemplateId);
            expect(result.questionsCount).toBe(questionsCount);
            expect(result.instanceId).toBe(instanceId);
            expect(result.creatorId).toBe(creatorId);
            expect(result.status).toBe(status);
            expect(result.title).toBe(title);
            expect(result.id).toBe(fakeId);
            expect(result.createdAt).toBe(fakeDate);
            expect(result.updatedAt).toBe(fakeDate);
            expect(result.goalsProgressMode).toBe(goalsProgressMode);
            expect(result.deletedAt).toBeNull();
        });
    });

    describe('getFullTemplateById', () => {
        let template: PerformanceReviewTemplate;

        beforeEach(() => {
            template = generateRandomTemplate();
        });

        it('find performance review template successfully with question templates', async () => {
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            const result = await performanceReviewsService.getFullTemplateById(template.id, template.instanceId);

            checkMockCallsCount(formTemplatesPortMock.addQuestionTemplatesToFormTemplate, 1);

            expect(result).toBe(template);
        });

        it('throw not found error if template does not exist', async () => {
            repositoryMock.getTemplateById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.getFullTemplateById(template.id, template.instanceId)).rejects.toThrow(NotFoundError);
        });
    });

    describe('deleteTemplate', () => {
        let template: PerformanceReviewTemplate;

        beforeEach(() => {
            template = generateRandomTemplate();
        });

        it('delete performance review template without question successfully', async () => {
            template.formTemplate = generateRandomFormTemplate(template.formTemplateId!);

            formTemplatesPortMock.deleteFormTemplate.mockResolvedValueOnce();

            await expect(performanceReviewsService.deleteTemplate(template)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesPortMock.deleteFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 0);
            checkMockCallsCount(repositoryMock.deleteTemplates, 1);
        });

        it('delete performance review template successfully with question template in draft', async () => {
            template.formTemplate = generateRandomFormTemplate(template.formTemplateId!);
            template.goalsQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), template.instanceId, {
                status: QUESTION_TEMPLATE_STATUS.DRAFT,
            });
            formTemplatesPortMock.deleteFormTemplate.mockResolvedValueOnce();
            formTemplatesPortMock.deleteQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsService.deleteTemplate(template)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesPortMock.deleteFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 1);
            checkMockCallsCount(repositoryMock.deleteTemplates, 1);
        });

        it('delete performance review template successfully with question template in use', async () => {
            template.formTemplate = generateRandomFormTemplate(template.formTemplateId!);
            template.goalsQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), template.instanceId, {
                status: QUESTION_TEMPLATE_STATUS.IN_USE,
            });

            formTemplatesPortMock.deleteFormTemplate.mockResolvedValueOnce();
            formTemplatesPortMock.deleteQuestionTemplates.mockResolvedValueOnce();

            await expect(performanceReviewsService.deleteTemplate(template)).resolves.not.toThrow();

            checkMockCallsCount(formTemplatesPortMock.deleteFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 0);
            checkMockCallsCount(repositoryMock.deleteTemplates, 1);
        });
    });

    describe('updateTemplate', () => {
        let instanceId: number;
        let performanceReviewTemplate: PerformanceReviewTemplate;
        let editedFormTemplate: FormTemplate;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            performanceReviewTemplate = generateRandomTemplate();
            editedFormTemplate = generateFormTemplateWithValues(performanceReviewTemplate.formTemplateId!, instanceId);
        });

        it('update template successfully and delete question template', async () => {
            performanceReviewTemplate.status = FORM_TEMPLATE_STATUS.DRAFT;
            repositoryMock.saveTemplate.mockImplementationOnce((template) => Promise.resolve(template));
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), instanceId, {
                status: QUESTION_TEMPLATE_STATUS.DRAFT,
            });
            performanceReviewTemplate.goalsQuestionTemplate = goalsQuestionTemplate;
            const competenciesQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), instanceId, {
                status: QUESTION_TEMPLATE_STATUS.DRAFT,
            });
            performanceReviewTemplate.competenciesQuestionTemplate = competenciesQuestionTemplate;
            const user = generateRandomUser();
            user.instanceId = instanceId;
            const result = await performanceReviewsService.updateTemplate(
                user,
                performanceReviewTemplate,
                {
                    ...formTemplateToFormTemplateParams(editedFormTemplate),
                    goalsQuestionTemplate: null,
                    competenciesQuestionTemplate: null,
                    goalsProgressMode: null,
                },
            );

            expect(result).toBeInstanceOf(PerformanceReviewTemplate);
            expect(result.formTemplateId).toBe(performanceReviewTemplate.formTemplateId);
            expect(result.instanceId).toBe(performanceReviewTemplate.instanceId);
            expect(result.id).toBe(performanceReviewTemplate.id);
            expect(result.createdAt).toBe(performanceReviewTemplate.createdAt);
            expect(result.status).toBe(performanceReviewTemplate.status);
            expect(result.title).toBe(performanceReviewTemplate.title);
            expect(result.questionsCount).toBe(performanceReviewTemplate.questionsCount);
            expect(result.goalsProgressMode).toBeNull();
            expect(result.goalsQuestionTemplateId).toBeNull();
            expect(result.goalsQuestionTemplate).toBeNull();
            expect(result.competenciesQuestionTemplateId).toBeNull();
            expect(result.competenciesQuestionTemplate).toBeNull();

            checkMockCallsCount(repositoryMock.saveTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 2);
            checkMockCallsCount(formTemplatesPortMock.createQuestionTemplate, 0);
            checkMockCallsCount(formTemplatesPortMock.editQuestionTemplate, 0);
        });

        it('update template successfully and create question template', async () => {
            performanceReviewTemplate.status = FORM_TEMPLATE_STATUS.DRAFT;
            repositoryMock.saveTemplate.mockImplementationOnce((template) => Promise.resolve(template));
            const createdGoalsQuestionTemplate = generateRandomQuestionTemplate();
            formTemplatesPortMock.createQuestionTemplate.mockResolvedValueOnce(createdGoalsQuestionTemplate);
            const editQuestionParams = generateCreateQuestionTemplateDataRandomly();
            const goalsProgressMode = PerformanceReviewTemplateGoalsProgressMode.MANUAL;
            const user = generateRandomUser();
            user.instanceId = instanceId;
            const result = await performanceReviewsService.updateTemplate(
                user,
                performanceReviewTemplate,
                {
                    ...formTemplateToFormTemplateParams(editedFormTemplate),
                    goalsQuestionTemplate: editQuestionParams,
                    goalsProgressMode,
                    competenciesQuestionTemplate: null,
                },
            );

            expect(result).toBeInstanceOf(PerformanceReviewTemplate);
            expect(result.formTemplateId).toBe(performanceReviewTemplate.formTemplateId);
            expect(result.instanceId).toBe(performanceReviewTemplate.instanceId);
            expect(result.id).toBe(performanceReviewTemplate.id);
            expect(result.createdAt).toBe(performanceReviewTemplate.createdAt);
            expect(result.status).toBe(performanceReviewTemplate.status);
            expect(result.title).toBe(performanceReviewTemplate.title);
            expect(result.questionsCount).toBe(performanceReviewTemplate.questionsCount);
            expect(result.goalsQuestionTemplateId).toBe(createdGoalsQuestionTemplate.id);
            expect(result.goalsProgressMode).toBe(goalsProgressMode);
            expect(result.competenciesQuestionTemplateId).toBeNull();
            expect(result.competenciesQuestionTemplate).toBeNull();

            checkMockCallsCount(repositoryMock.saveTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 0);
            checkMockCallsCount(formTemplatesPortMock.createQuestionTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.editQuestionTemplate, 0);
        });

        it('update template successfully and edit question template', async () => {
            performanceReviewTemplate.status = FORM_TEMPLATE_STATUS.DRAFT;
            repositoryMock.saveTemplate.mockImplementationOnce((template) => Promise.resolve(template));
            const editedGoalsTemplate = generateRandomQuestionTemplate();
            const editedCompetenciesTemplate = generateRandomQuestionTemplate();
            formTemplatesPortMock.editQuestionTemplate.mockResolvedValueOnce(editedGoalsTemplate);
            formTemplatesPortMock.editQuestionTemplate.mockResolvedValueOnce(editedCompetenciesTemplate);
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), instanceId, {
                status: QUESTION_TEMPLATE_STATUS.DRAFT,
            });
            const competenciesQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), instanceId, {
                status: QUESTION_TEMPLATE_STATUS.DRAFT,
            });
            performanceReviewTemplate.goalsQuestionTemplate = goalsQuestionTemplate;
            performanceReviewTemplate.competenciesQuestionTemplate = competenciesQuestionTemplate;
            const editGoalsQuestionParams = generateCreateQuestionTemplateDataRandomly();
            const editCompetenciesQuestionParams = generateCreateQuestionTemplateDataRandomly();
            const goalsProgressMode = PerformanceReviewTemplateGoalsProgressMode.MANUAL;
            const user = generateRandomUser();
            user.instanceId = instanceId;
            const result = await performanceReviewsService.updateTemplate(
                user,
                performanceReviewTemplate,
                {
                    ...formTemplateToFormTemplateParams(editedFormTemplate),
                    goalsProgressMode,
                    goalsQuestionTemplate: editGoalsQuestionParams,
                    competenciesQuestionTemplate: editCompetenciesQuestionParams,
                },
            );

            expect(result).toBeInstanceOf(PerformanceReviewTemplate);
            expect(result.formTemplateId).toBe(performanceReviewTemplate.formTemplateId);
            expect(result.instanceId).toBe(performanceReviewTemplate.instanceId);
            expect(result.id).toBe(performanceReviewTemplate.id);
            expect(result.createdAt).toBe(performanceReviewTemplate.createdAt);
            expect(result.status).toBe(performanceReviewTemplate.status);
            expect(result.title).toBe(performanceReviewTemplate.title);
            expect(result.questionsCount).toBe(performanceReviewTemplate.questionsCount);
            expect(result.goalsQuestionTemplateId).toBe(editedGoalsTemplate.id);
            expect(result.goalsQuestionTemplate).toBe(editedGoalsTemplate);
            expect(result.goalsProgressMode).toBe(goalsProgressMode);
            expect(result.competenciesQuestionTemplateId).toBe(editedCompetenciesTemplate.id);
            expect(result.competenciesQuestionTemplate).toBe(editedCompetenciesTemplate);

            checkMockCallsCount(repositoryMock.saveTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 0);
            checkMockCallsCount(formTemplatesPortMock.createQuestionTemplate, 0);
            checkMockCallsCount(formTemplatesPortMock.editQuestionTemplate, 2);
        });

        it('should throw error when changing question type and question is in use', async () => {
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), instanceId, {
                status: QUESTION_TEMPLATE_STATUS.IN_USE,
                type: QUESTION_TEMPLATES_QUESTION_TYPES.TEXT,
            });
            performanceReviewTemplate.goalsQuestionTemplate = goalsQuestionTemplate;
            performanceReviewTemplate.competenciesQuestionTemplate = null;
            const editGoalsQuestionParams = generateCreateQuestionTemplateDataRandomly();
            editGoalsQuestionParams.type = QUESTION_TEMPLATES_QUESTION_TYPES.RATING;
            const user = generateRandomUser();
            user.instanceId = instanceId;

            await expect(
                performanceReviewsService.updateTemplate(user, performanceReviewTemplate, {
                    ...formTemplateToFormTemplateParams(editedFormTemplate),
                    goalsQuestionTemplate: editGoalsQuestionParams,
                    goalsProgressMode: null,
                    competenciesQuestionTemplate: null,
                }),
            ).rejects.toMatchObject({ code: ErrorCodes.CANNOT_EDIT_FORM_QUESTION_IN_USE });

            checkMockCallsCount(repositoryMock.saveTemplate, 0);
            checkMockCallsCount(formTemplatesPortMock.editQuestionTemplate, 0);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 0);
        });

        it('should throw error when removing question template that is in use', async () => {
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(fakePositiveInteger(), instanceId, {
                status: QUESTION_TEMPLATE_STATUS.IN_USE,
            });
            performanceReviewTemplate.goalsQuestionTemplate = goalsQuestionTemplate;
            performanceReviewTemplate.competenciesQuestionTemplate = null;
            const user = generateRandomUser();
            user.instanceId = instanceId;

            await expect(
                performanceReviewsService.updateTemplate(user, performanceReviewTemplate, {
                    ...formTemplateToFormTemplateParams(editedFormTemplate),
                    goalsQuestionTemplate: null,
                    goalsProgressMode: null,
                    competenciesQuestionTemplate: null,
                }),
            ).rejects.toMatchObject({ code: ErrorCodes.CANNOT_CHANGE_QUESTION_CONFIG });

            checkMockCallsCount(repositoryMock.saveTemplate, 0);
            checkMockCallsCount(formTemplatesPortMock.deleteQuestionTemplates, 0);
        });
    });

    describe('addFormReviewsToCycle', () => {
        let cycleId: number;
        let reviewForms: PerformanceReviewFormReview[];

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            const reviewFormsCount = faker.number.int({ max: 3, min: 1 });
            reviewForms = Array(reviewFormsCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewFormReview(cycleId));
        });

        it('successfully set form reviews', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent());
            repositoryMock.getFormReviewsOfCycle.mockResolvedValueOnce(reviewForms);

            await performanceReviewsService.addFormReviewsToCycle(reviewCycle);

            expect(reviewCycle.reviewForms).toHaveLength(reviewForms.length);

            reviewCycle.reviewForms?.forEach((reviewForm, index) => {
                expect(reviewForm.id).toBe(reviewForms[index].id);
                expect(reviewForm.createdAt).toBe(reviewForms[index].createdAt);
                expect(reviewForm.updatedAt).toBe(reviewForms[index].updatedAt);
                expect(reviewForm.deletedAt).toBe(reviewForms[index].deletedAt);
                expect(reviewForm.cycleId).toBe(reviewForms[index].cycleId);
            });

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycle, 1);
        });
    });

    describe('getNineBoxOfCycle', () => {
        let reviewCycle: PerformanceReviewCycle;
        let fakeId: number;
        let fakeDate: Date;
        let nineBoxPoints: PerformanceReviewNineBoxPoint[];
        let template: PerformanceReviewTemplate;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeId, fakeDate, {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
            const formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                    withGoalScore: true,
                }),
            ];
            reviewCycle.formTemplates = formTemplates;
            const nineBoxPointsCount = faker.number.int({ max: 3, min: 1 });
            nineBoxPoints = Array(nineBoxPointsCount)
                .fill(1)
                .map(() => generateRandomNineBoxPoint());

            template = generateRandomTemplate();
            template.formTemplateId = formTemplates[0].id;
        });

        it('get nine box points successfully', async () => {
            repositoryMock.getFormReviewsOfCycleForNineBox.mockResolvedValueOnce(nineBoxPoints);
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            const response = await performanceReviewsService.getNineBoxOfCycle(reviewCycle);

            expect(response).toHaveLength(nineBoxPoints.length);

            response.forEach((nineBoxPoint, index) => {
                expect(nineBoxPoint.formScore).toBe(nineBoxPoints[index].formScore);
                expect(nineBoxPoint.goalsScore).toBe(nineBoxPoints[index].goalsScore);
                expect(nineBoxPoint.user.userId).toBe(nineBoxPoints[index].user.userId);
            });

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForNineBox, 1);
        });

        it('fail if the status of the cycle is not finished or in progress', async () => {
            reviewCycle.status = faker.helpers.arrayElement([
                PerformanceReviewCycleStatus.DRAFT,
                PerformanceReviewCycleStatus.SELECTION,
                PerformanceReviewCycleStatus.PENDING,
            ]);

            await expect(performanceReviewsService.getNineBoxOfCycle(reviewCycle)).rejects.toThrow(BadRequestError);
        });

        it('fail if the cycle is missing a subordinate template', async () => {
            const formTemplates = [
                generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                    evaluationType: faker.helpers.arrayElement([
                        PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                        PerformanceReviewEvaluationTypes.AUTO_REVIEW,
                        PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                        PerformanceReviewEvaluationTypes.PEER_REVIEW,
                    ]),
                }),
            ];
            reviewCycle.formTemplates = formTemplates;

            await expect(performanceReviewsService.getNineBoxOfCycle(reviewCycle)).rejects.toThrow(BadRequestError);
        });

        it('fail if the cycle subordinate template is missing goals score', async () => {
            reviewCycle.formTemplates![0].scoreConfig!.withGoalScore = false;
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);

            await expect(performanceReviewsService.getNineBoxOfCycle(reviewCycle)).rejects.toThrow(BadRequestError);
        });
    });

    describe('getReviewerSelectionStats', () => {
        let cycleId: number;
        let instanceId: number;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
        });

        it('successfully get reviewers selection stats for all_users mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });
            const countActiveAndUnclaimedUsers = fakePositiveInteger();
            const reviewedsAsignedCount = fakePositiveInteger();

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            usersPortMock.getCountOfUsersByStatus.mockResolvedValueOnce(countActiveAndUnclaimedUsers);
            repositoryMock.countReviewedsAssigned.mockResolvedValueOnce(reviewedsAsignedCount);

            const stats = await performanceReviewsService.getReviewerSelectionStats(instanceId, cycleId);

            expect(stats.totalTargetUsers).toBe(countActiveAndUnclaimedUsers);
            expect(stats.totalTargetUsersWithReviewerAssigned).toBe(reviewedsAsignedCount);
        });

        it('successfully get reviewers selection stats for target_users mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            const totalTargetUsers = fakePositiveInteger();
            const reviewedsAsignedCount = fakePositiveInteger();

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.countTargetUsers.mockResolvedValueOnce(totalTargetUsers);
            repositoryMock.countReviewedsAssigned.mockResolvedValueOnce(reviewedsAsignedCount);

            const stats = await performanceReviewsService.getReviewerSelectionStats(instanceId, cycleId);

            expect(stats.totalTargetUsers).toBe(totalTargetUsers);
            expect(stats.totalTargetUsersWithReviewerAssigned).toBe(reviewedsAsignedCount);
        });
    });

    describe('listCycleRevieweds', () => {
        let cycleId: number;
        let reviewedIds: number[];

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewedIds = [1, 2, 3];
        });

        it('successfully get revieweds list', async () => {
            const revieweds = reviewedIds.map((reviewedId) => generatePerformanceReviewReviewer({ cycleId, reviewedId }));

            repositoryMock.getRevieweds.mockResolvedValueOnce(revieweds);
            const returnedRevieweds = await performanceReviewsService.listCycleRevieweds(cycleId, reviewedIds);

            returnedRevieweds.forEach((reviewed, index) => {
                expect(reviewed.id).toBe(revieweds[index].id);
                expect(reviewed.createdAt).toBe(revieweds[index].createdAt);
                expect(reviewed.updatedAt).toBe(revieweds[index].updatedAt);
                expect(reviewed.deletedAt).toBe(revieweds[index].deletedAt);
                expect(reviewed.cycleId).toBe(revieweds[index].cycleId);
                expect(reviewed.reviewedId).toBe(revieweds[index].reviewedId);
                expect(reviewed.reviewerId).toBe(revieweds[index].reviewerId);
            });
        });

        it('successfully get empty revieweds list', async () => {
            repositoryMock.getRevieweds.mockResolvedValueOnce([]);
            const returnedRevieweds = await performanceReviewsService.listCycleRevieweds(cycleId, reviewedIds);

            expect(returnedRevieweds).toBeEmpty();
        });
    });

    describe('getReviewersOfReviewed', () => {
        let cycleId: number;
        let reviewedId: number;
        let reviewerIds: number[];

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewedId = fakePositiveInteger();
            reviewerIds = [1, 2, 3];
        });

        it('successfully get reviewers of reviewed', async () => {
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId }));

            repositoryMock.getReviewersOfReviewed.mockResolvedValueOnce(reviewers);
            const result = await performanceReviewsService.getReviewersOfReviewed(cycleId, reviewedId);

            result.forEach((reviewed, index) => {
                expect(reviewed.id).toBe(reviewers[index].id);
                expect(reviewed.createdAt).toBe(reviewers[index].createdAt);
                expect(reviewed.updatedAt).toBe(reviewers[index].updatedAt);
                expect(reviewed.deletedAt).toBe(reviewers[index].deletedAt);
                expect(reviewed.cycleId).toBe(reviewers[index].cycleId);
                expect(reviewed.reviewedId).toBe(reviewers[index].reviewedId);
                expect(reviewed.reviewerId).toBe(reviewers[index].reviewerId);
            });
        });

        it('successfully get only external reviewers of reviewed', async () => {
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId }));

            repositoryMock.getReviewersOfReviewed.mockResolvedValueOnce(reviewers);
            const result = await performanceReviewsService.getReviewersOfReviewed(cycleId, reviewedId, {
                evaluationType: PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
            });

            result.forEach((reviewed, index) => {
                expect(reviewed.id).toBe(reviewers[index].id);
                expect(reviewed.createdAt).toBe(reviewers[index].createdAt);
                expect(reviewed.updatedAt).toBe(reviewers[index].updatedAt);
                expect(reviewed.deletedAt).toBe(reviewers[index].deletedAt);
                expect(reviewed.cycleId).toBe(reviewers[index].cycleId);
                expect(reviewed.reviewedId).toBe(reviewers[index].reviewedId);
                expect(reviewed.reviewerId).toBe(reviewers[index].reviewerId);
            });
        });

        it('successfully get only internal reviewers of reviewed', async () => {
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId }));
            const evaluationType = faker.helpers.arrayElement([
                PerformanceReviewEvaluationTypes.PEER_REVIEW,
                PerformanceReviewEvaluationTypes.OTHERS_REVIEW,
            ]);

            repositoryMock.getReviewersOfReviewed.mockResolvedValueOnce(reviewers);
            const result = await performanceReviewsService.getReviewersOfReviewed(cycleId, reviewedId, { evaluationType });

            result.forEach((reviewed, index) => {
                expect(reviewed.id).toBe(reviewers[index].id);
                expect(reviewed.createdAt).toBe(reviewers[index].createdAt);
                expect(reviewed.updatedAt).toBe(reviewers[index].updatedAt);
                expect(reviewed.deletedAt).toBe(reviewers[index].deletedAt);
                expect(reviewed.cycleId).toBe(reviewers[index].cycleId);
                expect(reviewed.reviewedId).toBe(reviewers[index].reviewedId);
                expect(reviewed.reviewerId).toBe(reviewers[index].reviewerId);
            });
        });

        it('successfully get empty reviewers of reviewed', async () => {
            repositoryMock.getReviewersOfReviewed.mockResolvedValueOnce([]);
            const returnedRevieweds = await performanceReviewsService.getReviewersOfReviewed(cycleId, reviewedId);

            expect(returnedRevieweds).toBeEmpty();
        });
    });

    describe('listCycleReviewers', () => {
        let cycleId: number;
        let instanceId: number;
        let options: ListOptions;
        let reviewerIds: number[];

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
            options = {
                pagination: {
                    limit: 10,
                    offset: 0,
                },
            };
            reviewerIds = [1, 2, 3];
        });

        it('successfully get reviewers list', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewerId }));
            const totalCount = reviewers.length;
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            repositoryMock.getReviewers.mockResolvedValueOnce({ reviewers, totalCount });

            const result = await performanceReviewsService.listCycleReviewers(reviewCycle, options);

            expect(result.totalCount).toBe(totalCount);

            result.reviewers.forEach((reviewer, index) => {
                expect(reviewer.id).toBe(reviewers[index].id);
                expect(reviewer.createdAt).toBe(reviewers[index].createdAt);
                expect(reviewer.updatedAt).toBe(reviewers[index].updatedAt);
                expect(reviewer.deletedAt).toBe(reviewers[index].deletedAt);
                expect(reviewer.cycleId).toBe(reviewers[index].cycleId);
                expect(reviewer.reviewedId).toBe(reviewers[index].reviewedId);
                expect(reviewer.reviewerId).toBe(reviewers[index].reviewerId);
                expect(reviewer.reviewedCount).toBe(reviewers[index].reviewedCount);
                expect(reviewer.reviewerCount).toBe(reviewers[index].reviewerCount);
            });
        });

        it('successfully get empty reviewers list', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            const reviewers: PerformanceReviewReviewer[] = [];
            const totalCount = reviewers.length;
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getReviewers.mockResolvedValueOnce({ reviewers, totalCount });
            const result = await performanceReviewsService.listCycleReviewers(reviewCycle, options);

            expect(result.totalCount).toBe(totalCount);
            expect(result.reviewers).toBeEmpty();
        });
    });

    describe('getAllReviewers', () => {
        let cycleId: number;
        let reviewedReviewers: Map<number, number[]>;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            reviewedReviewers = new Map();
            reviewedReviewers.set(1, [2, 3]);
            reviewedReviewers.set(2, [3, 4]);
            reviewedReviewers.set(3, [4]);
        });

        it('successfully get reviewers list', async () => {
            const reviewers: PerformanceReviewReviewer[] = [];
            reviewedReviewers.forEach((reviewerIds, reviewedId) => {
                reviewerIds.forEach((reviewerId) => reviewers.push(generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId })));
            });
            repositoryMock.getAllReviewers.mockResolvedValueOnce(reviewers);

            const result = await performanceReviewsService.getAllReviewers(cycleId);

            reviewedReviewers.forEach((reviewerIds, reviewedId) => {
                const reviewersOfReviewed = result.get(reviewedId);

                expect(reviewersOfReviewed).toHaveLength(reviewerIds.length);

                const returnedReviewerIds = reviewersOfReviewed?.map((reviewer) => reviewer.reviewerId) ?? [];

                returnedReviewerIds.forEach((reviewerId, index) => expect(reviewerId).toBe(reviewerIds[index]));
            });
        });

        it('successfully get empty reviewers list', async () => {
            const reviewers: PerformanceReviewReviewer[] = [];
            repositoryMock.getAllReviewers.mockResolvedValueOnce(reviewers);
            const result = await performanceReviewsService.getAllReviewers(cycleId);

            expect(result).toBeEmpty();
        });
    });

    describe('updateReviewers', () => {
        let cycleId: number;
        let instanceId: number;
        let reviewedId: number;
        let reviewerIds: number[];

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
            reviewedId = fakePositiveInteger();
            reviewerIds = [1, 2, 3];
        });

        it('successful internal reviewers update for all_users mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });
            const reviewedUser = userWithProfileFieldsToUserDao(generateUserWithProfileFields({ id: reviewedId }));
            reviewedUser.instanceId = instanceId;
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId }));

            usersPortMock.getUser.mockResolvedValueOnce(reviewedUser);
            repositoryMock.deleteReviewers.mockResolvedValue();
            repositoryMock.upsertInternalReviewers.mockResolvedValueOnce(reviewers);
            usersPortMock.usersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewInternalReviewerIds.mockResolvedValueOnce(reviewerIds);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    reviewerIdsToAdd: reviewerIds,
                    hasOthers: false,
                    hasPeer: true,
                    hasPeerExternal: false,
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 0);
            checkMockCallsCount(usersPortMock.usersExist, 1);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 0);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 0);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 1);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 0);
        });

        it('successful internal reviewers update for target_users mode', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });
            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId }));

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            repositoryMock.deleteReviewers.mockResolvedValue();
            repositoryMock.upsertInternalReviewers.mockResolvedValueOnce(reviewers);
            usersPortMock.usersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewInternalReviewerIds.mockResolvedValueOnce(reviewerIds);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    reviewerIdsToAdd: reviewerIds,
                    hasOthers: false,
                    hasPeer: true,
                    hasPeerExternal: false,
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 0);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(usersPortMock.usersExist, 1);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 0);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 0);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 1);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 0);
        });

        it('successful external reviewers update', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.SELECTION,
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });

            const reviewedUser = userWithProfileFieldsToUserDao(generateUserWithProfileFields({ id: reviewedId }));
            reviewedUser.instanceId = instanceId;

            const externalReviewersIds = [fakePositiveInteger()];
            const upsertedExternalReviewers = reviewerIds.map((externalReviewerId) =>
                generatePerformanceReviewReviewer({ cycleId, externalReviewerId, reviewedId }),
            );

            usersPortMock.getUser.mockResolvedValueOnce(reviewedUser);
            repositoryMock.checkExternalReviewersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewExternalReviewerIds.mockResolvedValueOnce(externalReviewersIds);
            repositoryMock.deleteReviewers.mockResolvedValue();
            repositoryMock.upsertExternalReviewers.mockResolvedValue(upsertedExternalReviewers);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    externalReviewerIdsToAdd: externalReviewersIds,
                    hasOthers: false,
                    hasPeer: false,
                    hasPeerExternal: true,
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 1);
            checkMockCallsCount(repositoryMock.getTargetUser, 0);
            checkMockCallsCount(usersPortMock.usersExist, 0);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 1);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 1);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 0);
        });

        it('successful internal reviewers update for started cycle', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);
            const reviewers = reviewerIds.map((reviewerId) => generatePerformanceReviewReviewer({ cycleId, reviewedId, reviewerId }));

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            repositoryMock.deleteReviewers.mockResolvedValue();
            repositoryMock.upsertInternalReviewers.mockResolvedValueOnce(reviewers);
            usersPortMock.usersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewInternalReviewerIds.mockResolvedValueOnce(reviewerIds);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    reviewerIdsToAdd: reviewerIds,
                    hasOthers: false,
                    hasPeer: true,
                    hasPeerExternal: false,
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 0);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(usersPortMock.usersExist, 1);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 0);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 0);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 1);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 1);
        });

        it('successful external reviewers update for started cycle', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            const reviewedUser = userWithProfileFieldsToUserDao(generateUserWithProfileFields({ id: reviewedId }));
            reviewedUser.instanceId = instanceId;

            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);
            const externalReviewersIds = [fakePositiveInteger()];
            const upsertedExternalReviewers = reviewerIds.map((externalReviewerId) =>
                generatePerformanceReviewReviewer({ cycleId, externalReviewerId, reviewedId }),
            );

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            usersPortMock.getUser.mockResolvedValueOnce(reviewedUser);
            repositoryMock.checkExternalReviewersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewExternalReviewerIds.mockResolvedValueOnce(externalReviewersIds);
            repositoryMock.deleteReviewers.mockResolvedValue();
            repositoryMock.upsertExternalReviewers.mockResolvedValue(upsertedExternalReviewers);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    externalReviewerIdsToAdd: externalReviewersIds,
                    hasOthers: false,
                    hasPeer: false,
                    hasPeerExternal: true,
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 0);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(usersPortMock.usersExist, 0);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 1);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 1);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 1);
        });

        it('successful external reviewers update for started cycle if external reviewers already exist for the target user', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            const reviewedUser = userWithProfileFieldsToUserDao(generateUserWithProfileFields({ id: reviewedId }));
            reviewedUser.instanceId = instanceId;

            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);
            const externalReviewersIds = [fakePositiveInteger()];

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            repositoryMock.checkExternalReviewersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewExternalReviewerIds.mockResolvedValueOnce([]);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    externalReviewerIdsToAdd: externalReviewersIds,
                    hasOthers: false,
                    hasPeer: false,
                    hasPeerExternal: true,
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 0);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(usersPortMock.usersExist, 0);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 1);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 0);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 0);
        });

        it('successful internal reviewers update for started cycle if reviewers already exist for the target user', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            usersPortMock.usersExist.mockResolvedValueOnce(true);
            repositoryMock.getNewInternalReviewerIds.mockResolvedValueOnce([]);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    hasOthers: false,
                    hasPeer: true,
                    hasPeerExternal: false,
                    reviewerIdsToAdd: [reviewerIds[0]],
                }),
            ).resolves.not.toThrow();

            checkMockCallsCount(usersPortMock.getUser, 0);
            checkMockCallsCount(repositoryMock.getTargetUser, 1);
            checkMockCallsCount(usersPortMock.usersExist, 1);
            checkMockCallsCount(repositoryMock.checkExternalReviewersExist, 0);
            checkMockCallsCount(repositoryMock.getNewInternalReviewerIds, 1);
            checkMockCallsCount(repositoryMock.getNewExternalReviewerIds, 0);
            checkMockCallsCount(repositoryMock.upsertExternalReviewers, 0);
            checkMockCallsCount(repositoryMock.upsertInternalReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteReviewers, 0);
            checkMockCallsCount(repositoryMock.deleteFormReviewsOfCycleForReviewerIds, 0);
            checkMockCallsCount(repositoryMock.createFormReviews, 0);
        });

        it('unsuccessful external reviewers update for started cycle if external reviewers do not exist', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            const reviewedUser = userWithProfileFieldsToUserDao(generateUserWithProfileFields({ id: reviewedId }));
            reviewedUser.instanceId = instanceId;

            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);
            const externalReviewersIds = [fakePositiveInteger()];

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            repositoryMock.checkExternalReviewersExist.mockResolvedValueOnce(false);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    externalReviewerIdsToAdd: externalReviewersIds,
                    hasOthers: false,
                    hasPeer: false,
                    hasPeerExternal: true,
                }),
            ).rejects.toThrow(BadRequestError);
        });

        it('unsuccessful internal reviewers update for started cycle if reviewers do not exist', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.PEER_REVIEW],
                instanceId: instanceId,
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
                usersMode: PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS,
            });

            const reviewedTargetUser = generateRandomPerformanceReviewTargetUser(cycleId, reviewedId);

            repositoryMock.getTargetUser.mockResolvedValueOnce(reviewedTargetUser);
            usersPortMock.usersExist.mockResolvedValueOnce(false);

            await expect(
                performanceReviewsService.updateReviewers(instanceId, reviewCycle, reviewedId, {
                    hasOthers: false,
                    hasPeer: true,
                    hasPeerExternal: false,
                    reviewerIdsToAdd: [reviewerIds[0]],
                }),
            ).rejects.toThrow(BadRequestError);
        });
    });

    describe('listTemplates', () => {
        let template: PerformanceReviewTemplate;

        beforeEach(() => {
            template = generateRandomTemplate();
        });

        it('successful if no templates exist', async () => {
            repositoryMock.listTemplates.mockResolvedValueOnce({ templates: [], totalCount: 0 });

            const result = await performanceReviewsService.listTemplates(template.instanceId, {
                pagination: {
                    limit: fakePositiveInteger(),
                    offset: fakePositiveInteger(),
                },
            });

            expect(result.totalCount).toBe(0);
            expect(result.templates).toHaveLength(0);
        });

        it('successful if one template exists', async () => {
            repositoryMock.listTemplates.mockResolvedValueOnce({ templates: [template], totalCount: 1 });

            const result = await performanceReviewsService.listTemplates(template.instanceId, {
                pagination: {
                    limit: fakePositiveInteger(),
                    offset: fakePositiveInteger(),
                },
            });

            expect(result.totalCount).toBe(1);
            expect(result.templates).toHaveLength(1);
            expect(result.templates[0]).toBe(template);
        });
    });

    describe('getReviewedUsers', () => {
        let cycleId: number;
        let reviewedUsers: PerformanceReviewUser[];
        let instanceId: number;
        let reviewedCount: number;
        let reviewCycle: PerformanceReviewCycle;
        let loggedUser: UserDAO;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
            reviewedCount = faker.number.int({ max: 3, min: 1 });
            reviewedUsers = Array(reviewedCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewUser(cycleId));
            reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), { instanceId: instanceId });
            loggedUser = new UserDAO();
            loggedUser.employeeInternalId = faker.lorem.word();
            loggedUser.firstName = faker.lorem.word();
            loggedUser.lastName = faker.lorem.word();
            loggedUser.instanceId = instanceId;
            loggedUser.id = fakePositiveInteger();
        });

        it('successfully get reviewers list for all_users mode', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.SELECTION;
            reviewCycle.reviewedUsersMode = PerformanceReviewCycleReviewedUsersMode.ALL_USERS;

            const users = reviewedUsers.map((reviewed) => {
                const user = new UserDAO();
                user.employeeInternalId = reviewed.employeeInternalId;
                user.firstName = reviewed.firstName;
                user.lastName = reviewed.lastName;
                user.instanceId = instanceId;
                user.id = reviewed.userId;
                return user;
            });

            usersPortMock.listAll.mockResolvedValueOnce({ count: reviewedCount, rows: users });

            const result = await performanceReviewsService.getReviewedUsers(reviewCycle, loggedUser, {});

            expect(result.totalCount).toBe(reviewedCount);

            result.users.forEach((user, index) => {
                expect(user.firstName).toBe(reviewedUsers[index].firstName);
                expect(user.lastName).toBe(reviewedUsers[index].lastName);
                expect(user.employeeInternalId).toBe(reviewedUsers[index].employeeInternalId);
                expect(user.userId).toBe(reviewedUsers[index].userId);
            });
        });

        it('successfully get reviewers list for target_users mode', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.SELECTION;
            reviewCycle.reviewedUsersMode = PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS;

            const targetUsers = reviewedUsers.map((reviewed) => generateRandomPerformanceReviewTargetUser(cycleId, reviewed.userId));
            const users = reviewedUsers.map((reviewed) => {
                const user = new UserDAO();
                user.employeeInternalId = reviewed.employeeInternalId;
                user.firstName = reviewed.firstName;
                user.lastName = reviewed.lastName;
                user.instanceId = instanceId;
                user.id = reviewed.userId;
                return user;
            });

            repositoryMock.getTargetUsersByFilters.mockResolvedValueOnce({ targetUsers: targetUsers, totalCount: reviewedCount });
            usersPortMock.getUsersByPks.mockResolvedValueOnce(users);

            const result = await performanceReviewsService.getReviewedUsers(reviewCycle, loggedUser, {});

            expect(result.totalCount).toBe(reviewedCount);

            result.users.forEach((user, index) => {
                expect(user.firstName).toBe(reviewedUsers[index].firstName);
                expect(user.lastName).toBe(reviewedUsers[index].lastName);
                expect(user.employeeInternalId).toBe(reviewedUsers[index].employeeInternalId);
                expect(user.userId).toBe(reviewedUsers[index].userId);
            });
        });
    });

    describe('getVisibleReviewedUsers', () => {
        let cycleId: number;
        let reviewedUsers: PerformanceReviewUser[];
        let instanceId: number;
        let reviewedCount: number;
        let reviewCycle: PerformanceReviewCycle;
        let loggedUser: UserDAO;

        beforeEach(() => {
            cycleId = fakePositiveInteger();
            instanceId = fakePositiveInteger();
            reviewedCount = faker.number.int({ max: 3, min: 1 });
            reviewedUsers = Array(reviewedCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewUser(cycleId));
            reviewCycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), { instanceId: instanceId });
            loggedUser = new UserDAO();
            loggedUser.employeeInternalId = faker.lorem.word();
            loggedUser.firstName = faker.lorem.word();
            loggedUser.lastName = faker.lorem.word();
            loggedUser.instanceId = instanceId;
            loggedUser.id = fakePositiveInteger();
        });

        it('successfully get reviewers list for all_users mode', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.SELECTION;
            reviewCycle.reviewedUsersMode = PerformanceReviewCycleReviewedUsersMode.ALL_USERS;

            const users = reviewedUsers.map((reviewed) => {
                const user = new UserDAO();
                user.employeeInternalId = reviewed.employeeInternalId;
                user.firstName = reviewed.firstName;
                user.lastName = reviewed.lastName;
                user.instanceId = instanceId;
                user.id = reviewed.userId;
                return user;
            });

            usersPortMock.getSubordinateIds.mockResolvedValueOnce(users.map((user) => user.id));
            usersPortMock.listAll.mockResolvedValueOnce({ count: reviewedCount, rows: users });

            const result = await performanceReviewsService.getVisibleReviewedUsers(reviewCycle, loggedUser, {});

            expect(result.totalCount).toBe(reviewedCount);

            result.users.forEach((user, index) => {
                expect(user.firstName).toBe(reviewedUsers[index].firstName);
                expect(user.lastName).toBe(reviewedUsers[index].lastName);
                expect(user.employeeInternalId).toBe(reviewedUsers[index].employeeInternalId);
                expect(user.userId).toBe(reviewedUsers[index].userId);
            });
        });

        it('successfully get reviewers list for target_users mode', async () => {
            reviewCycle.status = PerformanceReviewCycleStatus.SELECTION;
            reviewCycle.reviewedUsersMode = PerformanceReviewCycleReviewedUsersMode.TARGETED_USERS;

            const targetUsers = reviewedUsers.map((reviewed) => generateRandomPerformanceReviewTargetUser(cycleId, reviewed.userId));
            const users = reviewedUsers.map((reviewed) => {
                const user = new UserDAO();
                user.employeeInternalId = reviewed.employeeInternalId;
                user.firstName = reviewed.firstName;
                user.lastName = reviewed.lastName;
                user.instanceId = instanceId;
                user.id = reviewed.userId;
                return user;
            });

            repositoryMock.getTargetUsersByFilters.mockResolvedValueOnce({ targetUsers: targetUsers, totalCount: reviewedCount });
            usersPortMock.getUsersByPks.mockResolvedValueOnce(users);

            const result = await performanceReviewsService.getVisibleReviewedUsers(reviewCycle, loggedUser, {});

            expect(result.totalCount).toBe(reviewedCount);

            result.users.forEach((user, index) => {
                expect(user.firstName).toBe(reviewedUsers[index].firstName);
                expect(user.lastName).toBe(reviewedUsers[index].lastName);
                expect(user.employeeInternalId).toBe(reviewedUsers[index].employeeInternalId);
                expect(user.userId).toBe(reviewedUsers[index].userId);
            });
        });
    });

    describe('createExternalReviewer', () => {
        let email: string;
        let instanceId: number;
        let firstName: string;
        let lastName: string;
        let fakeId: number;
        let fakeDate: Date;

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            email = faker.word.words();
            firstName = faker.word.words();
            lastName = faker.word.words();
            fakeId = fakePositiveInteger();
            fakeDate = faker.date.recent();
        });

        it('create external reviewer successfully', async () => {
            repositoryMock.newExternalReviewer.mockImplementationOnce((externalReviewer) => {
                const newExternalReviewer = new PerformanceReviewExternalReviewer({
                    createdAt: fakeDate,
                    deletedAt: null,
                    duplicationId: externalReviewer.duplicationId,
                    email: externalReviewer.email,
                    firstName: externalReviewer.firstName,
                    id: fakeId,
                    instanceId: externalReviewer.instanceId,
                    lastName: externalReviewer.lastName,
                    updatedAt: fakeDate,
                });
                return Promise.resolve(newExternalReviewer);
            });

            const result = await performanceReviewsService.createExternalReviewer(instanceId, { email, firstName, lastName });

            expect(result).toBeInstanceOf(PerformanceReviewExternalReviewer);
            expect(result.firstName).toBe(firstName);
            expect(result.lastName).toBe(lastName);
            expect(result.instanceId).toBe(instanceId);
            expect(result.id).toBe(fakeId);
            expect(result.email).toBe(email);
            expect(result.createdAt).toBe(fakeDate);
            expect(result.updatedAt).toBe(fakeDate);
            expect(result.deletedAt).toBeNull();
        });
    });

    describe('duplicateTemplate', () => {
        let user: User;
        let template: PerformanceReviewTemplate;

        beforeEach(() => {
            template = generateRandomTemplate();
            user = generateRandomUser();
        });

        it('duplicate template successfully', async () => {
            user.instanceId = template.instanceId;
            repositoryMock.getTemplateById.mockResolvedValueOnce(template);
            const formTemplate = generateFormTemplateWithValues(fakePositiveInteger(), user.instanceId, {
                creatorId: user.id,
                description: template.formTemplate!.description!,
                origin: FORM_TEMPLATE_ORIGINS.PERFORMANCE_REVIEW,
                questionCount: template.questionsCount,
                questionTemplates: template.formTemplate!.questionTemplates!,
                status: FORM_TEMPLATE_STATUS.DRAFT,
                title: template.title,
            });
            formTemplatesPortMock.createFormTemplate.mockResolvedValueOnce(Promise.resolve(formTemplate));

            repositoryMock.createTemplate.mockImplementationOnce(() => {
                const newTemplate = new PerformanceReviewTemplate({
                    createdAt: faker.date.recent(),
                    creatorId: formTemplate.creatorId,
                    duplicationId: generateRandomTemplate().duplicationId,
                    formTemplateId: formTemplate.id!,
                    id: fakePositiveInteger(),
                    instanceId: template.instanceId,
                    goalsQuestionTemplateId: template.goalsQuestionTemplateId,
                    competenciesQuestionTemplateId: template.competenciesQuestionTemplateId,
                    questionsCount: formTemplate.questionsCount,
                    status: formTemplate.status!,
                    title: formTemplate.title,
                    updatedAt: faker.date.recent(),
                });
                return Promise.resolve(newTemplate);
            });

            const result = await performanceReviewsService.duplicateTemplate(template.id, user);

            expect(result.id).not.toBe(template.id);
            expect(result.instanceId).toBe(template.instanceId);
            expect(result.status).toBe(FORM_TEMPLATE_STATUS.DRAFT);
            expect(result.title).toBe(template.title);
            expect(result.goalsQuestionTemplate).toBe(template.goalsQuestionTemplate);
            expect(result.goalsQuestionTemplateId).toBe(template.goalsQuestionTemplateId);
            expect(result.competenciesQuestionTemplate).toBe(template.competenciesQuestionTemplate);
            expect(result.competenciesQuestionTemplateId).toBe(template.competenciesQuestionTemplateId);
            expect(result.createdAt).not.toBe(template.createdAt);
            expect(result.updatedAt).not.toBe(template.updatedAt);
            expect(result.deletedAt).toBeNull();
            expect(result.creatorId).toBe(user.id);
            expect(result.duplicationId).not.toBe(template.duplicationId);
            expect(result.formTemplateId).not.toBe(template.formTemplateId);
            expect(result.questionsCount).toBe(template.questionsCount);
            expect(result.formTemplate).toBeInstanceOf(FormTemplate);
        });
    });

    describe('findExternalReviewerByEmail', () => {
        let externalReviewer: PerformanceReviewExternalReviewer;

        beforeEach(() => {
            externalReviewer = generateRandomExternalReviewer();
        });

        it('find external reviewer', async () => {
            repositoryMock.getExternalReviewerByEmail.mockResolvedValueOnce(externalReviewer);

            const result = await performanceReviewsService.findExternalReviewerByEmail(externalReviewer.instanceId, externalReviewer.email);

            expect(result).toBe(externalReviewer);
        });
    });

    describe('listExternalReviewers', () => {
        let reviewer: PerformanceReviewExternalReviewer;

        beforeEach(() => {
            reviewer = generateRandomExternalReviewer();
        });

        it('successful if no reviewers exist', async () => {
            repositoryMock.listExternalReviewers.mockResolvedValueOnce({ externalReviewers: [], totalCount: 0 });

            const result = await performanceReviewsService.listExternalReviewers(reviewer.instanceId, {
                pagination: {
                    limit: fakePositiveInteger(),
                    offset: fakePositiveInteger(),
                },
            });

            expect(result.totalCount).toBe(0);
            expect(result.externalReviewers).toHaveLength(0);
        });

        it('successful if one reviewer exists', async () => {
            repositoryMock.listExternalReviewers.mockResolvedValueOnce({ externalReviewers: [reviewer], totalCount: 1 });

            const result = await performanceReviewsService.listExternalReviewers(reviewer.instanceId, {
                pagination: {
                    limit: fakePositiveInteger(),
                    offset: fakePositiveInteger(),
                },
            });

            expect(result.totalCount).toBe(1);
            expect(result.externalReviewers).toHaveLength(1);
            expect(result.externalReviewers[0]).toBe(reviewer);
        });
    });

    describe('getExternalReviewers', () => {
        let instanceId: number;
        let externalReviewerIds: number[];

        beforeEach(() => {
            instanceId = fakePositiveInteger();
            externalReviewerIds = faker.helpers.multiple(faker.number.int);
        });

        it('successfully get external reviewers', async () => {
            const externalReviewers = externalReviewerIds.map((externalReviewerId) =>
                generateRandomExternalReviewer({ id: externalReviewerId, instanceId }),
            );

            repositoryMock.getExternalReviewers.mockResolvedValueOnce(externalReviewers);
            const result = await performanceReviewsService.getExternalReviewers(instanceId, externalReviewerIds);

            result.forEach((externalReviewer, index) => {
                expect(externalReviewer.id).toBe(externalReviewers[index].id);
                expect(externalReviewer.instanceId).toBe(externalReviewers[index].instanceId);
                expect(externalReviewer.createdAt).toBe(externalReviewers[index].createdAt);
                expect(externalReviewer.updatedAt).toBe(externalReviewers[index].updatedAt);
                expect(externalReviewer.deletedAt).toBe(externalReviewers[index].deletedAt);
                expect(externalReviewer.firstName).toBe(externalReviewers[index].firstName);
                expect(externalReviewer.lastName).toBe(externalReviewers[index].lastName);
                expect(externalReviewer.email).toBe(externalReviewers[index].email);
            });
        });

        it('successfully get empty external reviewers list', async () => {
            repositoryMock.getExternalReviewers.mockResolvedValueOnce([]);
            const result = await performanceReviewsService.getExternalReviewers(instanceId, externalReviewerIds);

            expect(result).toBeEmpty();
        });
    });

    describe('getExternalReviewerById', () => {
        let externalReviewer: PerformanceReviewExternalReviewer;

        beforeEach(() => {
            const fakeId = fakePositiveInteger();
            externalReviewer = generateRandomExternalReviewer({ id: fakeId });
        });

        it('find external reviewer successfully', async () => {
            repositoryMock.getExternalReviewerById.mockResolvedValueOnce(externalReviewer);

            const result = await performanceReviewsService.getExternalReviewerById(externalReviewer.id);

            expect(result).toBe(externalReviewer);
        });

        it('throw not found error if external reviewer does not exist', async () => {
            repositoryMock.getEditRequestById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.getExternalReviewerById(externalReviewer.id)).rejects.toThrow(NotFoundError);
        });
    });

    describe('findVisibleCycleForExternalReviewer', () => {
        let externalReviewer: PerformanceReviewExternalReviewer;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            externalReviewer = generateRandomExternalReviewer();
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                instanceId: externalReviewer.instanceId,
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.IN_PROGRESS, PerformanceReviewCycleStatus.FINISHED]),
            });
        });

        it('find visible cycle successfully if user is reviewer', async () => {
            repositoryMock.getExternalReviewerById.mockResolvedValueOnce(externalReviewer);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForExternalReviewerId.mockImplementationOnce((cycleId, externalReviewerId) => {
                const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), cycleId, { reviewerId: externalReviewerId });
                formReview.reviewed = generateRandomPerformanceReviewUser(formReview.reviewedId);
                return Promise.resolve([formReview]);
            });

            const result = await performanceReviewsService.findVisibleCycleForExternalReviewer(reviewCycle.id, externalReviewer.id);

            expect(result).toBe(reviewCycle);
            expect(result.reviewedFormReviews).toBeNull();
            expect(result.reviewerFormReviews).not.toBeNull();

            expect(result.reviewerFormReviews!.length).toBeGreaterThan(0);

            checkCycleFormReviewsHaveUsers(result.reviewerFormReviews!);

            checkMockCallsCount(repositoryMock.getExternalReviewerById, 1);
            checkMockCallsCount(repositoryMock.getCycleById, 1);

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForExternalReviewerId, 1);
        });

        it('throw not found error if external reviewer does not exist', async () => {
            repositoryMock.getExternalReviewerById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findVisibleCycleForExternalReviewer(reviewCycle.id, externalReviewer.id)).rejects.toThrow(
                NotFoundError,
            );
        });

        it('throw not found error if cycle does not exist', async () => {
            repositoryMock.getExternalReviewerById.mockResolvedValueOnce(externalReviewer);
            repositoryMock.getCycleById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findVisibleCycleForExternalReviewer(reviewCycle.id, externalReviewer.id)).rejects.toThrow(
                NotFoundError,
            );
        });

        it('throw not found error if cycle has not started', async () => {
            reviewCycle.status = faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(performanceReviewsService.findVisibleCycleForExternalReviewer(reviewCycle.id, externalReviewer.id)).rejects.toThrow(
                NotFoundError,
            );
        });
    });

    describe('findVisibleExternalReview', () => {
        let fakeId: number;
        let externalReviewer: PerformanceReviewExternalReviewer;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            fakeId = fakePositiveInteger();
            externalReviewer = generateRandomExternalReviewer();
            reviewCycle = generateRandomPerformanceReviewCycle();
        });

        it('find visible review form successfully if userId matches reviewer', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, reviewCycle.id, {
                externalReviewerId: externalReviewer.id,
            });
            repositoryMock.getExternalReviewerById.mockResolvedValue(externalReviewer);
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            const result = await performanceReviewsService.findVisibleExternalReview(fakeId, reviewCycle.id, externalReviewer.id);

            expect(result).toBe(reviewForm);
        });

        it('throw not found error if external reviewer does not exist', async () => {
            repositoryMock.getExternalReviewerById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findVisibleExternalReview(fakeId, reviewCycle.id, externalReviewer.id)).rejects.toThrow(
                NotFoundError,
            );
        });

        it('throw not found error if review does not exist', async () => {
            repositoryMock.getExternalReviewerById.mockResolvedValue(externalReviewer);
            repositoryMock.getFormReviewById.mockResolvedValueOnce(null);

            await expect(performanceReviewsService.findVisibleExternalReview(fakeId, reviewCycle.id, externalReviewer.id)).rejects.toThrow(
                NotFoundError,
            );
        });

        it('throw not found error if userId does not match reviewer', async () => {
            const reviewForm = generatePerformanceReviewFormReviewWithValues(fakeId, reviewCycle.id, {
                reviewerId: faker.number.int({ min: externalReviewer.id + 1 }),
            });
            repositoryMock.getFormReviewById.mockResolvedValueOnce(reviewForm);

            await expect(performanceReviewsService.findVisibleExternalReview(fakeId, reviewCycle.id, externalReviewer.id)).rejects.toThrow(
                NotFoundError,
            );
        });
    });

    describe('getTemplateByIds', () => {
        let ids: number[];

        beforeEach(() => {
            ids = faker.helpers.multiple(faker.number.int);
        });

        it('successfully get templates', async () => {
            const templates = ids.map((id) => generateRandomTemplate(id));

            repositoryMock.getTemplatesByIds.mockResolvedValueOnce(templates);
            const result = await performanceReviewsService.getTemplateByIds(ids);

            result.forEach((template, index) => {
                expect(template.id).toBe(templates[index].id);
                expect(template.instanceId).toBe(templates[index].instanceId);
                expect(template.createdAt).toBe(templates[index].createdAt);
                expect(template.updatedAt).toBe(templates[index].updatedAt);
                expect(template.deletedAt).toBe(templates[index].deletedAt);
            });
        });
    });

    describe('createTemplatesOnly', () => {
        let creationInterfaces: PerformanceReviewTemplateForCreation[];

        beforeEach(() => {
            const templatesCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(templatesCount)
                .fill(1)
                .map(() => generateRandomTemplateForCreation());
        });

        it('should create templates', async () => {
            repositoryMock.createTemplates.mockImplementationOnce((templates: PerformanceReviewTemplateForCreation[]) => {
                const newTemplates = templates.map((ft) => {
                    return generatePerformanceReviewTemplateFromCreation(ft);
                });
                return Promise.resolve(newTemplates);
            });
            const result = await performanceReviewsService.createTemplatesOnly(creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((formTemplate, index) => {
                expect(formTemplate).toBeInstanceOf(PerformanceReviewTemplate);
                expect(formTemplate.formTemplateId).toBe(creationInterfaces[index].formTemplateId);
                expect(formTemplate.goalsQuestionTemplateId).toBe(creationInterfaces[index].goalsQuestionTemplateId);
                expect(formTemplate.competenciesQuestionTemplateId).toBe(creationInterfaces[index].competenciesQuestionTemplateId);
                expect(formTemplate.createdAt).not.toBeNull();
                expect(formTemplate.updatedAt).not.toBeNull();
                expect(formTemplate.id).not.toBeNull();
                expect(formTemplate.deletedAt).toBeNull();
            });
        });
    });

    describe('createReviewCyclesReviewersOnly', () => {
        let creationInterfaces: PerformanceReviewReviewerForCreation[];

        beforeEach(() => {
            const reviewerCount = faker.number.int({ max: 3, min: 1 });
            creationInterfaces = Array(reviewerCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewReviewerForCreation());
        });

        it('should create reviewers', async () => {
            repositoryMock.createReviewCyclesReviewers.mockImplementationOnce((reviewers: PerformanceReviewReviewerForCreation[]) => {
                const newTemplates = reviewers.map((reviewer) => {
                    return generatePerformanceReviewReviewerFromCreation(reviewer);
                });
                return Promise.resolve(newTemplates);
            });
            const result = await performanceReviewsService.createReviewCyclesReviewersOnly(creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((reviewer, index) => {
                expect(reviewer).toBeInstanceOf(PerformanceReviewReviewer);
                expect(reviewer.cycleId).toBe(creationInterfaces[index].cycleId);
                expect(reviewer.externalReviewerId).toBe(creationInterfaces[index].externalReviewerId);
                expect(reviewer.reviewedId).toBe(creationInterfaces[index].reviewedId);
                expect(reviewer.reviewerId).toBe(creationInterfaces[index].reviewerId);
                expect(reviewer.createdAt).not.toBeNull();
                expect(reviewer.updatedAt).not.toBeNull();
                expect(reviewer.id).not.toBeNull();
                expect(reviewer.deletedAt).toBeNull();
            });
        });
    });

    describe('createTargetUsersOnly', () => {
        let creationInterfaces: PerformanceReviewTargetUserForCreation[];
        let cycleId: number;

        beforeEach(() => {
            const targetUserCount = faker.number.int({ max: 3, min: 1 });
            cycleId = fakePositiveInteger();
            creationInterfaces = Array(targetUserCount)
                .fill(1)
                .map(() => generateRandomPerformanceReviewTargetUserForCreation(cycleId));
        });

        it('should create target users', async () => {
            repositoryMock.upsertTargetUsers.mockImplementationOnce((targetUsers: PerformanceReviewTargetUserForCreation[]) => {
                const newTemplates = targetUsers.map((targetUser) => {
                    return getPerformanceReviewTargetUserFromCreation(targetUser);
                });
                return Promise.resolve(newTemplates);
            });
            const result = await performanceReviewsService.createTargetUsersOnly(creationInterfaces);

            expect(result).toHaveLength(creationInterfaces.length);

            result.forEach((reviewer, index) => {
                expect(reviewer).toBeInstanceOf(PerformanceReviewTargetUser);
                expect(reviewer.cycleId).toBe(cycleId);
                expect(reviewer.shareStatus).toBe(creationInterfaces[index].shareStatus);
                expect(reviewer.userId).toBe(creationInterfaces[index].userId);
                expect(reviewer.responsibleId).toBe(creationInterfaces[index].responsibleId);
                expect(reviewer.createdAt).not.toBeNull();
                expect(reviewer.updatedAt).not.toBeNull();
                expect(reviewer.id).not.toBeNull();
                expect(reviewer.deletedAt).toBeNull();
            });
        });
    });

    describe('getUnreadCount', () => {
        it('successfully get unread count', async () => {
            const fakeResult = fakePositiveInteger();
            repositoryMock.userHasPendingReviewActions.mockResolvedValueOnce(fakeResult);
            const result = await performanceReviewsService.getUnreadCount(generateRandomUser());

            expect(result).toBe(fakeResult);
        });
    });

    describe('clearUnreadCount', () => {
        it('should throw not implemented error', () => {
            expect(() => performanceReviewsService.clearUnreadCount(generateRandomUser())).toThrow(
                new NotImplementedError('Method has not been implemented', ErrorCodes.NOT_IMPLEMENTED_METHOD),
            );
        });
    });

    describe('clearUnreadCountOfId', () => {
        it('should throw not implemented error', () => {
            const user = generateRandomUser();

            expect(() =>
                performanceReviewsService.clearUnreadCountOfId(user, generateRandomInstance(user.instanceId), fakePositiveInteger()),
            ).toThrow(new NotImplementedError('Method has not been implemented', ErrorCodes.NOT_IMPLEMENTED_METHOD));
        });
    });

    describe('getUnreadCountOfId', () => {
        it('should throw not implemented error', () => {
            expect(() => performanceReviewsService.getUnreadCountOfId(generateRandomUser(), fakePositiveInteger())).toThrow(
                new NotImplementedError('Method has not been implemented', ErrorCodes.NOT_IMPLEMENTED_METHOD),
            );
        });
    });

    describe('editTargetUserPartially', () => {
        let targetUser: PerformanceReviewTargetUser;
        let instanceId: number;
        let params: UpdatePerformanceReviewTargetUserVC;
        let loggedUser: User;

        beforeEach(() => {
            const cycleId = fakePositiveInteger();
            loggedUser = generateRandomUser();
            targetUser = generateRandomPerformanceReviewTargetUser(cycleId);
            instanceId = fakePositiveInteger();
            params = {
                summary: faker.lorem.words(),
            };
        });

        it('successfully edit summary of target user', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(targetUser.cycleId, faker.date.recent(), {
                instanceId: instanceId,
                withSummary: true,
            });
            targetUser.responsibleId = loggedUser.id;

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            const updatedTargetUser = new PerformanceReviewTargetUser({ ...targetUser, summary: params.summary });
            repositoryMock.saveTargetUser.mockResolvedValueOnce(updatedTargetUser);

            const result = await performanceReviewsService.editTargetUserPartially(targetUser, loggedUser.id, instanceId, params);

            expect(result).toBe(updatedTargetUser);
            expect(result.summary).toBe(params.summary);
        });

        it('throw bad request error if review has already been shared', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(targetUser.cycleId, faker.date.recent(), {
                instanceId: instanceId,
                withSummary: true,
            });
            targetUser.shareStatus = faker.helpers.arrayElement(isFormSharedStatuses);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(performanceReviewsService.editTargetUserPartially(targetUser, loggedUser.id, instanceId, params)).rejects.toThrow(
                BadRequestError,
            );
        });

        it('throw bad request error if review cycle does not accept summary', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(targetUser.cycleId, faker.date.recent(), {
                instanceId: instanceId,
                withSummary: false,
            });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(performanceReviewsService.editTargetUserPartially(targetUser, loggedUser.id, instanceId, params)).rejects.toThrow(
                BadRequestError,
            );
        });

        it('throw access denied error if user is not responsible', async () => {
            const reviewCycle = generatePerformanceReviewCycleWithValues(targetUser.cycleId, faker.date.recent(), {
                instanceId: instanceId,
                withSummary: true,
            });
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(performanceReviewsService.editTargetUserPartially(targetUser, loggedUser.id, instanceId, params)).rejects.toThrow(
                ForbiddenError,
            );
        });
    });

    describe('scoreNeedsObfuscationFromReview', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewId: number;
        let reviewedId: number;
        let userId: number;
        let evaluationType: PerformanceReviewEvaluationTypesValues;

        beforeEach(() => {
            evaluationType = faker.helpers.arrayElement([PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW, PerformanceReviewEvaluationTypes.AUTO_REVIEW]);
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [evaluationType],
            });
            reviewId = fakePositiveInteger();
        });

        it('score needs obfuscation true when logged user is not the boss of reviewed user', () => {
            reviewCycle.formTemplates![0].scoreConfig!.withHiddenScoreForReviewed = true;
            reviewedId = fakePositiveInteger();
            const responsibleId = fakePositiveInteger();
            userId = responsibleId + 1; // User is not the boss
            const reviewForm = generatePerformanceReviewFormReviewWithValues(reviewId, reviewCycle.id, {
                reviewedId,
                responsibleId,
                type: evaluationType,
            });

            expect(performanceReviewsService.scoreNeedsObfuscationFromReview(reviewCycle, reviewForm, userId)).toBeTrue();
        });

        it('score needs obfuscation false because logged user is the boss of reviewed user', () => {
            reviewCycle.formTemplates![0].scoreConfig!.withHiddenScoreForReviewed = true;
            reviewedId = fakePositiveInteger();
            const responsibleId = fakePositiveInteger();
            userId = responsibleId; // User is the boss
            const reviewForm = generatePerformanceReviewFormReviewWithValues(reviewId, reviewCycle.id, {
                reviewedId,
                responsibleId,
                type: evaluationType,
            });

            expect(performanceReviewsService.scoreNeedsObfuscationFromReview(reviewCycle, reviewForm, userId)).toBeFalse();
        });

        it('score needs obfuscation true because reviewed user has no boss', () => {
            reviewCycle.formTemplates![0].scoreConfig!.withHiddenScoreForReviewed = true;
            reviewedId = fakePositiveInteger();
            userId = fakePositiveInteger();
            const reviewForm = generatePerformanceReviewFormReviewWithValues(reviewId, reviewCycle.id, {
                reviewedId,
                type: evaluationType,
            });
            // Set responsibleId to null to simulate a user with no boss
            (reviewForm as any).responsibleId = null;

            expect(performanceReviewsService.scoreNeedsObfuscationFromReview(reviewCycle, reviewForm, userId)).toBeTrue();
        });

        it('score needs obfuscation false because there is no subordinate type', () => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [
                    faker.helpers.arrayElement([
                        PerformanceReviewEvaluationTypes.PEER_REVIEW,
                        PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW,
                        PerformanceReviewEvaluationTypes.OTHERS_REVIEW,
                        PerformanceReviewEvaluationTypes.BOSS_REVIEW,
                    ]),
                ],
            });
            reviewCycle.formTemplates![0].scoreConfig!.withHiddenScoreForReviewed = true;
            reviewedId = fakePositiveInteger();
            userId = reviewedId;
            const reviewForm = generatePerformanceReviewFormReviewWithValues(reviewId, reviewCycle.id, {
                reviewedId,
                type: evaluationType,
            });

            expect(performanceReviewsService.scoreNeedsObfuscationFromReview(reviewCycle, reviewForm, userId)).toBe(false);
        });

        it('score needs obfuscation false because withHiddenScoreForReviewed is false', () => {
            reviewCycle.formTemplates![0].scoreConfig!.withHiddenScoreForReviewed = false;
            reviewedId = fakePositiveInteger();
            userId = reviewedId;
            const reviewForm = generatePerformanceReviewFormReviewWithValues(reviewId, reviewCycle.id, {
                reviewedId,
                type: evaluationType,
            });

            expect(performanceReviewsService.scoreNeedsObfuscationFromReview(reviewCycle, reviewForm, userId)).toBe(false);
        });
    });

    describe('scoreNeedsObfuscationFromTarget', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewedId: number;
        let userId: number;
        let evaluationType: PerformanceReviewEvaluationTypesValues;

        beforeEach(() => {
            evaluationType = faker.helpers.arrayElement([PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW, PerformanceReviewEvaluationTypes.AUTO_REVIEW]);
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [evaluationType],
            });
        });

        it('score needs obfuscation false because withHiddenScoreForReviewed is false', () => {
            reviewCycle.formTemplates![0].scoreConfig!.withHiddenScoreForReviewed = false;
            reviewedId = fakePositiveInteger();
            userId = reviewedId;
            const targetUser = generateRandomPerformanceReviewTargetUser(reviewCycle.id, reviewedId);

            expect(performanceReviewsService.scoreNeedsObfuscationFromTarget(reviewCycle, targetUser, userId)).toBe(false);
        });
    });

    describe('sendShareReviewNotifications', () => {
        let sharedUserIds: number[];
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            sharedUserIds = fakePositiveIntegerArray();
            reviewCycle = generateRandomPerformanceReviewCycle();
        });

        it('successfully send share review notifications', async () => {
            await performanceReviewsService.sendShareReviewNotifications(sharedUserIds, reviewCycle);
            expect(performanceReviewNotificationsPort.sendShareReviewNotification).toHaveBeenCalledExactlyOnceWith(sharedUserIds, reviewCycle);
        });
    });

    describe('sendStartCycleNotifications', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewForms: PerformanceReviewFormReview[];
        let externalReviewerIds: number[];
        let externalReviewers: PerformanceReviewExternalReviewer[];

        beforeEach(() => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                formTemplateTypes: [PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW, PerformanceReviewEvaluationTypes.PEER_EXTERNAL_REVIEW],
            });
            externalReviewerIds = fakePositiveIntegerArray();
            reviewForms = externalReviewerIds.map((externalReviewerId) => {
                return generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), reviewCycle.id, { externalReviewerId });
            });
            externalReviewers = externalReviewerIds.map((externalReviewerId) => {
                return generateRandomExternalReviewer({ id: externalReviewerId });
            });
        });

        it('successfully send start cycle notifications', async () => {
            reviewCycle.reviewForms = reviewForms;
            repositoryMock.getExternalReviewers.mockResolvedValueOnce(externalReviewers);
            await performanceReviewsService.sendStartCycleNotifications(reviewCycle);

            expect(performanceReviewNotificationsPort.sendCycleStatusChangeNotifications).toHaveBeenCalledExactlyOnceWith(reviewCycle);
            expect(performanceReviewNotificationsPort.sendStartCycleNotificationsForExternalReviewers).toHaveBeenCalledExactlyOnceWith(
                reviewCycle,
                externalReviewers,
            );
        });
    });

    describe('sendFinishCycleNotifications', () => {
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            reviewCycle = generateRandomPerformanceReviewCycle();
        });

        it('successfully send finish cycle notifications', async () => {
            await performanceReviewsService.sendFinishCycleNotifications(reviewCycle);
            expect(performanceReviewNotificationsPort.sendCycleStatusChangeNotifications).toHaveBeenCalledExactlyOnceWith(reviewCycle);
        });
    });

    describe('sendPendingReviewNotifications', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewerIds: number[];
        let externalReviewerIds: number[];

        beforeEach(() => {
            reviewCycle = generateRandomPerformanceReviewCycle();
            reviewerIds = faker.helpers.multiple(faker.number.int);
            externalReviewerIds = faker.helpers.multiple(faker.number.int);
        });

        it('successfully send pending review notifications', async () => {
            await performanceReviewsService.sendPendingReviewNotifications(reviewCycle, reviewerIds, []);
            expect(performanceReviewNotificationsPort.sendPendingReviewNotifications).toHaveBeenCalledExactlyOnceWith(reviewCycle, reviewerIds);
        });

        it('successfully send pending review notifications for external reviewers', async () => {
            await performanceReviewsService.sendPendingReviewNotifications(reviewCycle, [], externalReviewerIds);
            expect(performanceReviewNotificationsPort.sendPendingReviewNotificationsForExternalReviewers).toHaveBeenCalledExactlyOnceWith(
                reviewCycle,
                externalReviewerIds,
            );
        });

        it('do not send pending review notifications if there are no reviewers', async () => {
            await performanceReviewsService.sendPendingReviewNotifications(reviewCycle, [], []);
            expect(performanceReviewNotificationsPort.sendPendingReviewNotifications).not.toHaveBeenCalled();
            expect(performanceReviewNotificationsPort.sendPendingReviewNotificationsForExternalReviewers).not.toHaveBeenCalled();
        });
    });

    describe('sendPerformanceReviewCycleConfirmNotification', () => {
        let reviewed: User;
        let targetUser: PerformanceReviewTargetUser;
        let instanceId: number;
        let reviewCycle: PerformanceReviewCycle;

        beforeEach(() => {
            reviewCycle = generateRandomPerformanceReviewCycle();
            reviewed = generateRandomUser();
            targetUser = generateRandomPerformanceReviewTargetUser(reviewCycle.id);
            instanceId = fakePositiveInteger();
        });

        it('successfully send performance review cycle confirm notification', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            await performanceReviewsService.sendPerformanceReviewCycleConfirmNotification(reviewed, targetUser, instanceId);
            expect(performanceReviewNotificationsPort.sendPerformanceReviewCycleConfirmNotification).toHaveBeenCalledExactlyOnceWith(
                reviewCycle,
                reviewed,
                targetUser,
            );
        });
    });

    describe('updateFormTemplatesScoreConfig', () => {
        let reviewCycle: PerformanceReviewCycle;
        let reviewTemplate: PerformanceReviewFormTemplate;
        let goalsWeight: number;
        let formWeight: number;
        let competenciesWeight: number;

        beforeEach(() => {
            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.PENDING]),
                usersMode: PerformanceReviewCycleReviewedUsersMode.ALL_USERS,
            });
            reviewTemplate = generateRandomPerformanceReviewFormTemplate(reviewCycle.id);
            reviewCycle.formTemplates = [reviewTemplate];
            formWeight = fakePositiveInteger({ max: 50 }); // 0–50
            competenciesWeight = formWeight;
            goalsWeight = 100 - formWeight * 2;
        });

        it('successfully update form templates score config', async () => {
            const formQuestionReferences = [fakePositiveInteger(), fakePositiveInteger(), fakePositiveInteger()];
            const formQuestionWeights = generateQuestionWeightsFromIds(formQuestionReferences);
            
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: questionWeightsObjectToArray(formQuestionWeights) ,
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails()
            };

            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.AUTO);
            template.goalsQuestionTemplateId = null;
            template.competenciesQuestionTemplateId = fakePositiveInteger();
            const competenciesQuestionTemplate = generateQuestionTemplateWithValues(template.competenciesQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: 5 }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
            });
            template.competenciesQuestionTemplate = competenciesQuestionTemplate;
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const choiceCount = fakePositiveInteger({ max: 10, min: 1 });
            const questionTemplates =
                formQuestionReferences.map((referenceId) =>
                    generateQuestionTemplateWithValues(referenceId, reviewCycle.instanceId, {
                        possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));

            const updatedFormTemplates = await performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig);
            expect(updatedFormTemplates).toStrictEqual([reviewTemplate]);
        });

        it('successfully update form templates with weighted score, scale, and direction weight 0', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 0,
                        weightDetails: {
                            competenciesWeight,
                            goalsWeight,
                            formWeight,
                            formQuestionWeights: [{ referenceId: fakePositiveInteger(), weight: 100 }],
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails()
            };

            const template = generateRandomTemplate(
                reviewTemplate.templateId!,
                reviewCycle.instanceId,
                PerformanceReviewTemplateGoalsProgressMode.MANUAL,
                true
            );
            const choiceCount = fakePositiveInteger({ max: 10, min: 1 });
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
            });
            const competenciesQuestionTemplate = generateQuestionTemplateWithValues(template.competenciesQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
            });
            template.goalsQuestionTemplate = goalsQuestionTemplate;
            template.competenciesQuestionTemplate = competenciesQuestionTemplate;
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValueOnce(competenciesQuestionTemplate);
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValueOnce(goalsQuestionTemplate);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            const questionTemplates =
                scoreConfig.directions[0].weightDetails?.formQuestionWeights.map((questionWeight) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));

            const updatedFormTemplates = await performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig);
            expect(updatedFormTemplates).toStrictEqual([reviewTemplate]);
        });

        it('update score config successfully with score, with objectives', async () => {
            const formQuestionReferences = [fakePositiveInteger(), fakePositiveInteger(), fakePositiveInteger()];
            const formQuestionWeights = generateQuestionWeightsFromIds(formQuestionReferences);
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: questionWeightsObjectToArray(formQuestionWeights),
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            const template = generateRandomTemplate(
                reviewTemplate.templateId!,
                reviewCycle.instanceId,
                PerformanceReviewTemplateGoalsProgressMode.MANUAL,
                true
            );
            const choiceCount = fakePositiveInteger({ max: 10, min: 1 });
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
            });
            const competenciesQuestionTemplate = generateQuestionTemplateWithValues(template.competenciesQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
            });
            template.goalsQuestionTemplate = goalsQuestionTemplate;
            template.competenciesQuestionTemplate = competenciesQuestionTemplate;
            formTemplatesPortMock.findQuestionTemplateById.mockResolvedValueOnce(goalsQuestionTemplate);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            
            const questionTemplates =
                formQuestionReferences.map((referenceId) =>
                    generateQuestionTemplateWithValues(referenceId, reviewCycle.instanceId, {
                        possibleAnswers: faker.helpers.multiple(faker.word.words, { count: choiceCount }),
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));

            const result = await performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig);

            expect(result[0].template).not.toBeNull();
            expect(result[0].scoreConfig!.directionWeight).toBe(scoreConfig.directions[0].weight);
            expect(result[0].scoreConfig!.formWeight).toBe(formWeight);
            expect(result[0].scoreConfig!.goalsWeight).toBe(goalsWeight);
            expect(result[0].scoreConfig!.competenciesWeight).toBe(competenciesWeight);
            expect(result[0].scoreConfig!.scaleStart).toBe(scoreConfig.scoreDetails!.scaleDetails!.start);
            expect(result[0].scoreConfig!.scaleEnd).toBe(scoreConfig.scoreDetails!.scaleDetails!.end);
            expect(result[0].scoreConfig!.withHiddenScoreForReviewed).toBe(scoreConfig.scoreDetails!.withHiddenScoreForReviewed);
            expect(result[0].scoreConfig!.withGoalScore).toBe(true);
            expect(result[0].scoreConfig!.withCompetenciesScore).toBe(true);
            expect(result[0].scoreConfig!.withScore).toBe(true);
            expect(result[0].formReviewConfig).toEqual(reviewTemplate.formReviewConfig);
            expect(result[0].scoreConfig).toEqual(reviewTemplate.scoreConfig);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.findFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(repositoryMock.saveFormTemplates, 1);
        });

        it('update form template successfully with score and valid question weights and default score config', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight: 100,
                            goalsWeight: 0,
                            competenciesWeight: 0,
                        },
                    },
                ],
                scoreDetails: generateDefaultScoreDetails(),
            };
            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId);
            template.goalsQuestionTemplateId = null;
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const questionTemplates =
                scoreConfig.directions[0].weightDetails?.formQuestionWeights.map((questionWeight, index) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        possibleAnswers: faker.helpers.multiple(faker.word.words, { count: index + 1 }),
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));

            const result = await performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig);

            expect(result[0].scoreConfig?.withScore).toBe(true);
            expect(result[0].scoreConfig?.withHiddenScoreForReviewed).toBe(scoreConfig.scoreDetails?.withHiddenScoreForReviewed ?? false);
            expect(result[0].scoreConfig?.withGoalScore).toBe(false);
            expect(result[0].scoreConfig?.withCompetenciesScore).toBe(false);
            expect(result[0].scoreConfig?.formTemplateQuestionWeights).toStrictEqual(
                questionWeightsArrayToObject(scoreConfig.directions[0].weightDetails!.formQuestionWeights),
            );
            expect(result[0].scoreConfig?.formWeight).toBe(100);
            expect(result[0].scoreConfig?.goalsWeight).toBe(0);
            expect(result[0].scoreConfig?.competenciesWeight).toBe(0);
            expect(result[0].scoreConfig?.scaleStart).toBe(0);
            expect(result[0].scoreConfig?.scaleEnd).toBe(10);
            expect(result[0].formReviewConfig).toEqual(reviewTemplate.formReviewConfig);
            expect(result[0].scoreConfig).toEqual(reviewTemplate.scoreConfig);
            expect(result[0].template).not.toBeNull();
            expect(result[0].template?.id).toBe(reviewTemplate.templateId);
            expect(result[0].scoreConfig?.directionWeight).toBe(scoreConfig.directions[0].weight);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplates, 1);
        });

        it('throw bad request error if has goals weight but no goal question rating template', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight: 50,
                            goalsWeight: 50,
                            competenciesWeight: 0,
                        },
                    },
                ],
                scoreDetails: generateDefaultScoreDetails(),
            };
            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            const goalsQuestionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: 5 }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.CHECKBOX,
            });
            template.goalsQuestionTemplate = goalsQuestionTemplate;
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const questionTemplates =
                scoreConfig.directions[0].weightDetails?.formQuestionWeights.map((questionWeight, index) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        possibleAnswers: faker.helpers.multiple(faker.word.words, { count: index + 1 }),
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throw bad request error if has competencies weight but no competency question rating template', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight: 50,
                            goalsWeight: 0,
                            competenciesWeight: 50,
                        },
                    },
                ],
                scoreDetails: generateDefaultScoreDetails(),
            };
            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId, undefined,true);
            const competenciesQuestionTemplate = generateQuestionTemplateWithValues(template.goalsQuestionTemplateId!, template.instanceId, {
                possibleAnswers: faker.helpers.multiple(faker.word.words, { count: 5 }),
                type: QUESTION_TEMPLATES_QUESTION_TYPES.CHECKBOX,
            });
            template.competenciesQuestionTemplate = competenciesQuestionTemplate;
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const questionTemplates =
                scoreConfig.directions[0].weightDetails?.formQuestionWeights.map((questionWeight, index) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        possibleAnswers: faker.helpers.multiple(faker.word.words, { count: index + 1 }),
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);
            repositoryMock.saveFormTemplate.mockImplementationOnce((reviewTemplate) => Promise.resolve(reviewTemplate));

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);
            
            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throw bad request error if with score and invalid question weights', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 100 },
                                { referenceId: fakePositiveInteger(), weight: 100 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const questionTemplates =
                scoreConfig.directions[0].weightDetails?.formQuestionWeights.map((questionWeight) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throw bad request error if there is a weight for goals but there are no goals', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId);
            template.goalsProgressMode = null;
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const questionTemplates =
                scoreConfig.directions[0].weightDetails?.formQuestionWeights.map((questionWeight) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throw bad request error if sum of weights exceed 100', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight: 60,
                            goalsWeight: 60,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            const template = generateRandomTemplate(reviewTemplate.templateId!, reviewCycle.instanceId, PerformanceReviewTemplateGoalsProgressMode.MANUAL, true);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            formTemplatesPortMock.findFormTemplate.mockResolvedValueOnce(generateRandomFormTemplate(template.formTemplateId!));
            const questionTemplates =
                scoreConfig.directions[0].weightDetails!.formQuestionWeights.map((questionWeight) =>
                    generateQuestionTemplateWithValues(questionWeight.referenceId, reviewCycle.instanceId, {
                        type: QUESTION_TEMPLATES_QUESTION_TYPES.RATING,
                    }),
                ) ?? [];
            formTemplatesPortMock.getRatingQuestionsOfFormTemplate.mockResolvedValueOnce(questionTemplates);

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 1);
            checkMockCallsCount(formTemplatesPortMock.findQuestionTemplateById, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throw bad request error if form template does not exist', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([]);

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);

            checkMockCallsCount(repositoryMock.getTemplatesByIds, 1);
            checkMockCallsCount(formTemplatesPortMock.getRatingQuestionsOfFormTemplate, 0);
            checkMockCallsCount(repositoryMock.saveFormTemplate, 0);
        });

        it('throw bad request error if cycle has already started', async () => {
            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                    scoreDetails: generateRandomScoreDetails(),
            };

            reviewCycle = generatePerformanceReviewCycleWithValues(fakePositiveInteger(), faker.date.recent(), {
                status: faker.helpers.arrayElement([PerformanceReviewCycleStatus.FINISHED, PerformanceReviewCycleStatus.IN_PROGRESS]),
            });

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);
        });

        it('throw bad request error if direction weight is not 100 or 0', async () => {
            const reviewTemplate1 = generateRandomPerformanceReviewFormTemplate(reviewCycle.id);
            const reviewTemplate2 = generatePerformanceReviewFormTemplateWithValues(reviewCycle.id, {
                evaluationType: faker.helpers.arrayElement(
                    Object.values(PerformanceReviewEvaluationTypes).filter((type) => type !== reviewTemplate1.evaluationType),
                ),
                templateId: reviewTemplate1.templateId,
            });
            reviewCycle.formTemplates = [reviewTemplate1, reviewTemplate2];

            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate1.evaluationType,
                        weight: 25,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                    {
                        evaluationType: reviewTemplate2.evaluationType,
                        weight: 50,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(
                new BadRequestError('Total direction weight must be 0 or approximately 100', ErrorCodes.REVIEW_CYCLE_INVALID_TOTAL_WEIGHT),
            );
        });

        it('throw error if some directions have no template', async () => {
            const reviewTemplate = generateRandomPerformanceReviewFormTemplate(reviewCycle.id);

            reviewCycle.formTemplates = [reviewTemplate];

            const scoreConfig = {
                directions: [
                    {
                        evaluationType: reviewTemplate.evaluationType,
                        weight: 100,
                        weightDetails: {
                            formQuestionWeights: [
                                { referenceId: fakePositiveInteger(), weight: 40 },
                                { referenceId: fakePositiveInteger(), weight: 60 },
                            ],
                            formWeight,
                            goalsWeight,
                            competenciesWeight,
                        },
                    },
                ],
                scoreDetails: generateRandomScoreDetails(),
            };

            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([]);

            await expect(performanceReviewsService.updateFormTemplatesScoreConfig(reviewCycle, scoreConfig)).rejects.toThrow(BadRequestError);
        });
    });

    describe('recalculateScoresForTargetUser', () => {
        const fakeCycleId = fakePositiveInteger();
        const fakeInstanceId = fakePositiveInteger();
        const fakeUserId = fakePositiveInteger();
        const evaluationType = PerformanceReviewEvaluationTypes.BOSS_REVIEW;

        let reviewCycle: PerformanceReviewCycle;
        let formTemplate: PerformanceReviewFormTemplate;
        let template: PerformanceReviewTemplate;

        beforeEach(() => {
            formTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType,
                withScore: true,
                formWeight: 100,
                goalsWeight: 0,
                competenciesWeight: 0,
                scaleStart: 0,
                scaleEnd: 10,
            });
            template = generateRandomTemplate(formTemplate.templateId!);
            reviewCycle = generatePerformanceReviewCycleWithValues(fakeCycleId, faker.date.recent(), {
                instanceId: fakeInstanceId,
                formTemplateTypes: [evaluationType],
                status: PerformanceReviewCycleStatus.IN_PROGRESS,
            });
            reviewCycle.formTemplates = [formTemplate];
        });

        it('should throw BadRequestError when cycle is not IN_PROGRESS', async () => {
            reviewCycle.status = faker.helpers.arrayElement([PerformanceReviewCycleStatus.PENDING, PerformanceReviewCycleStatus.DRAFT, PerformanceReviewCycleStatus.SELECTION]);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);

            await expect(
                performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId),
            ).rejects.toThrow(BadRequestError);

            checkMockCallsCount(repositoryMock.getFormReviewsOfCycleForReviewedId, 0);
        });

        it('should skip non-finished form reviews', async () => {
            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.PENDING,
                type: evaluationType,
                reviewedId: fakeUserId,
            });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(repositoryMock.saveFormReview, 0);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 0);
        });

        it('should skip form reviews without scoreConfig.withScore', async () => {
            const noScoreFormTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType,
                withScore: false,
            });
            reviewCycle.formTemplates = [noScoreFormTemplate];

            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(repositoryMock.saveFormReview, 0);
        });

        it('should recalculate form score and save when score changes', async () => {
            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });
            formReview.formScore = 50;
            formReview.score = 50;

            const newFormScore = 80;
            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(newFormScore);

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            repositoryMock.saveFormReview.mockImplementationOnce((fr) => Promise.resolve(fr));
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            expect(formReview.formScore).toBe(newFormScore);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('should not save when scores remain unchanged', async () => {
            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });
            formReview.formScore = 50;
            formReview.goalsScore = null;
            formReview.competenciesScore = null;

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(50);

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            const expectedScore = formReview.score;
            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            expect(formReview.formScore).toBe(50);
            if (formReview.score === expectedScore) {
                checkMockCallsCount(repositoryMock.saveFormReview, 0);
            }
        });

        it('should recalculate goals score when withGoalScore is enabled', async () => {
            const goalFormTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType,
                withScore: true,
                withGoalScore: true,
                formWeight: 50,
                goalsWeight: 50,
                competenciesWeight: 0,
                scaleStart: 0,
                scaleEnd: 10,
            });
            const goalTemplate = generateRandomTemplate(goalFormTemplate.templateId!, undefined, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            reviewCycle.formTemplates = [goalFormTemplate];

            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });
            formReview.formScore = 50;
            formReview.goalsScore = 40;

            // Non-empty array so findSavedGoalQuestionsWithAnswer returns data and recalculateGoalsScore runs
            const goalQuestionsWithAnswer = [generateGoalQuestionWithAnswer()];

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(50);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce(goalQuestionsWithAnswer);
            coreFormsPortMock.calculateGoalScore.mockReturnValueOnce({ score: 60, partialGoalScoreMap: {} });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([goalTemplate]);
            repositoryMock.saveFormReview.mockImplementationOnce((fr) => Promise.resolve(fr));
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            // Save is called when any component score changes (goals 40 → 60), even if total score could stay same
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
            expect(formReview.goalsScore).toBe(60);
        });

        it('should recalculate competencies score when withCompetenciesScore is enabled', async () => {
            const compFormTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType,
                withScore: true,
                withCompetenciesScore: true,
                formWeight: 50,
                goalsWeight: 0,
                competenciesWeight: 50,
                scaleStart: 0,
                scaleEnd: 10,
            });
            reviewCycle.formTemplates = [compFormTemplate];

            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });
            formReview.formScore = 50;
            formReview.competenciesScore = 30;

            // Non-empty array so findSavedCompetencyQuestionsWithAnswer returns data and recalculateCompetenciesScore runs
            const competencyQuestionsWithAnswer = [generateCompetencyQuestionWithAnswer()];

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(50);
            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValueOnce(competencyQuestionsWithAnswer);
            coreFormsPortMock.calculateCompetencyScore.mockReturnValueOnce({ score: 70, partialCompetencyScoreMap: {} });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            repositoryMock.saveFormReview.mockImplementationOnce((fr) => Promise.resolve(fr));
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 1);
            // Save is called when any component score changes (competencies 30 → 70), even if total score could stay same
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
            expect(formReview.competenciesScore).toBe(70);
        });

        it('should set goalsScore to null when no saved goal questions and prior goalsScore was 0', async () => {
            const goalFormTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType,
                withScore: true,
                withGoalScore: true,
                formWeight: 50,
                goalsWeight: 50,
                competenciesWeight: 0,
                scaleStart: 0,
                scaleEnd: 10,
            });
            const goalTemplate = generateRandomTemplate(goalFormTemplate.templateId!, undefined, PerformanceReviewTemplateGoalsProgressMode.MANUAL);
            reviewCycle.formTemplates = [goalFormTemplate];

            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });
            formReview.formScore = 50;
            formReview.goalsScore = 0;

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(50);
            repositoryMock.getGoalQuestionsWithAnswer.mockResolvedValueOnce([]);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([goalTemplate]);
            repositoryMock.saveFormReview.mockImplementationOnce((fr) => Promise.resolve(fr));
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            expect(formReview.goalsScore).toBeNull();
            checkMockCallsCount(coreFormsPortMock.calculateGoalScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('should set competenciesScore to null when no saved competency questions and prior competenciesScore was 0', async () => {
            const compFormTemplate = generatePerformanceReviewFormTemplateWithValues(fakeCycleId, {
                evaluationType,
                withScore: true,
                withCompetenciesScore: true,
                formWeight: 50,
                goalsWeight: 0,
                competenciesWeight: 50,
                scaleStart: 0,
                scaleEnd: 10,
            });
            reviewCycle.formTemplates = [compFormTemplate];

            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });
            formReview.formScore = 50;
            formReview.competenciesScore = 0;

            coreFormsPortMock.calculateAnswerScore.mockReturnValueOnce(50);
            repositoryMock.getCompetencyQuestionsWithAnswer.mockResolvedValueOnce([]);
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            repositoryMock.saveFormReview.mockImplementationOnce((fr) => Promise.resolve(fr));
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            expect(formReview.competenciesScore).toBeNull();
            checkMockCallsCount(coreFormsPortMock.calculateCompetencyScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 1);
        });

        it('should handle empty form reviews list', async () => {
            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(repositoryMock.saveFormReview, 0);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 0);
        });

        it('should skip form reviews without matching form template', async () => {
            const formReview = generatePerformanceReviewFormReviewWithValues(fakePositiveInteger(), fakeCycleId, {
                status: PerformanceReviewFormStatus.FINISHED,
                type: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
                reviewedId: fakeUserId,
                coreFormAnswerId: fakePositiveInteger(),
            });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);
            coreFormsPortMock.getFormAnswers.mockResolvedValueOnce([
                generateFormAnswerWithValues(formReview.coreFormAnswerId!, formReview.coreFormTag!, formReview.coreFormId!, []),
            ]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(repositoryMock.saveFormReview, 0);
            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 0);
        });

        it('should skip core form score calculation when form has no coreFormId or coreFormTag', async () => {
            const formReview = new PerformanceReviewFormReview({
                coreFormAnswerId: null,
                coreFormId: null,
                coreFormTag: null,
                createdAt: faker.date.recent(),
                cycleId: fakeCycleId,
                duplicationId: faker.string.uuid(),
                externalReviewerId: null,
                formScore: null,
                goalsScore: null,
                competenciesScore: null,
                id: fakePositiveInteger(),
                responsibleId: null,
                reviewedId: fakeUserId,
                reviewerId: fakePositiveInteger(),
                score: null,
                shareStatus: PerformanceReviewFormShareStatus.NOT_SHARED,
                status: PerformanceReviewFormStatus.FINISHED,
                type: evaluationType,
                updatedAt: faker.date.recent(),
            });

            repositoryMock.getCycleById.mockResolvedValueOnce(reviewCycle);
            repositoryMock.getFormReviewsOfCycleForReviewedId.mockResolvedValueOnce([formReview]);
            repositoryMock.getTemplatesByIds.mockResolvedValueOnce([template]);

            await performanceReviewsService.recalculateScoresForTargetUser(fakeCycleId, fakeInstanceId, fakeUserId);

            checkMockCallsCount(coreFormsPortMock.calculateAnswerScore, 0);
            checkMockCallsCount(repositoryMock.saveFormReview, 0);
        });
    });

    describe('recalculateAndPersistTargetUserFinalScore', () => {
        const buildCycleWithSingleFullWeightDirection = (cycleId: number, evaluationType: PerformanceReviewEvaluationTypesValues) => {
            const formTemplate = generatePerformanceReviewFormTemplateWithValues(cycleId, { evaluationType });
            formTemplate.scoreConfig!.directionWeight = 100;
            const cycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {});
            cycle.formTemplates = [formTemplate];
            return cycle;
        };

        it('computes the score from current FINISHED form-reviews and persists it', async () => {
            const cycleId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId, evaluationType);
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [userId]: { [evaluationType]: { score: 80 } } as never,
            });

            const result = await performanceReviewsService.recalculateAndPersistTargetUserFinalScore(cycle, userId);

            expect(result).toBe(80);
            expect(repositoryMock.updateTargetUserScore).toHaveBeenCalledWith(cycleId, userId, 80);
        });

        it('persists null when the user has no FINISHED form-reviews', async () => {
            const cycleId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId, PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({});

            const result = await performanceReviewsService.recalculateAndPersistTargetUserFinalScore(cycle, userId);

            expect(result).toBeNull();
            expect(repositoryMock.updateTargetUserScore).toHaveBeenCalledWith(cycleId, userId, null);
        });

        it('queries scores in REVIEWED participant scope', async () => {
            const cycleId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId, PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({});

            await performanceReviewsService.recalculateAndPersistTargetUserFinalScore(cycle, userId);

            expect(repositoryMock.getUsersScoresByEvaluationType).toHaveBeenCalledWith(
                cycleId,
                [userId],
                PerformanceReviewCycleParticipantType.REVIEWED,
            );
        });

        // Regression: under READ COMMITTED, two concurrent transactions doing
        // SELECT-then-UPDATE on the same target user would each see only their own pending
        // form-review write, and the last writer would overwrite with a stale value computed
        // against an incomplete review set. Acquiring a row-level FOR UPDATE lock on the
        // target user row before the SELECT serialises concurrent recomputes for the same
        // (cycleId, userId) pair, so the second one waits and reads the full review set
        // committed by the first.
        it('locks the target user row before the SELECT, so concurrent recomputes serialise', async () => {
            const cycleId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId, PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW);
            const callOrder: string[] = [];
            repositoryMock.lockTargetUserRow.mockImplementationOnce(async () => {
                callOrder.push('lock');
            });
            repositoryMock.getUsersScoresByEvaluationType.mockImplementationOnce(async () => {
                callOrder.push('select');
                return {};
            });
            repositoryMock.updateTargetUserScore.mockImplementationOnce(async () => {
                callOrder.push('update');
            });

            await performanceReviewsService.recalculateAndPersistTargetUserFinalScore(cycle, userId);

            expect(repositoryMock.lockTargetUserRow).toHaveBeenCalledWith(cycleId, userId);
            expect(callOrder).toEqual(['lock', 'select', 'update']);
        });

        it('rounds non-integer per-direction averages to an INTEGER score before persisting (matches `formReview.score` convention)', async () => {
            // ROUND(AVG(...), 2) from getUsersScoresByEvaluationType can return a 2-decimal float
            // in the [10, 100] scale (e.g. 78.83). The persisted column is INTEGER, so the service
            // must round before passing the value to updateTargetUserScore - same convention as
            // `calculateScore` for form reviews.
            const cycleId = fakePositiveInteger();
            const userId = fakePositiveInteger();
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId, evaluationType);
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [userId]: { [evaluationType]: { score: 78.83 } } as never,
            });

            const result = await performanceReviewsService.recalculateAndPersistTargetUserFinalScore(cycle, userId);

            expect(result).toBe(79);
            expect(Number.isInteger(result)).toBe(true);
            expect(repositoryMock.updateTargetUserScore).toHaveBeenCalledWith(cycleId, userId, 79);
        });
    });

    describe('getPersistedScoresWithFallback', () => {
        const callPrivate = (cycle: PerformanceReviewCycle, userIds: number[]) =>
            (performanceReviewsService as unknown as {
                getPersistedScoresWithFallback: (cycle: PerformanceReviewCycle, userIds: number[]) => Promise<{ [userId: number]: number | null }>;
            }).getPersistedScoresWithFallback(cycle, userIds);

        const buildCycleWithSingleFullWeightDirection = (cycleId: number) => {
            const formTemplate = generatePerformanceReviewFormTemplateWithValues(cycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            formTemplate.scoreConfig!.directionWeight = 100;
            const cycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {});
            cycle.formTemplates = [formTemplate];
            return cycle;
        };

        it('returns an empty object for an empty userIds array', async () => {
            const cycle = buildCycleWithSingleFullWeightDirection(fakePositiveInteger());

            const result = await callPrivate(cycle, []);

            expect(result).toEqual({});
            expect(repositoryMock.getTargetUsersScoresMap).not.toHaveBeenCalled();
        });

        it('returns the persisted map without recomputing when every userId has a non-null score', async () => {
            const cycle = buildCycleWithSingleFullWeightDirection(fakePositiveInteger());
            const u1 = fakePositiveInteger();
            const u2 = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [u1]: 75, [u2]: 80 });

            const result = await callPrivate(cycle, [u1, u2]);

            expect(result).toEqual({ [u1]: 75, [u2]: 80 });
            expect(repositoryMock.getUsersScoresByEvaluationType).not.toHaveBeenCalled();
            expect(repositoryMock.bulkUpdateTargetUserScores).not.toHaveBeenCalled();
        });

        it('self-heals userIds that are missing from the persisted map by recomputing and persisting them', async () => {
            const cycleId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId);
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const present = fakePositiveInteger();
            const missing = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [present]: 60 });
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [missing]: { [evaluationType]: { score: 90 } } as never,
            });

            const result = await callPrivate(cycle, [present, missing]);

            expect(result).toEqual({ [present]: 60, [missing]: 90 });
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledTimes(1);
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledWith(cycleId, new Map([[missing, 90]]));
        });

        // Regression: post-migration every PerformanceReviewTargetUsers row exists with score = NULL.
        // The fallback must treat `null` entries as "needs recompute"; otherwise reads return `null`
        // forever until the devops backfill endpoint is run manually.
        it('self-heals userIds whose persisted score is NULL (post-migration state)', async () => {
            const cycleId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId);
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const u1 = fakePositiveInteger();
            const u2 = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [u1]: null, [u2]: null });
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [u1]: { [evaluationType]: { score: 70 } } as never,
                [u2]: { [evaluationType]: { score: 85 } } as never,
            });

            const result = await callPrivate(cycle, [u1, u2]);

            expect(result).toEqual({ [u1]: 70, [u2]: 85 });
            expect(repositoryMock.getUsersScoresByEvaluationType).toHaveBeenCalledTimes(1);
            expect(repositoryMock.getUsersScoresByEvaluationType).toHaveBeenCalledWith(
                cycleId,
                [u1, u2],
                PerformanceReviewCycleParticipantType.REVIEWED,
            );
            // Both NULL rows recompute to non-null: a single bulk UPDATE writes both at once.
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledTimes(1);
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledWith(
                cycleId,
                new Map([
                    [u1, 70],
                    [u2, 85],
                ]),
            );
        });

        // The fallback short-circuits the bulk UPDATE entirely when every recompute yields null
        // (no FINISHED reviews yet). The rows are already null, so writing null again is a pure
        // no-op; skipping the call avoids issuing the round-trip per page render during the
        // cycle-startup window where most target users have no finished reviews.
        it('does NOT issue an UPDATE when both persisted score and recomputed score are null', async () => {
            const cycleId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId);
            const u1 = fakePositiveInteger();
            const u2 = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [u1]: null, [u2]: null });
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({});

            const result = await callPrivate(cycle, [u1, u2]);

            expect(result).toEqual({ [u1]: null, [u2]: null });
            expect(repositoryMock.bulkUpdateTargetUserScores).not.toHaveBeenCalled();
        });

        it('rounds non-integer per-direction averages to an INTEGER score when self-healing', async () => {
            // Same INTEGER-in-S2 convention applies on the fallback path: even if
            // getUsersScoresByEvaluationType returns a 2-decimal float (typical
            // ROUND(AVG(...), 2) output), what gets persisted and returned must be an integer
            // so it fits the column and stays consistent with `formReview.score`.
            const cycleId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId);
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const missing = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({});
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [missing]: { [evaluationType]: { score: 78.83 } } as never,
            });

            const result = await callPrivate(cycle, [missing]);

            expect(result).toEqual({ [missing]: 79 });
            expect(Number.isInteger(result[missing])).toBe(true);
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledWith(cycleId, new Map([[missing, 79]]));
        });
    });

    describe('getPersistedScoreForTargetUser', () => {
        const buildCycleWithSingleFullWeightDirection = (cycleId: number) => {
            const formTemplate = generatePerformanceReviewFormTemplateWithValues(cycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            formTemplate.scoreConfig!.directionWeight = 100;
            const cycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), {});
            cycle.formTemplates = [formTemplate];
            return cycle;
        };

        it('returns the persisted score for the user when present', async () => {
            const cycle = buildCycleWithSingleFullWeightDirection(fakePositiveInteger());
            const userId = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [userId]: 75 });

            const result = await performanceReviewsService.getPersistedScoreForTargetUser(cycle, userId);

            expect(result).toBe(75);
            expect(repositoryMock.getUsersScoresByEvaluationType).not.toHaveBeenCalled();
            expect(repositoryMock.bulkUpdateTargetUserScores).not.toHaveBeenCalled();
        });

        it('falls back to recompute-and-persist when the persisted score is NULL', async () => {
            const cycleId = fakePositiveInteger();
            const cycle = buildCycleWithSingleFullWeightDirection(cycleId);
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const userId = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [userId]: null });
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [userId]: { [evaluationType]: { score: 88 } } as never,
            });

            const result = await performanceReviewsService.getPersistedScoreForTargetUser(cycle, userId);

            expect(result).toBe(88);
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledWith(cycleId, new Map([[userId, 88]]));
        });

        it('returns null when the user has no persisted score and no FINISHED reviews to recompute from', async () => {
            const cycle = buildCycleWithSingleFullWeightDirection(fakePositiveInteger());
            const userId = fakePositiveInteger();
            repositoryMock.getTargetUsersScoresMap.mockResolvedValueOnce({ [userId]: null });
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({});

            const result = await performanceReviewsService.getPersistedScoreForTargetUser(cycle, userId);

            expect(result).toBeNull();
            expect(repositoryMock.bulkUpdateTargetUserScores).not.toHaveBeenCalled();
        });
    });

    describe('recalculateAndPersistAllTargetUsersFinalScoresForCycle', () => {
        const buildCycle = (cycleId: number, instanceId: number, status: PerformanceReviewCycleStatusValues) => {
            const formTemplate = generatePerformanceReviewFormTemplateWithValues(cycleId, {
                evaluationType: PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW,
            });
            formTemplate.scoreConfig!.directionWeight = 100;
            const cycle = generatePerformanceReviewCycleWithValues(cycleId, faker.date.recent(), { status });
            cycle.instanceId = instanceId;
            cycle.formTemplates = [formTemplate];
            return cycle;
        };

        it('issues a single batched SELECT and a single bulk UPDATE call for every target user in the cycle', async () => {
            const cycleId = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            const cycle = buildCycle(cycleId, instanceId, PerformanceReviewCycleStatus.IN_PROGRESS);
            const evaluationType = PerformanceReviewEvaluationTypes.SUBORDINATE_REVIEW;
            const u1 = fakePositiveInteger();
            const u2 = fakePositiveInteger();
            const u3 = fakePositiveInteger();
            const userIds = [u1, u2, u3];
            repositoryMock.getCycleById.mockResolvedValueOnce(cycle);
            repositoryMock.getTargetUserIds.mockResolvedValueOnce(new Set(userIds));
            // u1 and u2 have FINISHED reviews; u3 has none -> recompute is null.
            // Including the null entry in the bulk map is intentional: it overwrites stale
            // non-null scores back to null when every review has been removed.
            repositoryMock.getUsersScoresByEvaluationType.mockResolvedValueOnce({
                [u1]: { [evaluationType]: { score: 75 } } as never,
                [u2]: { [evaluationType]: { score: 90 } } as never,
            });

            await performanceReviewsService.recalculateAndPersistAllTargetUsersFinalScoresForCycle(cycleId, instanceId);

            expect(repositoryMock.getUsersScoresByEvaluationType).toHaveBeenCalledTimes(1);
            expect(repositoryMock.getUsersScoresByEvaluationType).toHaveBeenCalledWith(
                cycleId,
                userIds,
                PerformanceReviewCycleParticipantType.REVIEWED,
            );
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledTimes(1);
            expect(repositoryMock.bulkUpdateTargetUserScores).toHaveBeenCalledWith(
                cycleId,
                new Map<number, number | null>([
                    [u1, 75],
                    [u2, 90],
                    [u3, null],
                ]),
            );
            // The per-user method must NOT be reused (it would defeat the bulk objective).
            expect(repositoryMock.updateTargetUserScore).not.toHaveBeenCalled();
        });

        it('is a no-op when the cycle has no target users', async () => {
            const cycleId = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            const cycle = buildCycle(cycleId, instanceId, PerformanceReviewCycleStatus.FINISHED);
            repositoryMock.getCycleById.mockResolvedValueOnce(cycle);
            repositoryMock.getTargetUserIds.mockResolvedValueOnce(new Set());

            await performanceReviewsService.recalculateAndPersistAllTargetUsersFinalScoresForCycle(cycleId, instanceId);

            expect(repositoryMock.getUsersScoresByEvaluationType).not.toHaveBeenCalled();
            expect(repositoryMock.bulkUpdateTargetUserScores).not.toHaveBeenCalled();
        });

        it('throws BadRequestError when the cycle is not IN_PROGRESS or FINISHED', async () => {
            const cycleId = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            const cycle = buildCycle(cycleId, instanceId, PerformanceReviewCycleStatus.DRAFT);
            repositoryMock.getCycleById.mockResolvedValueOnce(cycle);

            await expect(
                performanceReviewsService.recalculateAndPersistAllTargetUsersFinalScoresForCycle(cycleId, instanceId),
            ).rejects.toThrow(BadRequestError);
            expect(repositoryMock.getTargetUserIds).not.toHaveBeenCalled();
        });
    });

    describe('listCompetencyQuestionsWithAnswerOfCycle', () => {
        it('delegates to the repository port with the given args', async () => {
            const cycleId = fakePositiveInteger();
            const instanceId = fakePositiveInteger();
            const pagination = { limit: 100, offset: 0 };
            const expected = { records: [], totalCount: 0 };

            repositoryMock.listCompetencyQuestionsWithAnswerOfCycle.mockResolvedValueOnce(expected);

            const result = await performanceReviewsService.listCompetencyQuestionsWithAnswerOfCycle(cycleId, instanceId, pagination);

            expect(result).toBe(expected);
            expect(repositoryMock.listCompetencyQuestionsWithAnswerOfCycle).toHaveBeenCalledTimes(1);
            expect(repositoryMock.listCompetencyQuestionsWithAnswerOfCycle).toHaveBeenCalledWith(cycleId, instanceId, pagination);
        });
    });
});
