Merged PR 573: 音声ファイルアップロードAPI修正
## 概要 [Task3069: 音声ファイルアップロードAPI修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3069) [Task3070: 音声ファイルダウンロードAPI修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3070) [Task3071: テンプレートファイルダウンロードAPI修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3071) 修正内容かぶるため、3本まとめてレビューお願いします。 - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 音声ファイルアップロードAPIを修正 ・第五階層の場合のみチェックを追加 ・アカウントがロックされている場合、エラー ・ユーザーにライセンスが未割当の場合、エラー ・ユーザーに紐づいたライセンスが有効期限切れの場合、エラー - 音声ファイルダウンロード、テンプレートファイルダウンロードAPIを修正 ・第五階層の場合のみチェックを追加 ・ユーザーにライセンスが未割当の場合、エラー ・ユーザーに紐づいたライセンスが有効期限切れの場合、エラー - 外部連携アプリ側の挙動の変化については考慮しない。 - ログ強化は別タスクで対応中。 - 影響範囲(他の機能にも影響があるか) ファイル操作以外は影響なし。 旧式のユニットテストを修正。 ## レビューポイント - 音声ファイルアップロードのユニットテストを最新の状態にしたが、不足していないか。 ~~- users.repositoryにユーザに紐づくライセンスが現在有効かどうかの判定を入れ込み、共通的に呼び出すようにしたが使いづらくないか(ライセンスが紐づいていない場合と有効期限切れの場合エラーとし、それ以外はtrueが帰る点について)~~ ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
40162ef3af
commit
eb3c7e55bd
@ -2077,6 +2077,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "不正なパラメータ",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"401": {
|
"401": {
|
||||||
"description": "認証エラー",
|
"description": "認証エラー",
|
||||||
"content": {
|
"content": {
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export const ErrorCodes = [
|
|||||||
'E010501', // アカウント不在エラー
|
'E010501', // アカウント不在エラー
|
||||||
'E010502', // アカウント情報変更不可エラー
|
'E010502', // アカウント情報変更不可エラー
|
||||||
'E010503', // 代行操作不許可エラー
|
'E010503', // 代行操作不許可エラー
|
||||||
|
'E010504', // アカウントロックエラー
|
||||||
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
||||||
'E010602', // タスク変更権限不足エラー
|
'E010602', // タスク変更権限不足エラー
|
||||||
'E010603', // タスク不在エラー
|
'E010603', // タスク不在エラー
|
||||||
@ -54,6 +55,7 @@ export const ErrorCodes = [
|
|||||||
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
|
||||||
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
|
||||||
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||||
|
'E010812', // ライセンス未割当エラー
|
||||||
'E010908', // タイピストグループ不在エラー
|
'E010908', // タイピストグループ不在エラー
|
||||||
'E011001', // ワークタイプ重複エラー
|
'E011001', // ワークタイプ重複エラー
|
||||||
'E011002', // ワークタイプ登録上限超過エラー
|
'E011002', // ワークタイプ登録上限超過エラー
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export const errors: Errors = {
|
|||||||
E010501: 'Account not Found Error.',
|
E010501: 'Account not Found Error.',
|
||||||
E010502: 'Account information cannot be changed Error.',
|
E010502: 'Account information cannot be changed Error.',
|
||||||
E010503: 'Delegation not allowed Error.',
|
E010503: 'Delegation not allowed Error.',
|
||||||
|
E010504: 'Account is locked Error.',
|
||||||
E010601: 'Task is not Editable Error',
|
E010601: 'Task is not Editable Error',
|
||||||
E010602: 'No task edit permissions Error',
|
E010602: 'No task edit permissions Error',
|
||||||
E010603: 'Task not found Error.',
|
E010603: 'Task not found Error.',
|
||||||
@ -43,6 +44,7 @@ export const errors: Errors = {
|
|||||||
E010809: 'Already license order status changed Error',
|
E010809: 'Already license order status changed Error',
|
||||||
E010810: 'Cancellation period expired error',
|
E010810: 'Cancellation period expired error',
|
||||||
E010811: 'Already license allocated Error',
|
E010811: 'Already license allocated Error',
|
||||||
|
E010812: 'License not allocated Error',
|
||||||
E010908: 'Typist Group not exist Error',
|
E010908: 'Typist Group not exist Error',
|
||||||
E011001: 'This WorkTypeID already used Error',
|
E011001: 'This WorkTypeID already used Error',
|
||||||
E011002: 'WorkTypeID create limit exceeded Error',
|
E011002: 'WorkTypeID create limit exceeded Error',
|
||||||
|
|||||||
@ -185,6 +185,11 @@ export const overrideBlobstorageService = <TService>(
|
|||||||
accountId: number,
|
accountId: number,
|
||||||
country: string,
|
country: string,
|
||||||
) => Promise<boolean>;
|
) => Promise<boolean>;
|
||||||
|
publishUploadSas?: (
|
||||||
|
context: Context,
|
||||||
|
accountId: number,
|
||||||
|
country: string,
|
||||||
|
) => Promise<string>;
|
||||||
publishTemplateUploadSas?: (
|
publishTemplateUploadSas?: (
|
||||||
context: Context,
|
context: Context,
|
||||||
accountId: number,
|
accountId: number,
|
||||||
@ -212,6 +217,12 @@ export const overrideBlobstorageService = <TService>(
|
|||||||
writable: true,
|
writable: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (overrides.publishUploadSas) {
|
||||||
|
Object.defineProperty(obj, obj.publishUploadSas.name, {
|
||||||
|
value: overrides.publishUploadSas,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (overrides.publishTemplateUploadSas) {
|
if (overrides.publishTemplateUploadSas) {
|
||||||
Object.defineProperty(obj, obj.publishTemplateUploadSas.name, {
|
Object.defineProperty(obj, obj.publishTemplateUploadSas.name, {
|
||||||
value: overrides.publishTemplateUploadSas,
|
value: overrides.publishTemplateUploadSas,
|
||||||
|
|||||||
@ -39,11 +39,15 @@ import { retrieveAuthorizationToken } from '../../common/http/helper';
|
|||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { makeContext } from '../../common/log';
|
import { makeContext } from '../../common/log';
|
||||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||||
|
import { LicensesService } from '../licenses/licenses.service';
|
||||||
|
|
||||||
@ApiTags('files')
|
@ApiTags('files')
|
||||||
@Controller('files')
|
@Controller('files')
|
||||||
export class FilesController {
|
export class FilesController {
|
||||||
constructor(private readonly filesService: FilesService) {}
|
constructor(
|
||||||
|
private readonly filesService: FilesService,
|
||||||
|
private readonly licensesService: LicensesService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: HttpStatus.OK,
|
status: HttpStatus.OK,
|
||||||
@ -140,6 +144,11 @@ export class FilesController {
|
|||||||
type: AudioUploadLocationResponse,
|
type: AudioUploadLocationResponse,
|
||||||
description: '成功時のレスポンス',
|
description: '成功時のレスポンス',
|
||||||
})
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: HttpStatus.BAD_REQUEST,
|
||||||
|
description: '不正なパラメータ',
|
||||||
|
type: ErrorResponse,
|
||||||
|
})
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
status: HttpStatus.UNAUTHORIZED,
|
status: HttpStatus.UNAUTHORIZED,
|
||||||
description: '認証エラー',
|
description: '認証エラー',
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import { HttpException, HttpStatus } from '@nestjs/common';
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||||
import {
|
import { makeBlobstorageServiceMockValue } from './test/files.service.mock';
|
||||||
makeBlobstorageServiceMockValue,
|
|
||||||
makeDefaultTasksRepositoryMockValue,
|
|
||||||
makeDefaultUsersRepositoryMockValue,
|
|
||||||
makeFilesServiceMock,
|
|
||||||
} from './test/files.service.mock';
|
|
||||||
import { DataSource } from 'typeorm';
|
import { DataSource } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
|
createLicense,
|
||||||
createTask,
|
createTask,
|
||||||
createUserGroupAndMember,
|
createUserGroupAndMember,
|
||||||
getTaskFromJobNumber,
|
getTaskFromJobNumber,
|
||||||
@ -16,6 +12,7 @@ import {
|
|||||||
import { FilesService } from './files.service';
|
import { FilesService } from './files.service';
|
||||||
import { makeContext } from '../../common/log';
|
import { makeContext } from '../../common/log';
|
||||||
import {
|
import {
|
||||||
|
makeHierarchicalAccounts,
|
||||||
makeTestAccount,
|
makeTestAccount,
|
||||||
makeTestSimpleAccount,
|
makeTestSimpleAccount,
|
||||||
makeTestUser,
|
makeTestUser,
|
||||||
@ -37,89 +34,243 @@ import { TasksRepositoryService } from '../../repositories/tasks/tasks.repositor
|
|||||||
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
||||||
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
|
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
|
||||||
import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
|
import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
|
||||||
|
import { DateWithZeroTime } from '../licenses/types/types';
|
||||||
|
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
|
||||||
|
|
||||||
describe('音声ファイルアップロードURL取得', () => {
|
describe('publishUploadSas', () => {
|
||||||
it('アップロードSASトークンが乗っているURLを返却する', async () => {
|
let source: DataSource | null = null;
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
beforeEach(async () => {
|
||||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
source = new DataSource({
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
type: 'sqlite',
|
||||||
const service = await makeFilesServiceMock(
|
database: ':memory:',
|
||||||
blobParam,
|
logging: false,
|
||||||
userRepoParam,
|
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||||
taskRepoParam,
|
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||||
);
|
});
|
||||||
|
return source.initialize();
|
||||||
expect(
|
|
||||||
await service.publishUploadSas(
|
|
||||||
makeContext('trackingId'),
|
|
||||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
|
||||||
),
|
|
||||||
).toEqual('https://blob-storage?sas-token');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('アカウント専用コンテナが無い場合でも、コンテナ作成しURLを返却する', async () => {
|
afterEach(async () => {
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
if (!source) return;
|
||||||
const userRepoParam = makeDefaultUsersRepositoryMockValue();
|
await source.destroy();
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
source = null;
|
||||||
|
|
||||||
blobParam.containerExists = false;
|
|
||||||
|
|
||||||
const service = await makeFilesServiceMock(
|
|
||||||
blobParam,
|
|
||||||
userRepoParam,
|
|
||||||
taskRepoParam,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await service.publishUploadSas(
|
|
||||||
makeContext('trackingId'),
|
|
||||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
|
||||||
),
|
|
||||||
).toEqual('https://blob-storage?sas-token');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ユーザー情報の取得に失敗した場合、例外エラーを返却する', async () => {
|
it('音声アップロードSASトークンが乗っているURLを取得できる', async () => {
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
if (!source) fail();
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { account: account } = await makeTestAccount(source, { tier: 5 });
|
||||||
|
const { external_id: externalId, id: userId } = await makeTestUser(source, {
|
||||||
|
account_id: 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,
|
||||||
|
account.id,
|
||||||
|
LICENSE_TYPE.NORMAL,
|
||||||
|
LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||||
|
userId,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const context = makeContext(externalId);
|
||||||
|
const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/${userId}`;
|
||||||
|
|
||||||
const service = await makeFilesServiceMock(
|
//SASトークンを返却する
|
||||||
blobParam,
|
overrideBlobstorageService(service, {
|
||||||
{
|
containerExists: async () => true,
|
||||||
findUserByExternalId: new Error(''),
|
publishUploadSas: async () => `${baseUrl}?sas-token`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = await service.publishUploadSas(context, externalId);
|
||||||
|
|
||||||
|
expect(url).toBe(`${baseUrl}?sas-token`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
// 第四階層のアカウント作成
|
||||||
|
const { admin } = await makeTestAccount(source, { tier: 4 });
|
||||||
|
|
||||||
|
const context = makeContext(admin.external_id);
|
||||||
|
|
||||||
|
//Blobコンテナ存在チェックに失敗するようにする
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
containerExists: async () => false,
|
||||||
|
publishUploadSas: async () => '',
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.publishUploadSas(context, admin.external_id);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('SASトークンの取得に失敗した場合はエラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
// 第四階層のアカウント作成
|
||||||
|
const { admin } = await makeTestAccount(source, { tier: 4 });
|
||||||
|
|
||||||
|
const context = makeContext(admin.external_id);
|
||||||
|
|
||||||
|
//BlobのSASトークン生成に失敗するようにする
|
||||||
|
overrideBlobstorageService(service, {
|
||||||
|
containerExists: async () => true,
|
||||||
|
publishUploadSas: async () => {
|
||||||
|
throw new Error('blob failed');
|
||||||
},
|
},
|
||||||
taskRepoParam,
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.publishUploadSas(context, admin.external_id);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('アカウントがロックされている場合、エラーとなる', async () => {
|
||||||
|
if (!source) fail();
|
||||||
|
const module = await makeTestingModule(source);
|
||||||
|
if (!module) fail();
|
||||||
|
const service = module.get<FilesService>(FilesService);
|
||||||
|
// 第五階層のアカウント作成
|
||||||
|
const { admin } = await makeTestAccount(source, { tier: 5, locked: true });
|
||||||
|
|
||||||
|
const context = makeContext(admin.external_id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await service.publishUploadSas(context, admin.external_id);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof HttpException) {
|
||||||
|
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
|
||||||
|
expect(e.getResponse()).toEqual(makeErrorResponse('E010504'));
|
||||||
|
} 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: 'author-user-external-id',
|
||||||
|
role: 'author',
|
||||||
|
author_id: 'AUTHOR_ID',
|
||||||
|
});
|
||||||
|
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(
|
await expect(
|
||||||
service.publishUploadSas(
|
service.publishUploadSas(makeContext('trackingId'), externalId),
|
||||||
makeContext('trackingId'),
|
|
||||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
|
||||||
),
|
|
||||||
).rejects.toEqual(
|
).rejects.toEqual(
|
||||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('アップロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
|
||||||
it('コンテナ作成に失敗した場合、例外エラーを返却する', async () => {
|
if (!source) fail();
|
||||||
const blobParam = makeBlobstorageServiceMockValue();
|
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
|
||||||
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
|
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
|
||||||
|
source,
|
||||||
const service = await makeFilesServiceMock(
|
|
||||||
blobParam,
|
|
||||||
{
|
|
||||||
findUserByExternalId: new Error(''),
|
|
||||||
},
|
|
||||||
taskRepoParam,
|
|
||||||
);
|
);
|
||||||
blobParam.publishUploadSas = new Error('Azure service down');
|
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(
|
await expect(
|
||||||
service.publishUploadSas(
|
service.publishUploadSas(makeContext('trackingId'), externalId),
|
||||||
makeContext('trackingId'),
|
|
||||||
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
|
||||||
),
|
|
||||||
).rejects.toEqual(
|
).rejects.toEqual(
|
||||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -879,7 +1030,76 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
),
|
),
|
||||||
).toEqual(`${url}?sas-token`);
|
).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'),
|
||||||
|
externalId,
|
||||||
|
audioFileId,
|
||||||
|
),
|
||||||
|
).toEqual(`${url}?sas-token`);
|
||||||
|
});
|
||||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||||
if (!source) fail();
|
if (!source) fail();
|
||||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
@ -1109,6 +1329,133 @@ describe('音声ファイルダウンロードURL取得', () => {
|
|||||||
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
|
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'),
|
||||||
|
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'),
|
||||||
|
externalId,
|
||||||
|
audioFileId,
|
||||||
|
),
|
||||||
|
).rejects.toEqual(
|
||||||
|
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('テンプレートファイルダウンロードURL取得', () => {
|
describe('テンプレートファイルダウンロードURL取得', () => {
|
||||||
@ -1175,7 +1522,76 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
),
|
),
|
||||||
).toEqual(`${url}?sas-token`);
|
).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 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',
|
||||||
|
'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.publishTemplateFileDownloadSas(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
externalId,
|
||||||
|
audioFileId,
|
||||||
|
),
|
||||||
|
).toEqual(`${url}?sas-token`);
|
||||||
|
});
|
||||||
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
|
||||||
if (!source) fail();
|
if (!source) fail();
|
||||||
const { id: accountId } = await makeTestSimpleAccount(source);
|
const { id: accountId } = await makeTestSimpleAccount(source);
|
||||||
@ -1394,6 +1810,133 @@ describe('テンプレートファイルダウンロードURL取得', () => {
|
|||||||
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
|
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.publishTemplateFileDownloadSas(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
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.publishTemplateFileDownloadSas(
|
||||||
|
makeContext('trackingId'),
|
||||||
|
externalId,
|
||||||
|
audioFileId,
|
||||||
|
),
|
||||||
|
).rejects.toEqual(
|
||||||
|
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('publishTemplateFileUploadSas', () => {
|
describe('publishTemplateFileUploadSas', () => {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { AudioOptionItem, AudioUploadFinishedResponse } from './types/types';
|
|||||||
import {
|
import {
|
||||||
OPTION_ITEM_NUM,
|
OPTION_ITEM_NUM,
|
||||||
TASK_STATUS,
|
TASK_STATUS,
|
||||||
|
TIERS,
|
||||||
USER_ROLES,
|
USER_ROLES,
|
||||||
} from '../../constants/index';
|
} from '../../constants/index';
|
||||||
import { User } from '../../repositories/users/entity/user.entity';
|
import { User } from '../../repositories/users/entity/user.entity';
|
||||||
@ -23,11 +24,19 @@ import {
|
|||||||
} from '../../repositories/tasks/errors/types';
|
} from '../../repositories/tasks/errors/types';
|
||||||
import { Context } from '../../common/log';
|
import { Context } from '../../common/log';
|
||||||
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
|
||||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
import {
|
||||||
|
AccountNotFoundError,
|
||||||
|
AccountLockedError,
|
||||||
|
} from '../../repositories/accounts/errors/types';
|
||||||
import { Task } from '../../repositories/tasks/entity/task.entity';
|
import { Task } from '../../repositories/tasks/entity/task.entity';
|
||||||
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
|
||||||
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
|
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
|
||||||
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
|
||||||
|
import {
|
||||||
|
LicenseExpiredError,
|
||||||
|
LicenseNotAllocatedError,
|
||||||
|
} from '../../repositories/licenses/errors/types';
|
||||||
|
import { DateWithZeroTime } from '../licenses/types/types';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilesService {
|
export class FilesService {
|
||||||
@ -269,7 +278,20 @@ export class FilesService {
|
|||||||
}
|
}
|
||||||
const accountId = user.account_id;
|
const accountId = user.account_id;
|
||||||
const country = user.account.country;
|
const country = user.account.country;
|
||||||
|
// 第五階層のみチェック
|
||||||
|
if (user.account.tier === TIERS.TIER5) {
|
||||||
|
// アカウントがロックされている場合、エラー
|
||||||
|
if (user.account.locked) {
|
||||||
|
throw new AccountLockedError('account is locked.');
|
||||||
|
}
|
||||||
|
// ライセンスの有効性をチェック
|
||||||
|
const { licenseError } = await this.checkLicenseValidityByUserId(
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
|
if (licenseError) {
|
||||||
|
throw licenseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
|
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
|
||||||
await this.blobStorageService.containerExists(
|
await this.blobStorageService.containerExists(
|
||||||
context,
|
context,
|
||||||
@ -286,10 +308,28 @@ export class FilesService {
|
|||||||
return url;
|
return url;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`error=${e}`);
|
this.logger.error(`error=${e}`);
|
||||||
throw new HttpException(
|
switch (e.constructor) {
|
||||||
makeErrorResponse('E009999'),
|
case AccountLockedError:
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
throw new HttpException(
|
||||||
);
|
makeErrorResponse('E010504'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
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'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`,
|
`[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`,
|
||||||
@ -323,6 +363,16 @@ export class FilesService {
|
|||||||
if (!user.account) {
|
if (!user.account) {
|
||||||
throw new AccountNotFoundError('account not found.');
|
throw new AccountNotFoundError('account not found.');
|
||||||
}
|
}
|
||||||
|
// 第五階層のみチェック
|
||||||
|
if (user.account.tier === TIERS.TIER5) {
|
||||||
|
// ライセンスの有効性をチェック
|
||||||
|
const { licenseError } = await this.checkLicenseValidityByUserId(
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
|
if (licenseError) {
|
||||||
|
throw licenseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
accountId = user.account.id;
|
accountId = user.account.id;
|
||||||
userId = user.id;
|
userId = user.id;
|
||||||
country = user.account.country;
|
country = user.account.country;
|
||||||
@ -330,14 +380,26 @@ export class FilesService {
|
|||||||
authorId = user.author_id ?? undefined;
|
authorId = user.author_id ?? undefined;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`error=${e}`);
|
this.logger.error(`error=${e}`);
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[OUT] [${context.trackingId}] ${this.publishAudioFileDownloadSas.name}`,
|
`[OUT] [${context.trackingId}] ${this.publishAudioFileDownloadSas.name}`,
|
||||||
);
|
);
|
||||||
throw new HttpException(
|
switch (e.constructor) {
|
||||||
makeErrorResponse('E009999'),
|
case LicenseExpiredError:
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
throw new HttpException(
|
||||||
);
|
makeErrorResponse('E010805'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
case LicenseNotAllocatedError:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E010812'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -460,6 +522,16 @@ export class FilesService {
|
|||||||
if (!user.account) {
|
if (!user.account) {
|
||||||
throw new AccountNotFoundError('account not found.');
|
throw new AccountNotFoundError('account not found.');
|
||||||
}
|
}
|
||||||
|
// 第五階層のみチェック
|
||||||
|
if (user.account.tier === TIERS.TIER5) {
|
||||||
|
// ライセンスの有効性をチェック
|
||||||
|
const { licenseError } = await this.checkLicenseValidityByUserId(
|
||||||
|
user.id,
|
||||||
|
);
|
||||||
|
if (licenseError) {
|
||||||
|
throw licenseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
accountId = user.account_id;
|
accountId = user.account_id;
|
||||||
userId = user.id;
|
userId = user.id;
|
||||||
country = user.account.country;
|
country = user.account.country;
|
||||||
@ -470,10 +542,23 @@ export class FilesService {
|
|||||||
this.logger.log(
|
this.logger.log(
|
||||||
`[OUT] [${context.trackingId}] ${this.publishTemplateFileDownloadSas.name}`,
|
`[OUT] [${context.trackingId}] ${this.publishTemplateFileDownloadSas.name}`,
|
||||||
);
|
);
|
||||||
throw new HttpException(
|
switch (e.constructor) {
|
||||||
makeErrorResponse('E009999'),
|
case LicenseExpiredError:
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
throw new HttpException(
|
||||||
);
|
makeErrorResponse('E010805'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
case LicenseNotAllocatedError:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E010812'),
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -562,6 +647,7 @@ export class FilesService {
|
|||||||
makeErrorResponse('E010701'),
|
makeErrorResponse('E010701'),
|
||||||
HttpStatus.BAD_REQUEST,
|
HttpStatus.BAD_REQUEST,
|
||||||
);
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
makeErrorResponse('E009999'),
|
makeErrorResponse('E009999'),
|
||||||
@ -679,4 +765,44 @@ export class FilesService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーに割り当てられているライセンスの有効性をチェックする。
|
||||||
|
* ライセンスが割り当てられていない場合、またはライセンスが有効期限切れの場合、エラー返却する。
|
||||||
|
* @param userId
|
||||||
|
* @returns licenseError?
|
||||||
|
*/
|
||||||
|
// TODO: TASK3084で共通部品化する
|
||||||
|
private async checkLicenseValidityByUserId(
|
||||||
|
userId: number,
|
||||||
|
): Promise<{ licenseError?: Error }> {
|
||||||
|
try {
|
||||||
|
const allocatedLicense = await this.usersRepository.findLicenseByUserId(
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allocatedLicense) {
|
||||||
|
return {
|
||||||
|
licenseError: new LicenseNotAllocatedError(
|
||||||
|
'license is not allocated.',
|
||||||
|
),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const currentDate = new DateWithZeroTime();
|
||||||
|
if (
|
||||||
|
allocatedLicense.expiry_date &&
|
||||||
|
allocatedLicense.expiry_date < currentDate
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
licenseError: new LicenseExpiredError('license is expired.'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {}; // エラーがない場合は空のオブジェクトを返す
|
||||||
|
} catch (e) {
|
||||||
|
// リポジトリ層のエラーやその他の例外をハンドリング
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export type BlobstorageServiceMockValue = {
|
|||||||
|
|
||||||
export type UsersRepositoryMockValue = {
|
export type UsersRepositoryMockValue = {
|
||||||
findUserByExternalId: User | Error;
|
findUserByExternalId: User | Error;
|
||||||
|
isUserLicenseValid: boolean | Error;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TasksRepositoryMockValue = {
|
export type TasksRepositoryMockValue = {
|
||||||
@ -91,13 +92,17 @@ export const makeBlobstorageServiceMock = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
|
export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
|
||||||
const { findUserByExternalId } = value;
|
const { findUserByExternalId, isUserLicenseValid } = value;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
findUserByExternalId:
|
findUserByExternalId:
|
||||||
findUserByExternalId instanceof Error
|
findUserByExternalId instanceof Error
|
||||||
? jest.fn<Promise<void>, []>().mockRejectedValue(findUserByExternalId)
|
? jest.fn<Promise<void>, []>().mockRejectedValue(findUserByExternalId)
|
||||||
: jest.fn<Promise<User>, []>().mockResolvedValue(findUserByExternalId),
|
: jest.fn<Promise<User>, []>().mockResolvedValue(findUserByExternalId),
|
||||||
|
isUserLicenseValid:
|
||||||
|
isUserLicenseValid instanceof Error
|
||||||
|
? jest.fn<Promise<void>, []>().mockRejectedValue(isUserLicenseValid)
|
||||||
|
: jest.fn<Promise<boolean>, []>().mockResolvedValue(isUserLicenseValid),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -169,6 +174,7 @@ export const makeDefaultUsersRepositoryMockValue =
|
|||||||
user: null,
|
user: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
isUserLicenseValid: true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import {
|
|||||||
} from '../../tasks/test/tasks.service.mock';
|
} from '../../tasks/test/tasks.service.mock';
|
||||||
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
|
||||||
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
|
||||||
|
import { License } from '../../../repositories/licenses/entity/license.entity';
|
||||||
|
|
||||||
export const createTask = async (
|
export const createTask = async (
|
||||||
datasource: DataSource,
|
datasource: DataSource,
|
||||||
@ -205,3 +206,33 @@ export const makeTestingModuleWithBlobAndNotification = async (
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createLicense = async (
|
||||||
|
datasource: DataSource,
|
||||||
|
licenseId: number,
|
||||||
|
expiry_date: Date | null,
|
||||||
|
accountId: number,
|
||||||
|
type: string,
|
||||||
|
status: string,
|
||||||
|
allocated_user_id: number | null,
|
||||||
|
order_id: number | null,
|
||||||
|
deleted_at: Date | null,
|
||||||
|
delete_order_id: number | null,
|
||||||
|
): Promise<void> => {
|
||||||
|
const { identifiers } = await datasource.getRepository(License).insert({
|
||||||
|
id: licenseId,
|
||||||
|
expiry_date: expiry_date,
|
||||||
|
account_id: accountId,
|
||||||
|
type: type,
|
||||||
|
status: status,
|
||||||
|
allocated_user_id: allocated_user_id,
|
||||||
|
order_id: order_id,
|
||||||
|
deleted_at: deleted_at,
|
||||||
|
delete_order_id: delete_order_id,
|
||||||
|
created_by: 'test_runner',
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_by: 'updater',
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
identifiers.pop() as License;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
|
||||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||||
import { AccessToken } from '../../common/token';
|
|
||||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||||
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
|
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
|
||||||
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
|
||||||
@ -13,6 +12,7 @@ import {
|
|||||||
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
|
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
|
||||||
import { UserNotFoundError } from '../../repositories/users/errors/types';
|
import { UserNotFoundError } from '../../repositories/users/errors/types';
|
||||||
import {
|
import {
|
||||||
|
DateWithZeroTime,
|
||||||
GetAllocatableLicensesResponse,
|
GetAllocatableLicensesResponse,
|
||||||
IssueCardLicensesResponse,
|
IssueCardLicensesResponse,
|
||||||
} from './types/types';
|
} from './types/types';
|
||||||
|
|||||||
@ -4,3 +4,5 @@ export class AccountNotFoundError extends Error {}
|
|||||||
export class DealerAccountNotFoundError extends Error {}
|
export class DealerAccountNotFoundError extends Error {}
|
||||||
// 管理者ユーザ未存在エラー
|
// 管理者ユーザ未存在エラー
|
||||||
export class AdminUserNotFoundError extends Error {}
|
export class AdminUserNotFoundError extends Error {}
|
||||||
|
// アカウントロックエラー
|
||||||
|
export class AccountLockedError extends Error {}
|
||||||
|
|||||||
@ -33,3 +33,6 @@ export class CancellationPeriodExpiredError extends Error {}
|
|||||||
|
|
||||||
// ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
// ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
|
||||||
export class AlreadyLicenseAllocatedError extends Error {}
|
export class AlreadyLicenseAllocatedError extends Error {}
|
||||||
|
|
||||||
|
// ライセンス未割当エラー
|
||||||
|
export class LicenseNotAllocatedError extends Error {}
|
||||||
|
|||||||
@ -648,6 +648,23 @@ export class UsersRepositoryService {
|
|||||||
return originAccount.delegation_permission;
|
return originAccount.delegation_permission;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* ユーザーに割り当てられているライセンスを取得する
|
||||||
|
* @param userId ユーザーID
|
||||||
|
* @returns License
|
||||||
|
*/
|
||||||
|
async findLicenseByUserId(userId: number): Promise<License | null> {
|
||||||
|
const allocatedLicense = await this.dataSource
|
||||||
|
.getRepository(License)
|
||||||
|
.findOne({
|
||||||
|
where: {
|
||||||
|
allocated_user_id: userId,
|
||||||
|
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return allocatedLicense;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ユーザーに紐づく各種情報を取得する
|
* ユーザーに紐づく各種情報を取得する
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user