first commit
CI / typecheck (push) Successful in 1m8s
CI / format (push) Failing after 1m6s
CI / lint (push) Failing after 49s
CI / test (push) Failing after 1m8s

This commit is contained in:
SDI
2026-07-15 18:05:12 +09:00
commit 12e4f17b62
4633 changed files with 817125 additions and 0 deletions
+390
View File
@@ -0,0 +1,390 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getDataSourceToken } from '@nestjs/typeorm';
import request from 'supertest';
import type { DataSource, Repository } from 'typeorm';
import { CodeTypeEnum } from '@/shared/code/code-type.enum';
import { CodeEntity } from '@/shared/code/code.entity';
import { CodeService } from '@/shared/code/code.service';
import { AppModule } from '@/app.module';
import {
EmailUserSignInRequestDto,
EmailUserSignUpRequestDto,
EmailVerificationCodeRequestDto,
EmailVerificationMailingRequestDto,
InvitationUserSignUpRequestDto,
} from '@/domains/admin/auth/dtos/requests';
import type { SignInResponseDto } from '@/domains/admin/auth/dtos/responses/sign-in-response.dto';
import { TenantEntity } from '@/domains/admin/tenant/tenant.entity';
import { UserDto } from '@/domains/admin/user/dtos/user.dto';
import {
UserStateEnum,
UserTypeEnum,
} from '@/domains/admin/user/entities/enums';
import { UserEntity } from '@/domains/admin/user/entities/user.entity';
import { UserPasswordService } from '@/domains/admin/user/user-password.service';
import { clearEntities } from '@/test-utils/util-functions';
interface JwtPayload {
sub: number;
email: string;
permissions: string[];
roleName: string;
}
describe('AppController (e2e)', () => {
let app: INestApplication;
let codeService: CodeService;
let userPasswordService: UserPasswordService;
let jwtService: JwtService;
let dataSource: DataSource;
let userRepo: Repository<UserEntity>;
let codeRepo: Repository<CodeEntity>;
let tenantRepo: Repository<TenantEntity>;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
await app.init();
dataSource = module.get(getDataSourceToken());
userRepo = dataSource.getRepository(UserEntity);
codeRepo = dataSource.getRepository(CodeEntity);
tenantRepo = dataSource.getRepository(TenantEntity);
codeService = module.get(CodeService);
userPasswordService = module.get(UserPasswordService);
jwtService = module.get(JwtService);
});
afterAll(async () => {
await dataSource.destroy();
await app.close();
});
beforeEach(async () => {
await clearEntities([userRepo, codeRepo, tenantRepo]);
await tenantRepo.save({
allowDomains: [],
siteName: faker.string.sample(),
});
});
describe('/auth/email/code (POST)', () => {
it('positive case', async () => {
const dto = new EmailVerificationMailingRequestDto();
dto.email = faker.internet.email();
return request(app.getHttpServer() as Server)
.post('/auth/email/code')
.send(dto)
.expect(201)
.expect(({ body }) => {
expect(body).toHaveProperty('expiredAt');
});
});
it('same email user already exists', async () => {
const user = await userRepo.save({
email: faker.internet.email(),
hashPassword: faker.internet.password(),
state: UserStateEnum.Active,
});
const dto = new EmailVerificationMailingRequestDto();
dto.email = user.email;
return request(app.getHttpServer() as Server)
.post('/auth/email/code')
.send(dto)
.expect(400);
});
});
describe('/auth/email/code/verify (POST)', () => {
let email: string;
let code: string;
beforeEach(async () => {
email = faker.internet.email();
code = await codeService.setCode({
type: CodeTypeEnum.EMAIL_VEIRIFICATION,
key: email,
});
});
it('positive', async () => {
const dto = new EmailVerificationCodeRequestDto();
dto.email = email;
dto.code = code;
const originalCode = await codeRepo.findOneBy({ code });
expect(originalCode?.isVerified).toEqual(false);
await request(app.getHttpServer() as Server)
.post('/auth/email/code/verify')
.send(dto)
.expect(200);
const updatedCode = await codeRepo.findOneBy({ code });
expect(updatedCode?.isVerified).toEqual(true);
});
it('invalid code', async () => {
const dto = new EmailVerificationCodeRequestDto();
dto.email = email;
dto.code = faker.string.sample();
const originalCode = await codeRepo.findOneBy({ code });
expect(originalCode?.isVerified).toEqual(false);
return request(app.getHttpServer() as Server)
.post('/auth/email/code/verify')
.send(dto)
.expect(400)
.then(async () => {
const updatedCode = await codeRepo.findOneBy({ code });
expect(updatedCode?.isVerified).toEqual(false);
});
});
it('invalid email', async () => {
const dto = new EmailVerificationCodeRequestDto();
dto.email = faker.internet.email();
dto.code = code;
return request(app.getHttpServer() as Server)
.post('/auth/email/code/verify')
.send(dto)
.expect(404);
});
});
describe('/auth/signUp/email', () => {
let email: string;
const setCode = async (email: string) =>
await codeService.setCode({
type: CodeTypeEnum.EMAIL_VEIRIFICATION,
key: email,
});
const verifyEmail = async (code: string, email: string) =>
await codeService.verifyCode({
type: CodeTypeEnum.EMAIL_VEIRIFICATION,
code,
key: email,
});
beforeEach(() => {
email = faker.internet.email();
});
it('positive', async () => {
const code = await setCode(email);
await verifyEmail(code, email);
const dto = new EmailUserSignUpRequestDto();
dto.email = email;
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/email')
.send(dto)
.expect(201)
.then(async () => {
const user = await userRepo.findOneBy({ email });
expect(user).toBeDefined();
});
});
it('must verfy email', async () => {
const dto = new EmailUserSignUpRequestDto();
dto.email = faker.internet.email();
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/email')
.send(dto)
.expect(400);
});
it('not verified email', async () => {
await setCode(email);
const dto = new EmailUserSignUpRequestDto();
dto.email = faker.internet.email();
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/email')
.send(dto)
.expect(400);
});
it('same email', async () => {
await userRepo.save({
email,
hashPassword: faker.internet.password(),
state: UserStateEnum.Active,
});
const code = await setCode(email);
await verifyEmail(code, email);
const dto = new EmailUserSignUpRequestDto();
dto.email = email;
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/email')
.send(dto)
.expect(400);
});
});
describe('/auth/signUp/invitation (POST)', () => {
let email: string;
const setCode = async (email: string) =>
await codeService.setCode({
type: CodeTypeEnum.USER_INVITATION,
key: email,
data: {
roleId: 1,
userType: UserTypeEnum.GENERAL,
invitedBy: new UserDto(),
},
});
beforeEach(() => {
email = faker.internet.email();
});
it('positive case', async () => {
const code = await setCode(email);
const dto = new InvitationUserSignUpRequestDto();
dto.code = code;
dto.email = email;
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/invitation')
.send(dto)
.expect(201);
});
it('no invitation', async () => {
const dto = new InvitationUserSignUpRequestDto();
dto.code = faker.string.sample();
dto.email = email;
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/invitation')
.send(dto)
.expect(400);
});
it('invalid code', async () => {
await setCode(email);
const dto = new InvitationUserSignUpRequestDto();
dto.code = faker.string.sample();
dto.email = email;
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/invitation')
.send(dto)
.expect(400);
});
it('invalid email', async () => {
const code = await setCode(email);
const dto = new InvitationUserSignUpRequestDto();
dto.code = code;
dto.email = faker.internet.email();
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signUp/invitation')
.send(dto)
.expect(400);
});
});
describe('/auth/signIn/email (POST)', () => {
let userEntity: UserEntity;
let password: string;
beforeEach(async () => {
password = faker.internet.password();
userEntity = await userRepo.save({
email: faker.internet.email(),
hashPassword: await userPasswordService.createHashPassword(password),
state: UserStateEnum.Active,
});
});
it('positive case', () => {
const dto = new EmailUserSignInRequestDto();
dto.email = userEntity.email;
dto.password = password;
return request(app.getHttpServer() as Server)
.post('/auth/signIn/email')
.send(dto)
.expect(201)
.expect(({ body }) => {
expect(body).toHaveProperty('accessToken');
expect(body).toHaveProperty('refreshToken');
const payload = jwtService.verify<JwtPayload>(
(body as SignInResponseDto).accessToken,
);
expect(payload.sub).toEqual(userEntity.id);
expect(payload).toHaveProperty('email');
expect(payload).toHaveProperty('permissions');
expect(payload).toHaveProperty('roleName');
});
});
it('invalid email', () => {
const dto = new EmailUserSignInRequestDto();
dto.email = faker.internet.email();
dto.password = password;
return request(app.getHttpServer() as Server)
.post('/auth/signIn/email')
.send(dto)
.expect(404);
});
it('invalid password', () => {
const dto = new EmailUserSignInRequestDto();
dto.email = userEntity.email;
dto.password = faker.internet.password();
return request(app.getHttpServer() as Server)
.post('/auth/signIn/email')
.send(dto)
.expect(401);
});
});
});
+324
View File
@@ -0,0 +1,324 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getDataSourceToken } from '@nestjs/typeorm';
import { Client } from '@opensearch-project/opensearch';
import type { Indices_Get_Response } from '@opensearch-project/opensearch/api/indices/get';
import request from 'supertest';
import type { DataSource, Repository } from 'typeorm';
import { AppModule } from '@/app.module';
import {
FieldFormatEnum,
FieldPropertyEnum,
FieldStatusEnum,
} from '@/common/enums';
import { HttpExceptionFilter } from '@/common/filters';
import { ChannelEntity } from '@/domains/admin/channel/channel/channel.entity';
import { ChannelService } from '@/domains/admin/channel/channel/channel.service';
import {
CreateChannelRequestDto,
UpdateChannelRequestDto,
} from '@/domains/admin/channel/channel/dtos/requests';
import type { CreateChannelResponseDto } from '@/domains/admin/channel/channel/dtos/responses/create-channel-response.dto';
import type { FindChannelByIdResponseDto } from '@/domains/admin/channel/channel/dtos/responses/find-channel-by-id-response.dto';
import type { FindChannelsByProjectIdResponseDto } from '@/domains/admin/channel/channel/dtos/responses/find-channels-by-id-response.dto';
import { FieldEntity } from '@/domains/admin/channel/field/field.entity';
import { FIELD_TYPES_TO_MAPPING_TYPES } from '@/domains/admin/channel/field/field.service';
import { OptionEntity } from '@/domains/admin/channel/option/option.entity';
import { ProjectEntity } from '@/domains/admin/project/project/project.entity';
import { createFieldDto, optionSort } from '@/test-utils/fixtures';
import {
clearEntities,
DEFAULT_FIELD_COUNT,
} from '@/test-utils/util-functions';
describe('AppController (e2e)', () => {
let app: INestApplication;
let dataSource: DataSource;
let channelRepo: Repository<ChannelEntity>;
let projectRepo: Repository<ProjectEntity>;
let fieldRepo: Repository<FieldEntity>;
let optionRepo: Repository<OptionEntity>;
let osService: Client;
let channelService: ChannelService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalPipes(
new ValidationPipe({ transform: true, whitelist: true }),
);
await app.init();
dataSource = module.get(getDataSourceToken());
channelRepo = dataSource.getRepository(ChannelEntity);
projectRepo = dataSource.getRepository(ProjectEntity);
fieldRepo = dataSource.getRepository(FieldEntity);
optionRepo = dataSource.getRepository(OptionEntity);
osService = module.get(Client);
channelService = module.get(ChannelService);
});
afterAll(async () => {
await dataSource.destroy();
await app.close();
});
let project: ProjectEntity;
beforeEach(async () => {
await clearEntities([projectRepo, channelRepo, fieldRepo, optionRepo]);
project = await projectRepo.save({
name: faker.string.sample(),
description: faker.string.sample(),
});
});
it('/projects/:projectId/channels (POST)', () => {
const fieldCount = faker.number.int({ min: 1, max: 10 });
const dto = new CreateChannelRequestDto();
dto.name = faker.string.sample();
dto.description = faker.string.sample();
dto.fields = Array.from({ length: fieldCount }).map((_) =>
createFieldDto({}),
);
return request(app.getHttpServer() as Server)
.post(`/projects/${project.id}/channels`)
.send(dto)
.expect(201)
.then(async ({ body }: { body: CreateChannelResponseDto }) => {
expect(body.id).toBeDefined();
const channel = await channelRepo.findOneBy({
id: body.id,
});
expect(channel).toBeDefined();
if (channel === null) {
throw new Error('Channel not found');
}
expect(channel.name).toEqual(dto.name);
expect(channel.description).toEqual(dto.description);
const fields = await fieldRepo.find({
where: { channel: { id: body.id } },
relations: { options: true },
});
expect(fields).toHaveLength(fieldCount + DEFAULT_FIELD_COUNT);
expect(fields.map(fieldEntityToDto2)).toEqual(
dto.fields
.concat({
name: 'createdAt',
key: 'createdAt',
format: FieldFormatEnum.date,
property: FieldPropertyEnum.READ_ONLY,
status: FieldStatusEnum.ACTIVE,
options: undefined,
description: '',
})
.concat({
name: 'updatedAt',
key: 'updatedAt',
format: FieldFormatEnum.date,
property: FieldPropertyEnum.READ_ONLY,
status: FieldStatusEnum.ACTIVE,
options: undefined,
description: '',
}),
);
const result: Indices_Get_Response = await osService.indices.get({
index: body.id.toString(),
});
expect(Object.keys(result.body)[0]).toEqual(body.id);
Object.entries<Record<string, { type: string }>>(
result.body[body.id].mappings?.properties as
| Record<string, Record<string, { type: string }>>
| ArrayLike<Record<string, { type: string }>>,
).forEach(([fieldId, { type }]) => {
const field =
fields.find(({ id }) => id === parseInt(fieldId)) ??
new FieldEntity();
expect(field).toBeDefined();
expect(FIELD_TYPES_TO_MAPPING_TYPES[field.format]).toEqual(type);
});
});
});
it('/projects/:projectId/channels (GET)', async () => {
const total = faker.number.int(10);
await channelRepo.save(
Array.from({ length: total }).map(() => ({
name: faker.word.noun(),
description: faker.lorem.lines(1),
project: { id: project.id },
})),
);
return request(app.getHttpServer() as Server)
.get(`/projects/${project.id}/channels`)
.expect(200)
.expect(({ body }) => {
expect(body).toHaveProperty('items');
expect(body).toHaveProperty('meta');
expect(
Array.isArray((body as FindChannelsByProjectIdResponseDto).items),
).toEqual(true);
for (const channel of (body as FindChannelsByProjectIdResponseDto)
.items) {
expect(channel).toHaveProperty('id');
expect(channel).toHaveProperty('name');
expect(channel).toHaveProperty('description');
}
expect(
(body as FindChannelsByProjectIdResponseDto).meta.totalItems,
).toEqual(total);
});
});
it('/channels/:id (GET)', async () => {
const channel = await channelRepo.save({
name: faker.string.sample(),
description: faker.string.sample(),
});
return request(app.getHttpServer() as Server)
.get('/channels/' + channel.id)
.expect(200)
.expect(({ body }) => {
expect((body as FindChannelByIdResponseDto).id).toEqual(channel.id);
expect((body as FindChannelByIdResponseDto).name).toEqual(channel.name);
expect((body as FindChannelByIdResponseDto).description).toEqual(
channel.description,
);
});
});
it('/channels/:id (PUT)', async () => {
const fieldCount = faker.number.int({ min: 1, max: 10 });
const { id: channelId } = await channelService.create({
projectId: project.id,
name: faker.string.sample(),
description: faker.string.sample(),
fields: Array.from({ length: fieldCount }).map((_) => createFieldDto({})),
feedbackSearchMaxDays: faker.number.int({ min: 1, max: 30 }),
imageConfig: null,
});
const originalFields = await fieldRepo.find({
where: { channel: { id: channelId } },
relations: { options: true },
});
// const existingFields = originalFields
// .map(fieldEntityToDto)
// .filter((v) => v.name !== 'createdAt' && v.name !== 'updatedAt');
const newfields = Array.from({ length: fieldCount }).map((_) =>
createFieldDto({}),
);
const dto = new UpdateChannelRequestDto();
dto.name = faker.string.sample();
dto.description = faker.string.sample();
// dto.fields = [...existingFields, ...newfields];
return request(app.getHttpServer() as Server)
.put(`/channels/${channelId}`)
.send(dto)
.expect(200)
.then(async () => {
const updatedchannel = await channelRepo.findOneBy({ id: channelId });
expect(updatedchannel?.name).toEqual(dto.name);
expect(updatedchannel?.description).toEqual(dto.description);
const updatedFields = await fieldRepo.find({
where: { channel: { id: channelId } },
relations: { options: true },
});
expect(updatedFields).toHaveLength(
originalFields.length + newfields.length,
);
// const activeFields = updatedFields.filter(
// (v) =>
// v.status === FieldStatusEnum.ACTIVE &&
// v.name !== 'updatedAt' &&
// v.name !== 'createdAt',
// );
// expect(activeFields.length).toEqual(dto.fields.length);
// activeFields.forEach((activeField) => {
// const fieldDto = dto.fields.find((v) => v.name === activeField.name);
// expect(fieldDto).toBeDefined();
// expect(activeField.name).toEqual(fieldDto.name);
// expect(activeField.type).toEqual(fieldDto.type);
// if (activeField.format === FieldFormatEnum.select) {
// expect(
// activeField.options.map((v) => v.name).sort(strSort),
// ).toEqual(fieldDto.options?.map((v) => v.name).sort(strSort));
// }
// });
});
});
});
// const fieldEntityToDto = (field: FieldEntity) => ({
// id: field.id,
// name: field.name,
// key: field.key,
// format: field.format,
// type: field.type,
// status: field.status,
// description: field.description,
// options: field.format === FieldFormatEnum.select ? field.options : undefined,
// });
const fieldEntityToDto2 = (field: FieldEntity) => ({
name: field.name,
format: field.format,
property: field.property,
status: field.status,
description: field.description,
options:
field.format === FieldFormatEnum.select ?
(field.options ?? []).map(({ name }) => ({ name })).sort(optionSort)
: undefined,
});
+284
View File
@@ -0,0 +1,284 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Client } from '@opensearch-project/opensearch';
import request from 'supertest';
import type { Repository } from 'typeorm';
import { AppModule } from '@/app.module';
import { FieldFormatEnum } from '@/common/enums';
import { HttpExceptionFilter } from '@/common/filters';
import { ChannelEntity } from '@/domains/admin/channel/channel/channel.entity';
import { ChannelService } from '@/domains/admin/channel/channel/channel.service';
import { FieldEntity } from '@/domains/admin/channel/field/field.entity';
import { FeedbackService } from '@/domains/admin/feedback/feedback.service';
import { ProjectEntity } from '@/domains/admin/project/project/project.entity';
import { ProjectService } from '@/domains/admin/project/project/project.service';
import { createFieldDto, getRandomValue } from '@/test-utils/fixtures';
import { clearEntities } from '@/test-utils/util-functions';
describe('AppController (e2e)', () => {
let app: INestApplication;
let projectService: ProjectService;
let channelService: ChannelService;
let feedbackService: FeedbackService;
let projectRepo: Repository<ProjectEntity>;
let channelRepo: Repository<ChannelEntity>;
let fieldRepo: Repository<FieldEntity>;
let osService: Client;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({ transform: true, whitelist: true }),
);
app.useGlobalFilters(new HttpExceptionFilter());
await app.init();
projectService = module.get(ProjectService);
channelService = module.get(ChannelService);
feedbackService = module.get(FeedbackService);
projectRepo = module.get(getRepositoryToken(ProjectEntity));
channelRepo = module.get(getRepositoryToken(ChannelEntity));
fieldRepo = module.get(getRepositoryToken(FieldEntity));
osService = module.get(Client);
});
afterAll(async () => {
await app.close();
});
let channel: ChannelEntity = new ChannelEntity();
let fields: FieldEntity[];
beforeEach(async () => {
await clearEntities([projectRepo, channelRepo, fieldRepo]);
const { id: projectId } = await projectService.create({
name: faker.word.noun(),
description: faker.lorem.lines(1),
timezone: {
countryCode: 'KR',
name: 'Asia/Seoul',
offset: '+09:00',
},
});
const { id: channelId } = await channelService.create({
projectId,
name: faker.word.noun(),
description: faker.lorem.lines(1),
fields: Array.from({
length: faker.number.int({ min: 1, max: 10 }),
}).map(createFieldDto),
feedbackSearchMaxDays: faker.number.int({ min: 1, max: 30 }),
imageConfig: null,
});
channel = await channelService.findById({ channelId });
fields = await fieldRepo.find({
where: { channel: { id: channel.id } },
relations: { options: true },
});
});
it('/channels/:channelId/feedbacks (POST)', () => {
const dto: Record<string, string | number | string[] | number[]> = {};
fields
.filter(({ name }) => name !== 'createdAt' && name !== 'updatedAt')
.forEach(({ name, format, options }) => {
dto[name] = getRandomValue(format, options);
});
return request(app.getHttpServer() as Server)
.post(`/channels/${channel.id}/feedbacks`)
.send(dto)
.expect(201)
.then(
async ({
body,
}: {
body: Record<string, any> & { issueNames?: string[] };
}) => {
expect(body.id).toBeDefined();
const esResult = await osService.get({
id: body.id as string,
index: channel.id.toString(),
});
delete esResult.body._source?.[
(fields.find((v) => v.name === 'createdAt') ?? { id: 0 }).id
];
expect(toApi(dto, fields)).toMatchObject(esResult.body._source ?? {});
},
);
});
it('/channels/:channelId/feedbacks (GET)', async () => {
const feedbackCount = faker.number.int({ min: 1, max: 10 });
const dataset: Record<string, any>[] = [];
for (let i = 0; i < feedbackCount; i++) {
const data: Record<string, any> = {};
fields
.filter(({ name }) => name !== 'createdAt' && name !== 'updatedAt')
.forEach(({ name, format, options }) => {
data[name] = getRandomValue(format, options);
});
await feedbackService.create({ channelId: channel.id, data });
dataset.push(data);
}
return request(app.getHttpServer() as Server)
.get(`/channels/${channel.id}/feedbacks`)
.expect(200)
.then(({ body }) => {
expect(Array.isArray(body)).toEqual(true);
expect(body).toHaveLength(feedbackCount);
});
});
it('/channels/:channelId/feedbacks/:feedbackId/field/:fieldId (PUT)', async () => {
const data = {};
const targetFields = fields.filter(
({ name }) => name !== 'createdAt' && name !== 'updatedAt',
);
targetFields.forEach(({ name, format, options }) => {
data[name] = getRandomValue(format, options);
});
const { id: feedbackId } = await feedbackService.create({
channelId: channel.id,
data,
});
const targetField = targetFields[faker.number.int(targetFields.length - 1)];
const newValue = getRandomValue(targetField.format, targetField.options);
return request(app.getHttpServer() as Server)
.put(
`/channels/${channel.id}/feedbacks/${feedbackId}/field/${targetField.id}`,
)
.send({ value: newValue })
.expect(200)
.then(async () => {
const { body } = await osService.get({
id: feedbackId.toString(),
index: channel.id.toString(),
});
expect(body._source?.[targetField.id]).toEqual(
targetField.format === FieldFormatEnum.select ?
((targetField.options ?? []).find((v) => v.name === newValue) ??
{ id: 0 }.id)
: newValue,
);
});
});
// const channel = await channelModel.create({
// name: faker.string.sample(),
// description: faker.string.sample(),
// });
// return request(app.getHttpServer())
// .get('/channels/' + channel.id)
// .expect(200)
// .expect(({ body }) => {
// expect(body.id).toEqual(channel.id);
// expect(body.name).toEqual(channel.name);
// expect(body.description).toEqual(channel.description);
// });
// });
// it('/channels/:id (PUT)', async () => {
// const channel = await channelModel.create({
// name: faker.string.sample(),
// description: faker.string.sample(),
// });
// await fieldModel.create(
// Array.from({ length: faker.number.int({ min: 1, max: 5 }) })
// .map(createField)
// .map((v) => ({ ...v, channel: { _id: channel.id } })),
// );
// const dto = new UpdateChannelRequestDto();
// dto.name = faker.string.sample();
// dto.description = faker.string.sample();
// dto.fields = [];
// return request(app.getHttpServer())
// .put(`/channels/${channel.id}`)
// .send(dto)
// .expect(200)
// .then(async () => {
// const updatedchannel = await channelModel.findById(channel.id);
// expect(updatedchannel.name).toEqual(dto.name);
// expect(updatedchannel.description).toEqual(dto.description);
// const fields = await fieldModel.find({ channel: { _id: channel.id } });
// expect(fields).toHaveLength(dto.fields.length);
// });
// });
// it('/channels/:id (DELETE)', async () => {
// const channel = await channelModel.create({
// name: faker.string.sample(),
// description: faker.string.sample(),
// });
// await request(app.getHttpServer())
// .delete(`/channels/${channel.id}`)
// .expect(200)
// .then(async () => {
// expect(await channelModel.findById(channel.id)).toBeNull();
// });
// expect(await channelModel.findById(channel.id)).toBeNull();
// });
});
const toApi = (
data: Record<string, string | number | string[] | number[]>,
fields: FieldEntity[],
) => {
return Object.entries(data).reduce((prev, [key, value]) => {
const field: FieldEntity =
fields.find((v) => v.name === key) ?? new FieldEntity();
return Object.assign(prev, {
[field.id]:
field.format === FieldFormatEnum.select ?
(
(field.options ?? []).find((v) => v.name === value) ??
new FieldEntity()
).id
: value,
});
}, {});
};
+160
View File
@@ -0,0 +1,160 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getDataSourceToken } from '@nestjs/typeorm';
import request from 'supertest';
import type { DataSource, Repository } from 'typeorm';
import { AppModule } from '@/app.module';
import { HttpExceptionFilter } from '@/common/filters';
import { CreateProjectRequestDto } from '@/domains/admin/project/project/dtos/requests';
import type { FindProjectByIdResponseDto } from '@/domains/admin/project/project/dtos/responses/find-project-by-id-response.dto';
import type { FindProjectsResponseDto } from '@/domains/admin/project/project/dtos/responses/find-projects-response.dto';
import { ProjectEntity } from '@/domains/admin/project/project/project.entity';
import { clearEntities } from '@/test-utils/util-functions';
describe('AppController (e2e)', () => {
let app: INestApplication;
let dataSource: DataSource;
let projectRepo: Repository<ProjectEntity>;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalPipes(
new ValidationPipe({ transform: true, whitelist: true }),
);
await app.init();
dataSource = module.get(getDataSourceToken());
projectRepo = dataSource.getRepository(ProjectEntity);
});
afterAll(async () => {
await dataSource.destroy();
await app.close();
});
beforeEach(async () => {
await clearEntities([projectRepo]);
});
it('/projects (GET)', async () => {
const total = faker.number.int(10);
await projectRepo.save(
Array.from({ length: total }).map(() => ({
name: faker.string.sample(),
description: faker.string.sample(),
})),
);
return request(app.getHttpServer() as Server)
.get('/projects')
.expect(200)
.expect(({ body }) => {
expect(body).toHaveProperty('items');
expect(body).toHaveProperty('meta');
expect(Array.isArray((body as FindProjectsResponseDto).items)).toEqual(
true,
);
for (const project of (body as FindProjectsResponseDto).items) {
expect(project).toHaveProperty('id');
expect(project).toHaveProperty('name');
expect(project).toHaveProperty('description');
}
expect((body as FindProjectsResponseDto).meta.totalItems).toEqual(
total,
);
});
});
it('/projects (POST)', () => {
const dto = new CreateProjectRequestDto();
dto.name = faker.string.sample();
dto.description = faker.string.sample();
return request(app.getHttpServer() as Server)
.post('/projects')
.send(dto)
.expect(201);
});
it('/projects/:id (GET)', async () => {
const project = await projectRepo.save({
name: faker.string.sample(),
description: faker.string.sample(),
});
return request(app.getHttpServer() as Server)
.get('/projects/' + project.id)
.expect(200)
.expect(({ body }) => {
expect((body as FindProjectByIdResponseDto).id).toEqual(project.id);
expect((body as FindProjectByIdResponseDto).name).toEqual(project.name);
expect((body as FindProjectByIdResponseDto).description).toEqual(
project.description,
);
});
});
it('/projects/:id (PUT)', async () => {
const project = await projectRepo.save({
name: faker.string.sample(),
description: faker.string.sample(),
});
const name = faker.string.sample();
const description = faker.string.sample();
return request(app.getHttpServer() as Server)
.put(`/projects/${project.id}`)
.send({ name, description })
.expect(200)
.then(async () => {
const updatedproject = await projectRepo.findOneBy({ id: project.id });
expect(updatedproject?.name).toEqual(name);
expect(updatedproject?.description).toEqual(description);
});
});
// it('/projects/:id (DELETE)', async () => {
// const project = await projectModel.create({
// name: faker.string.sample(),
// description: faker.string.sample(),
// });
// await request(app.getHttpServer())
// .delete(`/projects/${project.id}`)
// .expect(200)
// .then(async () => {
// expect(await projectModel.findById(project.id)).toBeNull();
// });
// expect(await projectModel.findById(project.id)).toBeNull();
// });
});
+13
View File
@@ -0,0 +1,13 @@
{
"displayName": "api-e2e",
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"moduleNameMapper": {
"^@/(.*)$": ["<rootDir>/../src/$1"]
},
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}
+202
View File
@@ -0,0 +1,202 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getDataSourceToken } from '@nestjs/typeorm';
import request from 'supertest';
import type { DataSource, Repository } from 'typeorm';
import { AppModule } from '@/app.module';
import { AuthService } from '@/domains/admin/auth/auth.service';
import { UpdateRoleRequestDto } from '@/domains/admin/project/role/dtos/requests';
import type {
GetAllRolesResponseDto,
GetAllRolesResponseRoleDto,
} from '@/domains/admin/project/role/dtos/responses/get-all-roles-response.dto';
import { PermissionEnum } from '@/domains/admin/project/role/permission.enum';
import { RoleEntity } from '@/domains/admin/project/role/role.entity';
import {
clearEntities,
getRandomEnumValues,
signInTestUser,
} from '@/test-utils/util-functions';
import { HttpStatusCode } from '@/types/http-status';
describe('AppController (e2e)', () => {
let app: INestApplication;
let dataSource: DataSource;
let roleRepo: Repository<RoleEntity>;
let authService: AuthService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({ transform: true, whitelist: true }),
);
await app.init();
dataSource = module.get(getDataSourceToken());
roleRepo = dataSource.getRepository(RoleEntity);
authService = module.get(AuthService);
});
afterAll(async () => {
await dataSource.destroy();
await app.close();
});
let accessToken: string;
beforeEach(async () => {
await clearEntities([roleRepo]);
const { jwt } = await signInTestUser(dataSource, authService);
accessToken = jwt.accessToken;
});
describe('/roles (GET)', () => {
it('positive case', async () => {
const total = faker.number.int(20);
for (let i = 0; i < total; i++) {
await roleRepo.save({
name: faker.string.sample(),
permissions: getRandomEnumValues(PermissionEnum),
});
}
return request(app.getHttpServer() as Server)
.get('/roles')
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatusCode.OK)
.expect(({ body }) => {
expect(body).toHaveProperty('roles');
expect(Array.isArray((body as GetAllRolesResponseDto).roles)).toEqual(
true,
);
for (const role of (body as GetAllRolesResponseDto).roles) {
expect(role).toHaveProperty('id');
expect(role).toHaveProperty('name');
expect(role).toHaveProperty('permissions');
}
expect(body).toHaveProperty('total');
expect((body as GetAllRolesResponseDto).total).toEqual(total + 1);
});
});
it('Unauthroized', async () => {
return request(app.getHttpServer() as Server)
.get('/roles')
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
describe('/roles (POST)', () => {
it('positive case', () => {
return request(app.getHttpServer() as Server)
.post('/roles')
.set('Authorization', `Bearer ${accessToken}`)
.send({
name: faker.string.sample(),
permissions: getRandomEnumValues(PermissionEnum),
})
.expect(201);
});
it('Unauthroized', () => {
return request(app.getHttpServer() as Server)
.post('/roles')
.send({
name: faker.string.sample(),
permissions: getRandomEnumValues(PermissionEnum),
})
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
it('/roles/:id (GET)', async () => {
const role = await roleRepo.save({
name: faker.string.sample(),
permissions: getRandomEnumValues(PermissionEnum),
});
return request(app.getHttpServer() as Server)
.get('/roles/' + role.id)
.expect(HttpStatusCode.OK)
.expect(({ body }) => {
expect((body as GetAllRolesResponseRoleDto).id).toEqual(role.id);
expect((body as GetAllRolesResponseRoleDto).name).toEqual(role.name);
expect((body as GetAllRolesResponseRoleDto).permissions).toEqual(
role.permissions,
);
});
});
describe('/roles/:id (PUT)', () => {
it('positive case', async () => {
const role = await roleRepo.save({
name: faker.string.sample(),
permissions: getRandomEnumValues(PermissionEnum),
});
const dto = new UpdateRoleRequestDto();
dto.name = 'updatedRole';
dto.permissions = getRandomEnumValues(PermissionEnum);
await request(app.getHttpServer() as Server)
.put(`/roles/${role.id}`)
.set('Authorization', `Bearer ${accessToken}`)
.send(dto)
.expect(204)
.then(async () => {
const updatedRole = await roleRepo.findOneBy({ id: role.id });
expect(updatedRole?.name).toEqual(dto.name);
expect(updatedRole?.permissions).toEqual(dto.permissions);
});
});
it('Unauthrized', async () => {
await request(app.getHttpServer() as Server)
.put(`/roles/${faker.number.int()}`)
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
describe('/roles/:id (DELETE)', () => {
it('positive case', async () => {
const role = await roleRepo.save({
name: faker.string.sample(),
permissions: getRandomEnumValues(PermissionEnum),
});
await request(app.getHttpServer() as Server)
.delete(`/roles/${role.id}`)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatusCode.OK)
.then(async () => {
expect(await roleRepo.findOneBy({ id: role.id })).toBeNull();
});
});
it('Unauthrized', async () => {
await request(app.getHttpServer() as Server)
.delete(`/roles/${faker.number.int()}`)
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
});
+190
View File
@@ -0,0 +1,190 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getDataSourceToken } from '@nestjs/typeorm';
import request from 'supertest';
import type { DataSource, Repository } from 'typeorm';
import { AppModule } from '@/app.module';
import { AuthService } from '@/domains/admin/auth/auth.service';
import {
SetupTenantRequestDto,
UpdateTenantRequestDto,
} from '@/domains/admin/tenant/dtos/requests';
import type { GetTenantResponseDto } from '@/domains/admin/tenant/dtos/responses/get-tenant-response.dto';
import { TenantEntity } from '@/domains/admin/tenant/tenant.entity';
import { clearEntities, signInTestUser } from '@/test-utils/util-functions';
import { HttpStatusCode } from '@/types/http-status';
describe('AppController (e2e)', () => {
let app: INestApplication;
let dataSource: DataSource;
let tenantRepo: Repository<TenantEntity>;
let authService: AuthService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({ transform: true, whitelist: true }),
);
await app.init();
dataSource = module.get(getDataSourceToken());
tenantRepo = dataSource.getRepository(TenantEntity);
authService = module.get(AuthService);
});
afterAll(async () => {
await dataSource.destroy();
await app.close();
});
beforeEach(async () => {
await clearEntities([tenantRepo]);
});
describe('/tenant (POST)', () => {
it('setup', async () => {
const dto = new SetupTenantRequestDto();
dto.siteName = faker.string.sample();
return request(app.getHttpServer() as Server)
.post('/tenant')
.send(dto)
.expect(201)
.then(async () => {
const tenants = await tenantRepo.find();
expect(tenants).toHaveLength(1);
const [tenant] = tenants;
for (const key in dto) {
const value = dto[key] as string;
expect(tenant[key]).toEqual(value);
}
});
});
it('already exists', async () => {
await tenantRepo.save({
siteName: faker.string.sample(),
allowDomains: [],
});
const dto = new SetupTenantRequestDto();
dto.siteName = faker.string.sample();
return request(app.getHttpServer() as Server)
.post('/tenant')
.send(dto)
.expect(400);
});
});
describe('/tenant (PUT)', () => {
let tenant: TenantEntity;
let accessToken: string;
beforeEach(async () => {
tenant = await tenantRepo.save({
siteName: faker.string.sample(),
allowDomains: [],
});
const { jwt } = await signInTestUser(dataSource, authService);
accessToken = jwt.accessToken;
});
it('update', async () => {
const dto = new UpdateTenantRequestDto();
dto.siteName = faker.string.sample();
dto.allowDomains = [];
return request(app.getHttpServer() as Server)
.put('/tenant')
.set('Authorization', `Bearer ${accessToken}`)
.send(dto)
.expect(204)
.then(async () => {
const updatedTenant = await tenantRepo.findOne({
where: { id: tenant.id },
});
expect(updatedTenant?.siteName).toEqual(dto.siteName);
expect(updatedTenant?.allowDomains).toEqual(dto.allowDomains);
});
});
it('not found tenant', async () => {
await tenantRepo.delete({ id: tenant.id });
const dto = new UpdateTenantRequestDto();
dto.siteName = faker.string.sample();
dto.allowDomains = [];
return request(app.getHttpServer() as Server)
.put('/tenant')
.set('Authorization', `Bearer ${accessToken}`)
.send(dto)
.expect(404);
});
it('not found role', async () => {
const dto = new UpdateTenantRequestDto();
dto.siteName = faker.string.sample();
dto.allowDomains = [];
return request(app.getHttpServer() as Server)
.put('/tenant')
.set('Authorization', `Bearer ${accessToken}`)
.send(dto)
.expect(404);
});
it('unauthorized', async () => {
const dto = new UpdateTenantRequestDto();
dto.siteName = faker.string.sample();
dto.allowDomains = [];
return request(app.getHttpServer() as Server)
.put('/tenant')
.send(dto)
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
describe('/tenant (GET)', () => {
const dto = new SetupTenantRequestDto();
beforeEach(async () => {
dto.siteName = faker.string.sample();
await request(app.getHttpServer() as Server)
.post('/tenant')
.send(dto);
});
it('find', async () => {
await request(app.getHttpServer() as Server)
.get('/tenant')
.expect(200)
.expect(({ body }) => {
expect(dto.siteName).toEqual((body as GetTenantResponseDto).siteName);
});
});
});
});
+277
View File
@@ -0,0 +1,277 @@
/**
* 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 type { Server } from 'net';
import { faker } from '@faker-js/faker';
import type { INestApplication } from '@nestjs/common';
import { ValidationPipe } from '@nestjs/common';
import type { TestingModule } from '@nestjs/testing';
import { Test } from '@nestjs/testing';
import { getDataSourceToken } from '@nestjs/typeorm';
import { DateTime } from 'luxon';
import request from 'supertest';
import type { DataSource, Repository } from 'typeorm';
import { AppModule } from '@/app.module';
import { AuthService } from '@/domains/admin/auth/auth.service';
import { RoleEntity } from '@/domains/admin/project/role/role.entity';
import type { UserDto } from '@/domains/admin/user/dtos';
import type { GetAllUserResponseDto } from '@/domains/admin/user/dtos/responses/get-all-user-response.dto';
import { UserStateEnum } from '@/domains/admin/user/entities/enums';
import { UserEntity } from '@/domains/admin/user/entities/user.entity';
import {
clearEntities,
getRandomEnumValue,
signInTestUser,
} from '@/test-utils/util-functions';
import { HttpStatusCode } from '@/types/http-status';
describe('AppController (e2e)', () => {
let app: INestApplication;
let dataSource: DataSource;
let userRepo: Repository<UserEntity>;
let roleRepo: Repository<RoleEntity>;
let authService: AuthService;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(
new ValidationPipe({ transform: true, whitelist: true }),
);
await app.init();
dataSource = module.get(getDataSourceToken());
userRepo = dataSource.getRepository(UserEntity);
roleRepo = dataSource.getRepository(RoleEntity);
authService = module.get(AuthService);
});
afterAll(async () => {
await dataSource.destroy();
await app.close();
});
let total: number;
let userEntities: UserEntity[];
let accessToken: string;
let ownerUser: UserEntity;
beforeEach(async () => {
await clearEntities([userRepo, roleRepo]);
const length = faker.number.int({ min: 20, max: 30 });
userEntities = (
await userRepo.save(
Array.from({ length: length }).map(() => ({
email: faker.internet.email(),
state: getRandomEnumValue(UserStateEnum),
hashPassword: faker.internet.password(),
})),
)
).sort((a, b) =>
DateTime.fromJSDate(b.createdAt)
.diff(DateTime.fromJSDate(a.createdAt))
.as('milliseconds'),
);
const { jwt, user } = await signInTestUser(dataSource, authService);
accessToken = jwt.accessToken;
ownerUser = user;
total = length + 1;
});
describe('/users (GET)', () => {
it('no query', async () => {
const expectUsers = userEntities
.concat(ownerUser)
.sort((a, b) =>
DateTime.fromJSDate(b.createdAt)
.diff(DateTime.fromJSDate(a.createdAt))
.as('milliseconds'),
)
.map(({ id, email }) => ({
id,
email,
}))
.slice(0, 10);
return request(app.getHttpServer() as Server)
.get('/users')
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatusCode.OK)
.expect(({ body }) => {
expect(body).toHaveProperty('items');
expect(body).toHaveProperty('meta');
const { items, meta } = body as GetAllUserResponseDto;
expect(items).toEqual(expectUsers);
expect(meta.totalItems).toEqual(total);
expect(meta.itemCount).toEqual(10);
});
});
it('UnAuthorized', async () => {
return request(app.getHttpServer() as Server)
.get('/users')
.expect(HttpStatusCode.UNAUTHORIZED);
});
it('page and limit', async () => {
const limit = faker.number.int({ min: 1, max: 10 });
const page = faker.number.int({
min: 1,
max: Math.floor(total / limit),
});
const expectUsers = userEntities
.concat(ownerUser)
.sort((a, b) =>
DateTime.fromJSDate(b.createdAt)
.diff(DateTime.fromJSDate(a.createdAt))
.as('milliseconds'),
)
.map(({ id, email }) => ({
id,
email,
}))
.slice((page - 1) * limit, page * limit);
return request(app.getHttpServer() as Server)
.get(`/users?page=${page}&limit=${limit}`)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatusCode.OK)
.expect(({ body }) => {
expect(body).toHaveProperty('items');
expect(body).toHaveProperty('meta');
const { items, meta } = body as GetAllUserResponseDto;
expect(items).toEqual(expectUsers);
expect(meta.totalItems).toEqual(total);
expect(meta.itemCount).toBeLessThanOrEqual(10);
});
});
});
describe('/users (DELETE)', () => {
it('positive case', async () => {
const ids = faker.helpers.arrayElements(userEntities).map((v) => v.id);
await request(app.getHttpServer() as Server)
.delete(`/users`)
.set('Authorization', `Bearer ${accessToken}`)
.send({ ids })
.expect(HttpStatusCode.OK)
.then(async () => {
for (const id of ids) {
const result = await userRepo.findOneBy({ id });
expect(result).toBeNull();
}
});
});
it('Unauthorized', async () => {
await request(app.getHttpServer() as Server)
.delete(`/users`)
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
describe('/users/:id (GET)', () => {
it('', async () => {
await request(app.getHttpServer() as Server)
.get(`/users/${ownerUser.id}`)
.set('Authorization', `Bearer ${accessToken}`)
.expect(200)
.expect(({ body }) => {
expect((body as UserDto).id).toEqual(ownerUser.id);
expect((body as UserDto).email).toEqual(ownerUser.email);
});
});
it('', async () => {
await request(app.getHttpServer() as Server)
.get(`/users/${ownerUser.id}`)
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
describe('/users/:id (DELETE)', () => {
it('positive', async () => {
return request(app.getHttpServer() as Server)
.delete(`/users/${ownerUser.id}`)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatusCode.OK)
.then(async () => {
const result = await userRepo.findOneBy({ id: ownerUser.id });
expect(result).toBeNull();
});
});
it('Unauthorization', async () => {
return request(app.getHttpServer() as Server)
.delete(`/users/${faker.number.int()}`)
.set('Authorization', `Bearer ${accessToken}`)
.expect(HttpStatusCode.UNAUTHORIZED);
});
it('Unauthorization', async () => {
return request(app.getHttpServer() as Server)
.delete(`/users/${ownerUser.id}`)
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
describe('/users/:id/role (PUT)', () => {
it('positive case', async () => {
const role = await roleRepo.save({
name: faker.string.sample(),
permissions: [],
});
await request(app.getHttpServer() as Server)
.put(`/users/${ownerUser.id}/role`)
.set('Authorization', `Bearer ${accessToken}`)
.send({ roleId: role.id })
.expect(HttpStatusCode.NO_CONTENT);
});
it('Unauthroized', async () => {
const role = await roleRepo.save({
name: faker.string.sample(),
permissions: [],
});
await request(app.getHttpServer() as Server)
.put(`/users/${ownerUser.id}/role`)
.send({ roleId: role.id })
.expect(HttpStatusCode.UNAUTHORIZED);
});
});
// it('', async () => {
// await request(app.getHttpServer())
// .put(`/users/password/reset/code`)
// .expect(204);
// });
// it('', async () => {
// await request(app.getHttpServer()).put(`/users/password/reset`).expect(204);
// });
// it('', async () => {
// await request(app.getHttpServer())
// .put(`/users/password/change`)
// .expect(204);
// });
});