457 lines
14 KiB
TypeScript
457 lines
14 KiB
TypeScript
/**
|
|
* Copyright 2025 LY Corporation
|
|
*
|
|
* LY Corporation licenses this file to you under the Apache License,
|
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at:
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
import { faker } from '@faker-js/faker';
|
|
import { Test } from '@nestjs/testing';
|
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
import type { Repository } from 'typeorm';
|
|
|
|
import { optionFixture } from '@/test-utils/fixtures';
|
|
import { TestConfig } from '@/test-utils/util-functions';
|
|
import { OptionServiceProviders } from '../../../../test-utils/providers/option.service.providers';
|
|
import {
|
|
CreateManyOptionsDto,
|
|
CreateOptionDto,
|
|
ReplaceManyOptionsDto,
|
|
} from './dtos';
|
|
import {
|
|
OptionKeyDuplicatedException,
|
|
OptionNameDuplicatedException,
|
|
} from './exceptions';
|
|
import { OptionEntity } from './option.entity';
|
|
import { OptionService } from './option.service';
|
|
|
|
describe('Option Test suite', () => {
|
|
let optionService: OptionService;
|
|
let optionRepo: Repository<OptionEntity>;
|
|
|
|
beforeEach(async () => {
|
|
const module = await Test.createTestingModule({
|
|
imports: [TestConfig],
|
|
providers: OptionServiceProviders,
|
|
}).compile();
|
|
|
|
optionService = module.get<OptionService>(OptionService);
|
|
optionRepo = module.get(getRepositoryToken(OptionEntity));
|
|
});
|
|
|
|
describe('create', () => {
|
|
it('creating an option succeeds with a new valid input', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new CreateOptionDto();
|
|
dto.fieldId = fieldId;
|
|
dto.key = faker.string.sample();
|
|
dto.name = faker.string.sample();
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue([
|
|
{
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
},
|
|
] as OptionEntity[]);
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
const option = await optionService.create(dto);
|
|
|
|
expect(option.key).toBe(dto.key);
|
|
expect(option.name).toBe(dto.name);
|
|
});
|
|
it('creating an option succeeds with an inactive input', async () => {
|
|
const fieldId = faker.number.int();
|
|
const optionId = faker.number.int();
|
|
const dto = new CreateOptionDto();
|
|
dto.fieldId = fieldId;
|
|
dto.key = faker.string.sample();
|
|
dto.name = faker.string.sample();
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue([
|
|
{
|
|
id: optionId,
|
|
key: 'deleted_' + dto.key,
|
|
name: dto.name,
|
|
},
|
|
] as OptionEntity[]);
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
const option = await optionService.create(dto);
|
|
|
|
expect(option.key).toBe(dto.key);
|
|
expect(option.name).toBe(dto.name);
|
|
});
|
|
it('creating an option fails with a duplicate name', async () => {
|
|
const fieldId = faker.number.int();
|
|
const duplicateName = optionFixture.name;
|
|
const dto = new CreateOptionDto();
|
|
dto.fieldId = fieldId;
|
|
dto.key = faker.string.sample();
|
|
dto.name = duplicateName;
|
|
|
|
await expect(optionService.create(dto)).rejects.toThrow(
|
|
OptionNameDuplicatedException,
|
|
);
|
|
});
|
|
it('creating an option fails with a duplicate key', async () => {
|
|
const fieldId = faker.number.int();
|
|
const duplicateKey = optionFixture.key;
|
|
const dto = new CreateOptionDto();
|
|
dto.fieldId = fieldId;
|
|
dto.key = duplicateKey;
|
|
dto.name = faker.string.sample();
|
|
|
|
await expect(optionService.create(dto)).rejects.toThrow(
|
|
OptionKeyDuplicatedException,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('createMany', () => {
|
|
it('creating many options succeeds with valid inputs', async () => {
|
|
const fieldId = faker.number.int();
|
|
const optionLength = faker.number.int({ min: 1, max: 10 });
|
|
const dto = new CreateManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = Array.from({
|
|
length: optionLength,
|
|
}).map(() => ({
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
}));
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
const options = await optionService.createMany(dto);
|
|
|
|
for (let i = 0; i < optionLength; i++) {
|
|
expect(options[i].key).toBe(dto.options[i].key);
|
|
expect(options[i].name).toBe(dto.options[i].name);
|
|
}
|
|
});
|
|
it('creating many options fails with duplicate names', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new CreateManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = Array.from({
|
|
length: faker.number.int({ min: 2, max: 10 }),
|
|
}).map(() => ({
|
|
key: faker.string.sample(),
|
|
name: 'duplicateName',
|
|
}));
|
|
|
|
await expect(optionService.createMany(dto)).rejects.toThrow(
|
|
OptionNameDuplicatedException,
|
|
);
|
|
});
|
|
it('creating many options fails with duplicate keys', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new CreateManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = Array.from({
|
|
length: faker.number.int({ min: 2, max: 10 }),
|
|
}).map(() => ({
|
|
key: 'duplicateKey',
|
|
name: faker.string.sample(),
|
|
}));
|
|
|
|
await expect(optionService.createMany(dto)).rejects.toThrow(
|
|
OptionKeyDuplicatedException,
|
|
);
|
|
});
|
|
});
|
|
describe('replaceMany', () => {
|
|
it('replacing many options succeeds with valid inputs', async () => {
|
|
const fieldId = faker.number.int();
|
|
const length = faker.number.int({ min: 1, max: 10 });
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = Array.from({
|
|
length,
|
|
}).map(() => ({
|
|
id: faker.number.int(),
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
}));
|
|
jest.spyOn(optionRepo, 'find').mockResolvedValue(
|
|
Array.from({
|
|
length: faker.number.int({ min: 1, max: 10 }),
|
|
}).map(() => ({
|
|
id: faker.number.int(),
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
deletedAt: null,
|
|
})) as unknown as OptionEntity[],
|
|
);
|
|
jest.spyOn(optionRepo, 'query');
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
await optionService.replaceMany(dto);
|
|
|
|
expect(optionRepo.save).toHaveBeenCalledTimes(length);
|
|
});
|
|
|
|
it('replacing many options fails with duplicate names', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = Array.from({
|
|
length: faker.number.int({ min: 2, max: 10 }),
|
|
}).map(() => ({
|
|
id: faker.number.int(),
|
|
key: faker.string.sample(),
|
|
name: 'duplicateName',
|
|
}));
|
|
|
|
await expect(optionService.replaceMany(dto)).rejects.toThrow(
|
|
OptionNameDuplicatedException,
|
|
);
|
|
});
|
|
|
|
it('replacing many options fails with duplicate keys', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = Array.from({
|
|
length: faker.number.int({ min: 2, max: 10 }),
|
|
}).map(() => ({
|
|
id: faker.number.int(),
|
|
key: 'duplicateKey',
|
|
name: faker.string.sample(),
|
|
}));
|
|
|
|
await expect(optionService.replaceMany(dto)).rejects.toThrow(
|
|
OptionKeyDuplicatedException,
|
|
);
|
|
});
|
|
|
|
it('replacing many options succeeds with empty options array', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = [];
|
|
jest.spyOn(optionRepo, 'find').mockResolvedValue([]);
|
|
jest.spyOn(optionRepo, 'query');
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
await optionService.replaceMany(dto);
|
|
|
|
expect(optionRepo.save).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
it('replacing many options handles inactive options correctly', async () => {
|
|
const fieldId = faker.number.int();
|
|
const optionId = faker.number.int();
|
|
const key = faker.string.sample();
|
|
const name = faker.string.sample();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = [{ id: optionId, key, name }];
|
|
|
|
jest.spyOn(optionRepo, 'find').mockResolvedValue([
|
|
{
|
|
id: optionId,
|
|
key: 'deleted_' + key,
|
|
name,
|
|
deletedAt: new Date(),
|
|
},
|
|
] as unknown as OptionEntity[]);
|
|
jest.spyOn(optionRepo, 'query');
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
await optionService.replaceMany(dto);
|
|
|
|
expect(optionRepo.save).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('replacing many options deletes unused options', async () => {
|
|
const fieldId = faker.number.int();
|
|
const existingOptionId = faker.number.int();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = [
|
|
{
|
|
id: faker.number.int(),
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
},
|
|
];
|
|
|
|
jest.spyOn(optionRepo, 'find').mockResolvedValue([
|
|
{
|
|
id: existingOptionId,
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
deletedAt: null,
|
|
},
|
|
] as unknown as OptionEntity[]);
|
|
jest.spyOn(optionRepo, 'query');
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
await optionService.replaceMany(dto);
|
|
|
|
expect(optionRepo.query).toHaveBeenCalledWith(
|
|
expect.stringContaining('UPDATE'),
|
|
expect.arrayContaining([expect.any(String), [existingOptionId]]),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('findByFieldId', () => {
|
|
it('finding options by field id succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const mockOptions = Array.from({
|
|
length: faker.number.int({ min: 1, max: 10 }),
|
|
}).map(() => ({
|
|
id: faker.number.int(),
|
|
key: faker.string.sample(),
|
|
name: faker.string.sample(),
|
|
fieldId,
|
|
})) as unknown as OptionEntity[];
|
|
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue(mockOptions);
|
|
|
|
const result = await optionService.findByFieldId({ fieldId });
|
|
|
|
expect(optionRepo.findBy).toHaveBeenCalledWith({
|
|
field: { id: fieldId },
|
|
});
|
|
expect(result).toEqual(mockOptions);
|
|
});
|
|
|
|
it('finding options by field id returns empty array when no options exist', async () => {
|
|
const fieldId = faker.number.int();
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue([]);
|
|
|
|
const result = await optionService.findByFieldId({ fieldId });
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('create edge cases', () => {
|
|
it('creating an option with empty key succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const name = faker.string.sample();
|
|
const dto = new CreateOptionDto();
|
|
dto.fieldId = fieldId;
|
|
dto.key = '';
|
|
dto.name = name;
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue([]);
|
|
jest
|
|
.spyOn(optionRepo, 'save')
|
|
.mockImplementation(() => Promise.resolve({ key: '', name } as any));
|
|
|
|
const result = await optionService.create(dto);
|
|
|
|
expect(result.key).toBe('');
|
|
});
|
|
|
|
it('creating an option with empty name succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const key = faker.string.sample();
|
|
const dto = new CreateOptionDto();
|
|
dto.fieldId = fieldId;
|
|
dto.key = key;
|
|
dto.name = '';
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue([]);
|
|
jest
|
|
.spyOn(optionRepo, 'save')
|
|
.mockImplementation(() => Promise.resolve({ key, name: '' } as any));
|
|
|
|
const result = await optionService.create(dto);
|
|
|
|
expect(result.name).toBe('');
|
|
});
|
|
|
|
it('creating an option with null fieldId succeeds', async () => {
|
|
const dto = new CreateOptionDto();
|
|
|
|
dto.fieldId = null as any;
|
|
dto.key = faker.string.sample();
|
|
dto.name = faker.string.sample();
|
|
jest.spyOn(optionRepo, 'findBy').mockResolvedValue([]);
|
|
jest
|
|
.spyOn(optionRepo, 'save')
|
|
.mockImplementation(() =>
|
|
Promise.resolve({ key: '', name: faker.string.sample() } as any),
|
|
);
|
|
|
|
const result = await optionService.create(dto);
|
|
|
|
expect(result).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('createMany edge cases', () => {
|
|
it('creating many options with empty array succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new CreateManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = [];
|
|
jest
|
|
.spyOn(optionRepo, 'save')
|
|
.mockImplementation(() => Promise.resolve([] as any));
|
|
|
|
const result = await optionService.createMany(dto);
|
|
|
|
expect(result).toEqual([]);
|
|
expect(optionRepo.save).toHaveBeenCalledWith([]);
|
|
});
|
|
|
|
it('creating many options with single option succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new CreateManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
dto.options = [
|
|
{ key: faker.string.sample(), name: faker.string.sample() },
|
|
];
|
|
jest
|
|
.spyOn(optionRepo, 'save')
|
|
.mockImplementation(() => Promise.resolve([{}] as any));
|
|
|
|
const result = await optionService.createMany(dto);
|
|
|
|
expect(result).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe('replaceMany edge cases', () => {
|
|
it('replacing many options with null options array succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
|
|
dto.options = null as any;
|
|
jest.spyOn(optionRepo, 'find').mockResolvedValue([]);
|
|
jest.spyOn(optionRepo, 'query');
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
await optionService.replaceMany(dto);
|
|
|
|
expect(optionRepo.save).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
it('replacing many options with undefined options array succeeds', async () => {
|
|
const fieldId = faker.number.int();
|
|
const dto = new ReplaceManyOptionsDto();
|
|
dto.fieldId = fieldId;
|
|
|
|
dto.options = undefined as any;
|
|
jest.spyOn(optionRepo, 'find').mockResolvedValue([]);
|
|
jest.spyOn(optionRepo, 'query');
|
|
jest.spyOn(optionRepo, 'save');
|
|
|
|
await optionService.replaceMany(dto);
|
|
|
|
expect(optionRepo.save).toHaveBeenCalledTimes(0);
|
|
});
|
|
});
|
|
});
|