Merged PR 707: 対応

## 概要
[Task3507: 対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3507)

アップロードとcheckout時にライセンスチェックを行う。ダウンロード時にはチェックを行わない。
- 影響範囲(他の機能にも影響があるか)
checkoutのとき、第五階層の場合にチェックが行われる。
外部連携APIが返却するパラメータが変わるため、OMDSさんに連携する

## レビューポイント
- 不要な個所まで削除していないか(アップロードの場合はチェックを残す)

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
maruyama.t 2024-01-26 11:07:56 +00:00
parent 794dae0c15
commit 986b710aaa
11 changed files with 307 additions and 525 deletions

View File

@ -219,9 +219,9 @@ export const PNS = {
};
/**
*
*
*/
export const USER_LICENSE_STATUS = {
export const USER_LICENSE_EXPIRY_STATUS = {
NORMAL: 'Normal',
NO_LICENSE: 'NoLicense',
ALERT: 'Alert',
@ -311,3 +311,13 @@ export const USER_AUDIO_FORMAT = 'DS2(QP)';
* @const {string[]}
*/
export const NODE_ENV_TEST = 'test';
/**
*
* @const {string[]}
*/
export const USER_LICENSE_STATUS = {
UNALLOCATED: 'unallocated',
ALLOCATED: 'allocated',
EXPIRED: 'expired',
} as const;

View File

@ -235,67 +235,6 @@ describe('publishUploadSas', () => {
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('アップロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishUploadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishUploadSas(
makeContext('trackingId', 'requestId'),
externalId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
describe('タスク作成から自動ルーティング(DB使用)', () => {
@ -1097,76 +1036,6 @@ describe('音声ファイルダウンロードURL取得', () => {
),
).toEqual(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 本日の日付を作成
let today = new Date();
today.setDate(today.getDate());
today = new DateWithZeroTime(today);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
today,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = true;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
await service.publishAudioFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).toEqual(`${url}?sas-token`);
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -1396,133 +1265,6 @@ describe('音声ファイルダウンロードURL取得', () => {
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishAudioFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishAudioFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
describe('テンプレートファイルダウンロードURL取得', () => {
@ -1596,70 +1338,7 @@ describe('テンプレートファイルダウンロードURL取得', () => {
);
expect(resultUrl).toBe(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
// 本日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate());
yesterday = new DateWithZeroTime(yesterday);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
TASK_STATUS.IN_PROGRESS,
userId,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = true;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
const resultUrl = await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
);
expect(resultUrl).toBe(`${url}?sas-token`);
});
it('タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -1849,135 +1528,6 @@ describe('テンプレートファイルダウンロードURL取得', () => {
}
}
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
TASK_STATUS.IN_PROGRESS,
undefined,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
try {
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010812'));
} else {
fail();
}
}
});
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'typist-user-external-id',
role: USER_ROLES.TYPIST,
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
TASK_STATUS.IN_PROGRESS,
undefined,
'AUTHOR_ID',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
try {
await service.publishTemplateFileDownloadSas(
makeContext('trackingId', 'requestId'),
externalId,
audioFileId,
),
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
} else {
fail();
}
}
});
});
describe('publishTemplateFileUploadSas', () => {

View File

@ -8,6 +8,7 @@ import {
OPTION_ITEM_NUM,
TASK_STATUS,
TIERS,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants/index';
import { User } from '../../repositories/users/entity/user.entity';
@ -308,10 +309,10 @@ export class FilesService {
context,
user.id,
);
if (state === 'expired') {
if (state === USER_LICENSE_STATUS.EXPIRED) {
throw new LicenseExpiredError('license is expired.');
}
if (state === 'inallocated') {
if (state === USER_LICENSE_STATUS.UNALLOCATED) {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
@ -392,20 +393,6 @@ export class FilesService {
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState(
context,
user.id,
);
if (state === 'expired') {
throw new LicenseExpiredError('license is expired.');
}
if (state === 'inallocated') {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
accountId = user.account.id;
userId = user.id;
country = user.account.country;
@ -422,16 +409,6 @@ export class FilesService {
}`,
);
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
@ -571,20 +548,6 @@ export class FilesService {
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState(
context,
user.id,
);
if (state === 'expired') {
throw new LicenseExpiredError('license is expired.');
}
if (state === 'inallocated') {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
accountId = user.account_id;
userId = user.id;
country = user.account.country;
@ -596,16 +559,6 @@ export class FilesService {
}`,
);
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),

View File

@ -8,6 +8,7 @@ import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_
import { NotificationhubModule } from '../../gateways/notificationhub/notificationhub.module';
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
@Module({
imports: [
@ -18,6 +19,7 @@ import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.r
AdB2cModule,
NotificationhubModule,
SendGridModule,
LicensesRepositoryModule,
],
providers: [TasksService],
controllers: [TasksController],

View File

@ -25,7 +25,13 @@ import {
makeTestSimpleAccount,
makeTestUser,
} from '../../common/test/utility';
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
import {
ADMIN_ROLES,
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
TASK_STATUS,
USER_ROLES,
} from '../../constants';
import { makeTestingModule } from '../../common/test/modules';
import { createSortCriteria } from '../users/test/utility';
import { createWorktype } from '../accounts/test/utility';
@ -38,6 +44,9 @@ import { NotificationhubService } from '../../gateways/notificationhub/notificat
import { Roles } from '../../common/types/role';
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
import { truncateAllTable } from '../../common/test/init';
import { makeDefaultLicensesRepositoryMockValue } from '../accounts/test/accounts.service.mock';
import { DateWithZeroTime } from '../licenses/types/types';
import { createLicense } from '../licenses/test/utility';
describe('TasksService', () => {
it('タスク一覧を取得できるadmin', async () => {
@ -48,12 +57,15 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
usersRepositoryMockValue,
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -122,6 +134,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
usersRepositoryMockValue.findUserByExternalId = new Error('DB failed');
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
@ -129,6 +143,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -164,6 +179,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
tasksRepositoryMockValue.getTasksFromAccountId = new Error('DB failed');
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
@ -171,6 +188,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -252,12 +270,15 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
usersRepositoryMockValue,
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
const offset = 0;
@ -292,6 +313,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
return;
}
@ -302,6 +325,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -376,6 +400,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
tasksRepositoryMockValue.getTasksFromAuthorIdAndAccountId = new Error(
'DB failed',
);
@ -385,6 +411,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -420,6 +447,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
if (usersRepositoryMockValue.findUserByExternalId instanceof Error) {
return;
}
@ -431,6 +460,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -508,12 +538,15 @@ describe('TasksService', () => {
tasksRepositoryMockValue.getTasksFromTypistRelations = new Error(
'DB failed',
);
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
usersRepositoryMockValue,
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -549,6 +582,8 @@ describe('TasksService', () => {
const adb2cServiceMockValue = makeDefaultAdb2cServiceMockValue();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const licensesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
adb2cServiceMockValue.getUsers = new Adb2cTooManyRequestsError();
const service = await makeTasksServiceMock(
tasksRepositoryMockValue,
@ -556,6 +591,7 @@ describe('TasksService', () => {
userGroupsRepositoryMockValue,
adb2cServiceMockValue,
notificationhubServiceMockValue,
licensesRepositoryMockValue,
);
const userId = 'userId';
@ -1632,7 +1668,75 @@ describe('checkout', () => {
user_group_id: null,
});
});
it('第五階層のアカウントの場合、有効なライセンスが割当されている場合チェックアウトできる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウントを作成
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
const { id: typistUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
// 本日の日付を作成
const today = new Date();
// 有効なライセンスを作成して紐づける
await createLicense(
source,
1,
today,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
typistUserId,
null,
null,
null,
);
const { taskId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Pending',
);
await createCheckoutPermissions(source, taskId, typistUserId);
const service = module.get<TasksService>(TasksService);
const initTask = await getTask(source, taskId);
await service.checkout(
makeContext('trackingId', 'requestId'),
1,
['typist'],
'typist-user-external-id',
);
const resultTask = await getTask(source, taskId);
const permisions = await getCheckoutPermissions(source, taskId);
expect(resultTask?.status).toEqual('InProgress');
expect(resultTask?.typist_user_id).toEqual(typistUserId);
//タスクの元々のステータスがPending,Inprogressの場合、文字起こし開始時刻は更新されない
expect(resultTask?.started_at).toEqual(initTask?.started_at);
expect(permisions.length).toEqual(1);
expect(permisions[0]).toEqual({
id: 2,
task_id: 1,
user_id: 1,
user_group_id: null,
});
});
it('ユーザーのRoleがTypistで、対象のタスクのStatus[Uploaded,Inprogress,Pending]以外の時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -1678,7 +1782,116 @@ describe('checkout', () => {
}
}
});
it('第五階層のアカウントの場合、ライセンスが未割当の場合チェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウントを作成
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Backup',
);
const service = module.get<TasksService>(TasksService);
try {
await service.checkout(
makeContext('trackingId', 'requestId'),
1,
['typist'],
'typist-user-external-id',
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010812'));
} else {
fail();
}
}
});
it('第五階層のアカウントの場合、ライセンスが有効期限切れの場合チェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウントを作成
const { id: accountId } = await makeTestSimpleAccount(source, { tier: 5 });
const { id: typistUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
typistUserId,
null,
null,
null,
);
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Backup',
);
const service = module.get<TasksService>(TasksService);
try {
await service.checkout(
makeContext('trackingId', 'requestId'),
1,
['typist'],
'typist-user-external-id',
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010805'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、チェックアウト権限が存在しない時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);

View File

@ -9,7 +9,13 @@ import {
SortDirection,
TaskListSortableAttribute,
} from '../../common/types/sort';
import { ADMIN_ROLES, TASK_STATUS, USER_ROLES } from '../../constants';
import {
ADMIN_ROLES,
TASK_STATUS,
TIERS,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants';
import {
AdB2cService,
Adb2cTooManyRequestsError,
@ -36,6 +42,12 @@ import { User } from '../../repositories/users/entity/user.entity';
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
import { getUserNameAndMailAddress } from '../../gateways/adb2c/utils/utils';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
import {
LicenseExpiredError,
LicenseNotAllocatedError,
} from '../../repositories/licenses/errors/types';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
@Injectable()
export class TasksService {
@ -48,6 +60,7 @@ export class TasksService {
private readonly adB2cService: AdB2cService,
private readonly sendgridService: SendGridService,
private readonly notificationhubService: NotificationhubService,
private readonly licensesRepository: LicensesRepositoryService,
) {}
async getTasks(
@ -276,9 +289,26 @@ export class TasksService {
} | params: { audioFileId: ${audioFileId}, roles: ${roles}, externalId: ${externalId} };`,
);
const { id, account_id, author_id } =
const { id, account_id, author_id, account } =
await this.usersRepository.findUserByExternalId(context, externalId);
if (!account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (account.tier === TIERS.TIER5) {
// ライセンスが有効でない場合、エラー
const { state } = await this.licensesRepository.getLicenseState(
context,
id,
);
if (state === USER_LICENSE_STATUS.EXPIRED) {
throw new LicenseExpiredError('license is expired.');
}
if (state === USER_LICENSE_STATUS.UNALLOCATED) {
throw new LicenseNotAllocatedError('license is not allocated.');
}
}
if (roles.includes(USER_ROLES.AUTHOR)) {
// API実行者がAuthorで、AuthorIDが存在しないことは想定外のため、エラーとする
if (!author_id) {
@ -308,6 +338,16 @@ export class TasksService {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
case CheckoutPermissionNotFoundError:
case TaskAuthorIdNotMatchError:
case InvalidRoleError:

View File

@ -17,6 +17,11 @@ import { NotificationhubService } from '../../../gateways/notificationhub/notifi
import { UserGroupsRepositoryService } from '../../../repositories/user_groups/user_groups.repository.service';
import { AccountsRepositoryService } from '../../../repositories/accounts/accounts.repository.service';
import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
import {
LicensesRepositoryMockValue,
makeLicensesRepositoryMock,
} from '../../accounts/test/accounts.service.mock';
import { LicensesRepositoryService } from '../../../repositories/licenses/licenses.repository.service';
export type TasksRepositoryMockValue = {
getTasksFromAccountId:
@ -65,6 +70,7 @@ export const makeTasksServiceMock = async (
userGroupsRepositoryMockValue: UserGroupsRepositoryMockValue,
adB2CServiceMockValue: AdB2CServiceMockValue,
notificationhubServiceMockValue: NotificationhubServiceMockValue,
licensesRepositoryMockValue: LicensesRepositoryMockValue,
): Promise<{
tasksService: TasksService;
taskRepoService: TasksRepositoryService;
@ -92,6 +98,8 @@ export const makeTasksServiceMock = async (
// メール送信でしか利用しておらず、テストする必要がないが、依存関係解決のため空オブジェクトを定義しておく。
case SendGridService:
return {};
case LicensesRepositoryService:
return makeLicensesRepositoryMock(licensesRepositoryMockValue);
}
})
.compile();

View File

@ -9,7 +9,7 @@ import {
} from 'class-validator';
import {
TASK_LIST_SORTABLE_ATTRIBUTES,
USER_LICENSE_STATUS,
USER_LICENSE_EXPIRY_STATUS,
} from '../../../constants';
import { USER_ROLES } from '../../../constants';
import {
@ -67,9 +67,9 @@ export class User {
remaining?: number;
@ApiProperty({
description: `${Object.values(USER_LICENSE_STATUS).join('/')}`,
description: `${Object.values(USER_LICENSE_EXPIRY_STATUS).join('/')}`,
})
@IsIn(Object.values(USER_LICENSE_STATUS), {
@IsIn(Object.values(USER_LICENSE_EXPIRY_STATUS), {
message: 'invalid license status',
})
licenseStatus: string;

View File

@ -22,7 +22,7 @@ import {
LICENSE_EXPIRATION_THRESHOLD_DAYS,
LICENSE_TYPE,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_LICENSE_EXPIRY_STATUS,
USER_ROLES,
} from '../../constants';
import { makeTestingModule } from '../../common/test/modules';
@ -1479,7 +1479,7 @@ describe('UsersService.getUsers', () => {
prompt: false,
expiration: undefined,
remaining: undefined,
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
},
{
id: typistUserId,
@ -1495,7 +1495,7 @@ describe('UsersService.getUsers', () => {
prompt: false,
expiration: undefined,
remaining: undefined,
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
},
{
id: noneUserId,
@ -1511,7 +1511,7 @@ describe('UsersService.getUsers', () => {
prompt: false,
expiration: undefined,
remaining: undefined,
licenseStatus: USER_LICENSE_STATUS.NO_LICENSE,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NO_LICENSE,
},
];
@ -1591,7 +1591,7 @@ describe('UsersService.getUsers', () => {
date1.getMonth() + 1
}/${date1.getDate()}`,
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS + 1,
licenseStatus: USER_LICENSE_STATUS.NORMAL,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.NORMAL,
},
{
id: user2,
@ -1609,7 +1609,7 @@ describe('UsersService.getUsers', () => {
date2.getMonth() + 1
}/${date2.getDate()}`,
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS,
licenseStatus: USER_LICENSE_STATUS.RENEW,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.RENEW,
},
{
id: user3,
@ -1627,7 +1627,7 @@ describe('UsersService.getUsers', () => {
date3.getMonth() + 1
}/${date3.getDate()}`,
remaining: LICENSE_EXPIRATION_THRESHOLD_DAYS - 1,
licenseStatus: USER_LICENSE_STATUS.ALERT,
licenseStatus: USER_LICENSE_EXPIRY_STATUS.ALERT,
},
];

View File

@ -37,7 +37,7 @@ import {
MANUAL_RECOVERY_REQUIRED,
OPTION_ITEM_VALUE_TYPE_NUMBER,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_LICENSE_EXPIRY_STATUS,
USER_ROLES,
} from '../../constants';
import { DateWithZeroTime } from '../licenses/types/types';
@ -617,7 +617,7 @@ export class UsersService {
throw new Error('mail not found.');
}
let status = USER_LICENSE_STATUS.NORMAL;
let status = USER_LICENSE_EXPIRY_STATUS.NORMAL;
// ライセンスの有効期限と残日数は、ライセンスが存在する場合のみ算出する
// ライセンスが存在しない場合は、undefinedのままとする
@ -648,11 +648,11 @@ export class UsersService {
remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS
) {
status = dbUser.auto_renew
? USER_LICENSE_STATUS.RENEW
: USER_LICENSE_STATUS.ALERT;
? USER_LICENSE_EXPIRY_STATUS.RENEW
: USER_LICENSE_EXPIRY_STATUS.ALERT;
}
} else {
status = USER_LICENSE_STATUS.NO_LICENSE;
status = USER_LICENSE_EXPIRY_STATUS.NO_LICENSE;
}
return {

View File

@ -15,6 +15,7 @@ import {
NODE_ENV_TEST,
SWITCH_FROM_TYPE,
TIERS,
USER_LICENSE_STATUS,
} from '../../constants';
import {
PoNumberAlreadyExistError,
@ -806,12 +807,17 @@ export class LicensesRepositoryService {
*
* @param userId ID
* @error { Error } DBアクセス失敗時の例外
* @returns Promise<{ state: 'allocated' | 'inallocated' | 'expired' }>
* @returns Promise<{ state: 'allocated' | 'unallocated' | 'expired' }>
*/
async getLicenseState(
context: Context,
userId: number,
): Promise<{ state: 'allocated' | 'inallocated' | 'expired' }> {
): Promise<{
state:
| typeof USER_LICENSE_STATUS.ALLOCATED
| typeof USER_LICENSE_STATUS.UNALLOCATED
| typeof USER_LICENSE_STATUS.EXPIRED;
}> {
const allocatedLicense = await this.dataSource
.getRepository(License)
.findOne({
@ -824,7 +830,7 @@ export class LicensesRepositoryService {
// ライセンスが割り当てられていない場合は未割当状態
if (allocatedLicense == null) {
return { state: 'inallocated' };
return { state: USER_LICENSE_STATUS.UNALLOCATED };
}
// ライセンスの有効期限が過ぎている場合は期限切れ状態
@ -833,9 +839,9 @@ export class LicensesRepositoryService {
allocatedLicense.expiry_date &&
allocatedLicense.expiry_date < currentDate
) {
return { state: 'expired' };
return { state: USER_LICENSE_STATUS.EXPIRED };
}
return { state: 'allocated' };
return { state: USER_LICENSE_STATUS.ALLOCATED };
}
}