Merged PR 158: [改善]ユニットテストの方針・実施方法を検討
## 概要 [Task1978: 検討し、Wikiにまとめる](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1978) - テスト毎にSQLiteのインメモリモードでDBを作成→データ構築→テスト→DBを破棄をすることで、Service~DBを含んだロジックに対するテストを行う - サンプルとしてTask一覧のテストを何件か実装 ## レビューポイント - 同様な形式で `features/xxx` 毎にテストを作成する時に問題となりそうなものはないか? ## レビュー対象外 - Wikiにまとめる方の作業は別途依頼予定 ## 動作確認状況 - ローカルでテストが動作することを確認
This commit is contained in:
parent
01a653015b
commit
5be4995d7d
1049
dictation_server/package-lock.json
generated
1049
dictation_server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -84,6 +84,7 @@
|
||||
"license-checker": "^25.0.1",
|
||||
"prettier": "^2.3.2",
|
||||
"source-map-support": "^0.5.20",
|
||||
"sqlite3": "^5.1.6",
|
||||
"supertest": "^6.1.3",
|
||||
"swagger-ui-express": "^4.5.0",
|
||||
"ts-jest": "28.0.1",
|
||||
|
||||
89
dictation_server/src/common/test/modules.ts
Normal file
89
dictation_server/src/common/test/modules.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
|
||||
import { TasksRepositoryModule } from '../../repositories/tasks/tasks.repository.module';
|
||||
import { AuthModule } from '../../features/auth/auth.module';
|
||||
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { AccountsModule } from '../../features/accounts/accounts.module';
|
||||
import { UsersModule } from '../../features/users/users.module';
|
||||
import { FilesModule } from '../../features/files/files.module';
|
||||
import { TasksModule } from '../../features/tasks/tasks.module';
|
||||
import { SendGridModule } from '../../features/../gateways/sendgrid/sendgrid.module';
|
||||
import { LicensesModule } from '../../features/licenses/licenses.module';
|
||||
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
|
||||
import { AudioFilesRepositoryModule } from '../../repositories/audio_files/audio_files.repository.module';
|
||||
import { AudioOptionItemsRepositoryModule } from '../../repositories/audio_option_items/audio_option_items.repository.module';
|
||||
import { CheckoutPermissionsRepositoryModule } from '../../repositories/checkout_permissions/checkout_permissions.repository.module';
|
||||
import { NotificationModule } from '../../features//notification/notification.module';
|
||||
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
import { AuthGuardsModule } from '../../common/guards/auth/authguards.module';
|
||||
import { SortCriteriaRepositoryModule } from '../../repositories/sort_criteria/sort_criteria.repository.module';
|
||||
import { AuthService } from '../../features/auth/auth.service';
|
||||
import { AccountsService } from '../../features/accounts/accounts.service';
|
||||
import { UsersService } from '../../features/users/users.service';
|
||||
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
||||
import { FilesService } from '../../features/files/files.service';
|
||||
import { LicensesService } from '../../features/licenses/licenses.service';
|
||||
import { TasksService } from '../../features/tasks/tasks.service';
|
||||
|
||||
export const makeTestingModule = async (
|
||||
datasource: DataSource,
|
||||
): Promise<TestingModule> => {
|
||||
try {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
isGlobal: true,
|
||||
}),
|
||||
AuthModule,
|
||||
AdB2cModule,
|
||||
AccountsModule,
|
||||
UsersModule,
|
||||
FilesModule,
|
||||
TasksModule,
|
||||
UsersModule,
|
||||
SendGridModule,
|
||||
LicensesModule,
|
||||
AccountsRepositoryModule,
|
||||
UsersRepositoryModule,
|
||||
LicensesRepositoryModule,
|
||||
AudioFilesRepositoryModule,
|
||||
AudioOptionItemsRepositoryModule,
|
||||
TasksRepositoryModule,
|
||||
CheckoutPermissionsRepositoryModule,
|
||||
UserGroupsRepositoryModule,
|
||||
UserGroupsRepositoryModule,
|
||||
NotificationModule,
|
||||
NotificationhubModule,
|
||||
BlobstorageModule,
|
||||
AuthGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
],
|
||||
providers: [
|
||||
AuthService,
|
||||
AccountsService,
|
||||
UsersService,
|
||||
NotificationhubService,
|
||||
FilesService,
|
||||
TasksService,
|
||||
LicensesService,
|
||||
],
|
||||
})
|
||||
.useMocker(async (token) => {
|
||||
switch (token) {
|
||||
case DataSource:
|
||||
return datasource;
|
||||
}
|
||||
})
|
||||
.compile();
|
||||
|
||||
return module;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
@ -6,8 +6,11 @@ import {
|
||||
} from './test/tasks.service.mock';
|
||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { TasksService } from './tasks.service';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { createAccount, createTask, createUser } from './test/utility';
|
||||
import { Adb2cTooManyRequestsError } from '../../gateways/adb2c/adb2c.service';
|
||||
import { UserNotFoundError } from '../../repositories/users/errors/types';
|
||||
import { makeTestingModule } from '../../common/test/modules';
|
||||
import { TasksNotFoundError } from '../../repositories/tasks/errors/types';
|
||||
|
||||
describe('TasksService', () => {
|
||||
@ -501,6 +504,174 @@ describe('TasksService', () => {
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
describe('DBテスト', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
logging: false,
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
|
||||
it('[Admin] Taskが0件であっても実行できる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { accountId } = await createAccount(source);
|
||||
const { externalId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId',
|
||||
'admin',
|
||||
);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const accessToken = { userId: externalId, role: 'admin', tier: 5 };
|
||||
const offset = 0;
|
||||
const limit = 20;
|
||||
const status = ['Uploaded,Backup'];
|
||||
const paramName = 'JOB_NUMBER';
|
||||
const direction = 'ASC';
|
||||
|
||||
const { tasks, total } = await service.getTasks(
|
||||
accessToken,
|
||||
offset,
|
||||
limit,
|
||||
status,
|
||||
paramName,
|
||||
direction,
|
||||
);
|
||||
expect(tasks).toEqual([]);
|
||||
expect(total).toEqual(0);
|
||||
});
|
||||
it('[Author] Authorは自分が作成者のTask一覧を取得できる', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId',
|
||||
'author',
|
||||
'MY_AUTHOR_ID',
|
||||
);
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
userId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Uploaded',
|
||||
);
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
userId,
|
||||
'MY_AUTHOR_ID',
|
||||
'',
|
||||
'01',
|
||||
'00000002',
|
||||
'Uploaded',
|
||||
);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const accessToken = { userId: 'userId', role: 'author', tier: 5 };
|
||||
const offset = 0;
|
||||
const limit = 20;
|
||||
const status = ['Uploaded', 'Backup'];
|
||||
const paramName = 'JOB_NUMBER';
|
||||
const direction = 'ASC';
|
||||
|
||||
const { tasks, total } = await service.getTasks(
|
||||
accessToken,
|
||||
offset,
|
||||
limit,
|
||||
status,
|
||||
paramName,
|
||||
direction,
|
||||
);
|
||||
|
||||
expect(total).toEqual(2);
|
||||
{
|
||||
const task = tasks[0];
|
||||
expect(task.jobNumber).toEqual('00000001');
|
||||
}
|
||||
{
|
||||
const task = tasks[1];
|
||||
expect(task.jobNumber).toEqual('00000002');
|
||||
}
|
||||
});
|
||||
it('[Author] Authorは同一アカウントであっても自分以外のAuhtorのTaskは取得できない', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const { accountId } = await createAccount(source);
|
||||
const { userId: userId_1 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId_1',
|
||||
'author',
|
||||
'AUTHOR_ID_1',
|
||||
);
|
||||
const { userId: userId_2 } = await createUser(
|
||||
source,
|
||||
accountId,
|
||||
'userId_2',
|
||||
'author',
|
||||
'AUTHOR_ID_2',
|
||||
);
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
userId_1,
|
||||
'AUTHOR_ID_1',
|
||||
'',
|
||||
'01',
|
||||
'00000001',
|
||||
'Uploaded',
|
||||
);
|
||||
|
||||
await createTask(
|
||||
source,
|
||||
accountId,
|
||||
userId_2,
|
||||
'AUTHOR_ID_2',
|
||||
'',
|
||||
'01',
|
||||
'00000002',
|
||||
'Uploaded',
|
||||
);
|
||||
|
||||
const service = module.get<TasksService>(TasksService);
|
||||
const accessToken = { userId: 'userId_1', role: 'author', tier: 5 };
|
||||
const offset = 0;
|
||||
const limit = 20;
|
||||
const status = ['Uploaded', 'Backup'];
|
||||
const paramName = 'JOB_NUMBER';
|
||||
const direction = 'ASC';
|
||||
|
||||
const { tasks, total } = await service.getTasks(
|
||||
accessToken,
|
||||
offset,
|
||||
limit,
|
||||
status,
|
||||
paramName,
|
||||
direction,
|
||||
);
|
||||
expect(total).toEqual(1);
|
||||
{
|
||||
const task = tasks[0];
|
||||
expect(task.jobNumber).toEqual('00000001');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('TasksService', () => {
|
||||
|
||||
89
dictation_server/src/features/tasks/test/utility.ts
Normal file
89
dictation_server/src/features/tasks/test/utility.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||
import { Task } from '../../../repositories/tasks/entity/task.entity';
|
||||
import { AudioFile } from '../../../repositories/audio_files/entity/audio_file.entity';
|
||||
|
||||
export const createAccount = async (
|
||||
datasource: DataSource,
|
||||
): Promise<{ accountId: number }> => {
|
||||
const { identifiers } = await datasource.getRepository(Account).insert({
|
||||
tier: 1,
|
||||
country: 'JP',
|
||||
delegation_permission: false,
|
||||
locked: false,
|
||||
company_name: 'test inc.',
|
||||
verified: true,
|
||||
deleted_at: '',
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const account = identifiers.pop() as Account;
|
||||
return { accountId: account.id };
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
datasource: DataSource,
|
||||
accountId: number,
|
||||
external_id: string,
|
||||
role: string,
|
||||
author_id?: string | undefined,
|
||||
): Promise<{ userId: number; externalId: string }> => {
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: accountId,
|
||||
external_id: external_id,
|
||||
role: role,
|
||||
accepted_terms_version: '1.0',
|
||||
author_id: author_id,
|
||||
email_verified: true,
|
||||
auto_renew: true,
|
||||
license_alert: true,
|
||||
notification: true,
|
||||
created_by: 'test_runner',
|
||||
created_at: new Date(),
|
||||
updated_by: 'updater',
|
||||
updated_at: new Date(),
|
||||
});
|
||||
const user = identifiers.pop() as User;
|
||||
return { userId: user.id, externalId: external_id };
|
||||
};
|
||||
|
||||
export const createTask = async (
|
||||
datasource: DataSource,
|
||||
account_id: number,
|
||||
owner_user_id: number,
|
||||
author_id: string,
|
||||
work_type_id: string,
|
||||
priority: string,
|
||||
jobNumber: string,
|
||||
status: string,
|
||||
): Promise<void> => {
|
||||
const { identifiers } = await datasource.getRepository(AudioFile).insert({
|
||||
account_id: account_id,
|
||||
owner_user_id: owner_user_id,
|
||||
url: '',
|
||||
file_name: 'x.zip',
|
||||
author_id: author_id,
|
||||
work_type_id: work_type_id,
|
||||
started_at: new Date(),
|
||||
duration: '100000',
|
||||
finished_at: new Date(),
|
||||
uploaded_at: new Date(),
|
||||
file_size: 10000,
|
||||
priority: priority,
|
||||
audio_format: 'audio_format',
|
||||
is_encrypted: true,
|
||||
});
|
||||
const audioFile = identifiers.pop() as AudioFile;
|
||||
await datasource.getRepository(Task).insert({
|
||||
job_number: jobNumber,
|
||||
account_id: account_id,
|
||||
is_job_number_enabled: true,
|
||||
audio_file_id: audioFile.id,
|
||||
status: status,
|
||||
priority: priority,
|
||||
created_at: new Date(),
|
||||
});
|
||||
};
|
||||
@ -40,7 +40,7 @@ export class Account {
|
||||
@Column({ nullable: true })
|
||||
secondary_admin_user_id?: number;
|
||||
|
||||
@Column('timestamp')
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
|
||||
@Column()
|
||||
|
||||
@ -22,7 +22,7 @@ export class LicenseOrder {
|
||||
@CreateDateColumn()
|
||||
ordered_at: Date;
|
||||
|
||||
@Column('timestamp', { nullable: true })
|
||||
@Column({ nullable: true })
|
||||
issued_at?: Date;
|
||||
|
||||
@Column()
|
||||
@ -31,7 +31,7 @@ export class LicenseOrder {
|
||||
@Column()
|
||||
status: string;
|
||||
|
||||
@Column('timestamp', { nullable: true })
|
||||
@Column({ nullable: true })
|
||||
canceled_at?: Date;
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ export class Task {
|
||||
started_at?: Date;
|
||||
@Column({ nullable: true })
|
||||
finished_at?: Date;
|
||||
@Column({ type: 'timestamp' })
|
||||
@Column({})
|
||||
created_at: Date;
|
||||
@OneToOne(() => AudioFile, (audiofile) => audiofile.task)
|
||||
@JoinColumn({ name: 'audio_file_id' })
|
||||
|
||||
@ -41,7 +41,7 @@ export class User {
|
||||
@Column()
|
||||
notification: boolean;
|
||||
|
||||
@Column('timestamp', { nullable: true })
|
||||
@Column({ nullable: true })
|
||||
deleted_at?: Date;
|
||||
|
||||
@Column()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user