import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { makeBlobstorageServiceMockValue } from './test/files.service.mock';
import { DataSource } from 'typeorm';
import {
  createLicense,
  createTask,
  createUserGroupAndMember,
  getTaskFromJobNumber,
  makeTestingModuleWithBlobAndNotification,
} from './test/utility';
import { FilesService } from './files.service';
import { makeContext } from '../../common/log';
import {
  makeHierarchicalAccounts,
  makeTestAccount,
  makeTestSimpleAccount,
  makeTestUser,
} from '../../common/test/utility';
import { makeTestingModule } from '../../common/test/modules';
import { overrideBlobstorageService } from '../../common/test/overrides';
import {
  createTemplateFile,
  getTemplateFiles,
} from '../templates/test/utility';
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
import { makeDefaultNotificationhubServiceMockValue } from '../tasks/test/tasks.service.mock';
import {
  createWorkflow,
  createWorkflowTypist,
} from '../workflows/test/utility';
import { createWorktype } from '../accounts/test/utility';
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
import { DateWithZeroTime } from '../licenses/types/types';
import {
  LICENSE_ALLOCATED_STATUS,
  LICENSE_TYPE,
  TASK_STATUS,
  USER_ROLES,
} from '../../constants';

describe('publishUploadSas', () => {
  let source: DataSource | null = 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 () => {
    if (!source) return;
    await source.destroy();
    source = null;
  });

  it('音声アップロードSASトークンが乗っているURLを取得できる', async () => {
    if (!source) fail();
    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, 'requestId');
    const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/${userId}`;

    //SASトークンを返却する
    overrideBlobstorageService(service, {
      containerExists: async () => true,
      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, 'requestId');

    //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, 'requestId');

    //BlobのSASトークン生成に失敗するようにする
    overrideBlobstorageService(service, {
      containerExists: async () => true,
      publishUploadSas: async () => {
        throw new Error('blob failed');
      },
    });

    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, 'requestId');

    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(
      service.publishUploadSas(
        makeContext('trackingId', 'requestId'),
        externalId,
      ),
    ).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 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使用)', () => {
  let source: DataSource | null = 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 () => {
    if (!source) return;
    await source.destroy();
    source = null;
  });
  it('タスク作成時に、自動ルーティングを行うことができる（APIの引数として渡されたAuthorIDとworkType）', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const {
      external_id: authorExternalId,
      id: authorUserId,
      author_id: authorAuthorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const { id: typistUserId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: 'typist',
      author_id: undefined,
    });
    // ワークタイプを作成
    const { id: worktypeId, custom_worktype_id } = await createWorktype(
      source,
      accountId,
      'worktypeId',
    );
    // テンプレートファイルを作成
    const { id: templateFileId } = await createTemplateFile(
      source,
      accountId,
      'templateFile',
      'http://blob/url/templateFile.zip',
    );
    // ワークフローを作成
    const { id: workflowId } = await createWorkflow(
      source,
      accountId,
      authorUserId,
      worktypeId,
      templateFileId,
    );
    // ワークフロータイピストを作成
    await createWorkflowTypist(source, workflowId, typistUserId);
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    const NotificationHubService = module.get<NotificationhubService>(
      NotificationhubService,
    );
    const result = await service.uploadFinished(
      makeContext('trackingId', 'requestId'),
      authorExternalId,
      'http://blob/url/file.zip',
      authorAuthorId ?? '',
      'file.zip',
      '11:22:33',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      256,
      '01',
      'DS2',
      'comment',
      custom_worktype_id,
      optionItemList,
      false,
    );
    expect(result).toEqual({ jobNumber: '00000001' });
    // 通知処理が想定通りの引数で呼ばれているか確認
    expect(NotificationHubService.notify).toHaveBeenCalledWith(
      makeContext('trackingId', 'requestId'),
      [`user_${typistUserId}`],
      {
        authorId: 'AUTHOR_ID',
        filename: 'file',
        priority: 'High',
        uploadedAt: '2023-05-26T11:22:33.444',
      },
    );
    // 作成したタスクを取得
    const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
    // タスクのチェックアウト権限を取得
    const resultCheckoutPermission = await getCheckoutPermissions(
      source,
      resultTask?.id ?? 0,
    );
    // タスクのテンプレートファイルIDを確認
    expect(resultTask?.template_file_id).toEqual(templateFileId);
    // タスクのチェックアウト権限が想定通り（ワークフローで設定されている）のユーザーIDで作成されているか確認
    expect(resultCheckoutPermission.length).toEqual(1);
    expect(resultCheckoutPermission[0].user_id).toEqual(typistUserId);
  });

  it('別のタスクが既に存在する場合、タスク作成時に、自動ルーティングを行うことができる（APIの引数として渡されたAuthorIDとworkType）', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const {
      external_id: authorExternalId,
      id: authorUserId,
      author_id: authorAuthorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const { id: typistUserId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: 'typist',
      author_id: undefined,
    });
    //タスクを作成
    await createTask(
      source,
      accountId,
      'http://blob/url/file.zip',
      'file.zip',
      '01',
      typistUserId,
      authorAuthorId ?? '',
    );
    // ワークタイプを作成
    const { id: worktypeId, custom_worktype_id } = await createWorktype(
      source,
      accountId,
      'worktypeId',
    );
    // ワークフローを作成
    const { id: workflowId } = await createWorkflow(
      source,
      accountId,
      authorUserId,
      worktypeId,
    );
    // ワークフロータイピストを作成
    await createWorkflowTypist(source, workflowId, typistUserId);
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    const NotificationHubService = module.get<NotificationhubService>(
      NotificationhubService,
    );
    const result = await service.uploadFinished(
      makeContext('trackingId', 'requestId'),
      authorExternalId,
      'http://blob/url/file.zip',
      authorAuthorId ?? '',
      'file.zip',
      '11:22:33',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      256,
      '01',
      'DS2',
      'comment',
      custom_worktype_id,
      optionItemList,
      false,
    );
    expect(result).toEqual({ jobNumber: '00000002' });
    // 通知処理が想定通りの引数で呼ばれているか確認
    expect(NotificationHubService.notify).toHaveBeenCalledWith(
      makeContext('trackingId', 'requestId'),
      [`user_${typistUserId}`],
      {
        authorId: 'AUTHOR_ID',
        filename: 'file',
        priority: 'High',
        uploadedAt: '2023-05-26T11:22:33.444',
      },
    );
    // 作成したタスクを取得
    const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
    // タスクのチェックアウト権限を取得
    const resultCheckoutPermission = await getCheckoutPermissions(
      source,
      resultTask?.id ?? 0,
    );
    // タスクのテンプレートファイルIDを確認
    expect(resultTask?.template_file_id).toBeNull();
    // タスクのチェックアウト権限が想定通り（ワークフローで設定されている）のユーザーIDで作成されているか確認
    expect(resultCheckoutPermission.length).toEqual(1);
    expect(resultCheckoutPermission[0].user_id).toEqual(typistUserId);
  });

  it('タスク作成時に、自動ルーティングを行うことができる（API実行者のAuthorIDとworkType）', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    // 音声ファイルの録音者のユーザー
    const { author_id: authorAuthorId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    // ルーティング先のタイピストのユーザー
    const { id: typistUserId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: 'typist',
      author_id: undefined,
    });
    // API実行者のユーザー
    const { external_id: myExternalId, id: myUserId } = await makeTestUser(
      source,
      {
        account_id: accountId,
        external_id: 'my-author-user-external-id',
        role: 'author',
        author_id: 'MY_AUTHOR_ID',
      },
    );

    // ワークタイプを作成
    const { id: worktypeId, custom_worktype_id } = await createWorktype(
      source,
      accountId,
      'worktypeId',
    );

    // テンプレートファイルを作成
    const { id: templateFileId } = await createTemplateFile(
      source,
      accountId,
      'templateFile',
      'http://blob/url/templateFile.zip',
    );

    // ワークフローを作成
    const { id: workflowId } = await createWorkflow(
      source,
      accountId,
      myUserId, // API実行者のユーザーIDを設定
      worktypeId,
      templateFileId,
    );
    // ユーザーグループを作成
    const { userGroupId } = await createUserGroupAndMember(
      source,
      accountId,
      'userGroupName',
      typistUserId, // ルーティング先のタイピストのユーザーIDを設定
    );
    // ワークフロータイピストを作成
    await createWorkflowTypist(
      source,
      workflowId,
      undefined,
      userGroupId, // ルーティング先のユーザーグループIDを設定
    );

    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    const NotificationHubService = module.get<NotificationhubService>(
      NotificationhubService,
    );
    const result = await service.uploadFinished(
      makeContext('trackingId', 'requestId'),
      myExternalId, // API実行者のユーザーIDを設定
      'http://blob/url/file.zip',
      authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
      'file.zip',
      '11:22:33',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      256,
      '01',
      'DS2',
      'comment',
      custom_worktype_id,
      optionItemList,
      false,
    );
    expect(result).toEqual({ jobNumber: '00000001' });
    // 通知処理が想定通りの引数で呼ばれているか確認
    expect(NotificationHubService.notify).toHaveBeenCalledWith(
      makeContext('trackingId', 'requestId'),
      [`user_${typistUserId}`],
      {
        authorId: 'AUTHOR_ID',
        filename: 'file',
        priority: 'High',
        uploadedAt: '2023-05-26T11:22:33.444',
      },
    );
    // 作成したタスクを取得
    const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
    // タスクのチェックアウト権限を取得
    const resultCheckoutPermission = await getCheckoutPermissions(
      source,
      resultTask?.id ?? 0,
    );
    // タスクのテンプレートファイルIDを確認
    expect(resultTask?.template_file_id).toEqual(templateFileId);
    // タスクのチェックアウト権限が想定通り（ワークフローで設定されている）のユーザーIDで作成されているか確認
    expect(resultCheckoutPermission.length).toEqual(1);
    expect(resultCheckoutPermission[0].user_group_id).toEqual(userGroupId);
  });
  it('タスク作成時に、音声ファイルメタ情報のAuthorIDに存在しないものが入っていても自動ルーティングを行うことができる（API実行者のAuthorIDとworkType）', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    // 音声ファイルの録音者のユーザー
    const { author_id: authorAuthorId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    // ルーティング先のタイピストのユーザー
    const { id: typistUserId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: 'typist',
      author_id: undefined,
    });
    // API実行者のユーザー
    const { external_id: myExternalId, id: myUserId } = await makeTestUser(
      source,
      {
        account_id: accountId,
        external_id: 'my-author-user-external-id',
        role: 'author',
        author_id: 'MY_AUTHOR_ID',
      },
    );

    // ワークタイプを作成
    const { id: worktypeId, custom_worktype_id } = await createWorktype(
      source,
      accountId,
      'worktypeId',
    );

    // テンプレートファイルを作成
    const { id: templateFileId } = await createTemplateFile(
      source,
      accountId,
      'templateFile',
      'http://blob/url/templateFile.zip',
    );

    // ワークフローを作成
    const { id: workflowId } = await createWorkflow(
      source,
      accountId,
      myUserId, // API実行者のユーザーIDを設定
      worktypeId,
      templateFileId,
    );
    // ユーザーグループを作成
    const { userGroupId } = await createUserGroupAndMember(
      source,
      accountId,
      'userGroupName',
      typistUserId, // ルーティング先のタイピストのユーザーIDを設定
    );
    // ワークフロータイピストを作成
    await createWorkflowTypist(
      source,
      workflowId,
      undefined,
      userGroupId, // ルーティング先のユーザーグループIDを設定
    );

    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    const NotificationHubService = module.get<NotificationhubService>(
      NotificationhubService,
    );
    const result = await service.uploadFinished(
      makeContext('trackingId', 'requestId'),
      myExternalId, // API実行者のユーザーIDを設定
      'http://blob/url/file.zip',
      'XXXXXXXXXX', // 音声ファイルの情報には、録音者のAuthorIDが入る
      'file.zip',
      '11:22:33',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      256,
      '01',
      'DS2',
      'comment',
      custom_worktype_id,
      optionItemList,
      false,
    );
    expect(result).toEqual({ jobNumber: '00000001' });
    // 通知処理が想定通りの引数で呼ばれているか確認
    expect(NotificationHubService.notify).toHaveBeenCalledWith(
      makeContext('trackingId', 'requestId'),
      [`user_${typistUserId}`],
      {
        authorId: 'XXXXXXXXXX',
        filename: 'file',
        priority: 'High',
        uploadedAt: '2023-05-26T11:22:33.444',
      },
    );
    // 作成したタスクを取得
    const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
    // タスクのチェックアウト権限を取得
    const resultCheckoutPermission = await getCheckoutPermissions(
      source,
      resultTask?.id ?? 0,
    );
    // タスクのテンプレートファイルIDを確認
    expect(resultTask?.template_file_id).toEqual(templateFileId);
    // タスクのチェックアウト権限が想定通り（ワークフローで設定されている）のユーザーIDで作成されているか確認
    expect(resultCheckoutPermission.length).toEqual(1);
    expect(resultCheckoutPermission[0].user_group_id).toEqual(userGroupId);
  });

  it('ワークフローが見つからない場合、タスク作成時に、自動ルーティングを行うことができない', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    // 音声ファイルの録音者のユーザー
    const {
      external_id: authorExternalId,
      id: authorUserId,
      author_id: authorAuthorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);

    const result = await service.uploadFinished(
      makeContext('trackingId', 'requestId'),
      authorExternalId, // API実行者のユーザーIDを設定
      'http://blob/url/file.zip',
      authorAuthorId ?? '', // 音声ファイルの情報には、録音者のAuthorIDが入る
      'file.zip',
      '11:22:33',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      '2023-05-26T11:22:33.444',
      256,
      '01',
      'DS2',
      'comment',
      'worktypeId',
      optionItemList,
      false,
    );
    expect(result).toEqual({ jobNumber: '00000001' });
    // タスクを取得
    const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
    // タスクのチェックアウト権限を取得
    const resultCheckoutPermission = await getCheckoutPermissions(
      source,
      resultTask?.id ?? 0,
    );
    // タスクがあることを確認
    expect(resultTask).not.toBeNull();
    // 自動ルーティングが行われていないことを確認
    expect(resultCheckoutPermission.length).toEqual(0);
  });
  it('日付フォーマットが不正な場合、エラーを返却する', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const {
      external_id: authorExternalId,
      id: authorUserId,
      author_id: authorAuthorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.uploadFinished(
        makeContext('trackingId', 'requestId'),
        authorExternalId,
        'http://blob/url/file.zip',
        authorAuthorId ?? '',
        'file.zip',
        '11:22:33',
        'yyyy-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
    );
  });
  it('オプションアイテムが10個ない場合、エラーを返却する', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const {
      external_id: authorExternalId,
      id: authorUserId,
      author_id: authorAuthorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.uploadFinished(
        makeContext('trackingId', 'requestId'),
        authorExternalId,
        'http://blob/url/file.zip',
        authorAuthorId ?? '',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        [
          {
            optionItemLabel: 'label_01',
            optionItemValue: 'value_01',
          },
        ],
        false,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST),
    );
  });
  it('タスク追加でユーザー情報の取得に失敗した場合、エラーを返却する', async () => {
    if (!source) fail();
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);

    await expect(
      service.uploadFinished(
        makeContext('trackingId', 'requestId'),
        'authorExternalId',
        'http://blob/url/file.zip',
        'authorAuthorId',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
  it('タスクのDBへの追加に失敗した場合、エラーを返却する', async () => {
    if (!source) fail();
    const blobParam = makeBlobstorageServiceMockValue();
    const notificationParam = makeDefaultNotificationhubServiceMockValue();

    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    const taskRepoService = module.get<TasksRepositoryService>(
      TasksRepositoryService,
    );
    taskRepoService.create = jest.fn().mockRejectedValue(new Error(''));
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: authorExternalId, author_id: authorAuthorId } =
      await makeTestUser(source, {
        account_id: accountId,
        external_id: 'author-user-external-id',
        role: 'author',
        author_id: 'AUTHOR_ID',
      });

    await expect(
      service.uploadFinished(
        makeContext('trackingId', 'requestId'),
        authorExternalId,
        'http://blob/url/file.zip',
        authorAuthorId ?? '',
        'file.zip',
        '11:22:33',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        '2023-05-26T11:22:33.444',
        256,
        '01',
        'DS2',
        'comment',
        'workTypeID',
        optionItemList,
        false,
      ),
    ).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
});

describe('音声ファイルダウンロードURL取得', () => {
  let source: DataSource | null = 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 () => {
    if (!source) return;
    await source.destroy();
    source = null;
  });

  it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const {
      external_id: externalId,
      id: userId,
      author_id: authorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      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('ダウンロード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);
    const { external_id: externalId, id: userId } = 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: 'AUTHOR_ID',
    });

    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${authorUserId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'Finished',
      userId,
    );

    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);

    await expect(
      service.publishAudioFileDownloadSas(
        makeContext('trackingId', 'requestId'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Typistの場合、自身が担当するタスクでない場合エラー', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: 'typist',
    });
    const { id: otherId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'other-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: 'AUTHOR_ID',
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${authorUserId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      otherId,
    );

    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);

    await expect(
      service.publishTemplateFileDownloadSas(
        makeContext('tracking', 'requestId'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Authorの場合、自身が登録したタスクでない場合エラー', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId, id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      'InProgress',
      undefined,
      'OTHOR_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);

    await expect(
      service.publishAudioFileDownloadSas(
        makeContext('trackingId', 'requestId'),
        externalId,
        audioFileId,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('Taskが存在しない場合はエラーとなる', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });

    const blobParam = makeBlobstorageServiceMockValue();

    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,
        1,
      ),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010603'), HttpStatus.BAD_REQUEST),
    );
  });

  it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const {
      external_id: externalId,
      id: userId,
      author_id: authorId,
    } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'author-user-external-id',
      role: 'author',
      author_id: 'AUTHOR_ID',
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/${userId}`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      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('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取得', () => {
  let source: DataSource | null = 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 () => {
    if (!source) return;
    await source.destroy();
    source = null;
  });

  it('ダウンロードSASトークンが乗っているURLを取得できる', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId, id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: USER_ROLES.TYPIST,
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      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('tracking', 'requestId'),
      externalId,
      audioFileId,
    );
    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);
    const { external_id: externalId, id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: USER_ROLES.TYPIST,
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      TASK_STATUS.FINISHED,
      userId,
    );

    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);

    try {
      await service.publishTemplateFileDownloadSas(
        makeContext('tracking', 'requestId'),
        externalId,
        audioFileId,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
      } else {
        fail();
      }
    }
  });

  it('自身が担当するタスクでない場合エラー', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: USER_ROLES.TYPIST,
    });
    const { id: otherId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'other-typist-user-external-id',
      role: USER_ROLES.TYPIST,
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      TASK_STATUS.IN_PROGRESS,
      otherId,
    );

    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);

    try {
      await service.publishTemplateFileDownloadSas(
        makeContext('tracking', 'requestId'),
        externalId,
        audioFileId,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
      } else {
        fail();
      }
    }
  });

  it('Taskが存在しない場合はエラーとなる', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: USER_ROLES.TYPIST,
    });

    const blobParam = makeBlobstorageServiceMockValue();

    const notificationParam = makeDefaultNotificationhubServiceMockValue();
    const module = await makeTestingModuleWithBlobAndNotification(
      source,
      blobParam,
      notificationParam,
    );
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);

    try {
      await service.publishTemplateFileDownloadSas(
        makeContext('tracking', 'requestId'),
        externalId,
        1,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010603'));
      } else {
        fail();
      }
    }
  });

  it('blobストレージにファイルが存在しない場合はエラーとなる', async () => {
    if (!source) fail();
    const { id: accountId } = await makeTestSimpleAccount(source);
    const { external_id: externalId, id: userId } = await makeTestUser(source, {
      account_id: accountId,
      external_id: 'typist-user-external-id',
      role: USER_ROLES.TYPIST,
    });
    const url = `https://saodmsusdev.blob.core.windows.net/account-${accountId}/Templates`;

    const { audioFileId } = await createTask(
      source,
      accountId,
      url,
      'test.zip',
      TASK_STATUS.IN_PROGRESS,
      userId,
      '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('tracking', 'requestId'),
        externalId,
        audioFileId,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010701'));
      } 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,
    });
    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', () => {
  let source: DataSource | null = 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 () => {
    if (!source) return;
    await source.destroy();
    source = null;
  });

  it('テンプレートファイルアップロードSASトークンが乗っているURLを取得できる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    // 第五階層のアカウント作成
    const { account, admin } = await makeTestAccount(source, { tier: 5 });

    const context = makeContext(admin.external_id, 'requestId');
    const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/Templates`;

    //SASトークンを返却する
    overrideBlobstorageService(service, {
      containerExists: async () => true,
      publishTemplateUploadSas: async () => `${baseUrl}?sas-token`,
    });

    const url = await service.publishTemplateFileUploadSas(
      context,
      admin.external_id,
    );

    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: 5 });

    const context = makeContext(admin.external_id, 'requestId');

    //Blobコンテナ存在チェックに失敗するようにする
    overrideBlobstorageService(service, {
      containerExists: async () => false,
      publishTemplateUploadSas: async () => '',
    });

    try {
      await service.publishTemplateFileUploadSas(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: 5 });

    const context = makeContext(admin.external_id, 'requestId');

    //BlobのSASトークン生成に失敗するようにする
    overrideBlobstorageService(service, {
      containerExists: async () => true,
      publishTemplateUploadSas: async () => {
        throw new Error('blob failed');
      },
    });

    try {
      await service.publishTemplateFileUploadSas(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();
      }
    }
  });
});

describe('templateUploadFinished', () => {
  let source: DataSource | null = 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 () => {
    if (!source) return;
    await source.destroy();
    source = null;
  });

  it('アップロード完了後のテンプレートファイル情報をDBに保存できる（新規追加）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    // 第五階層のアカウント作成
    const { account, admin } = await makeTestAccount(source, { tier: 5 });
    const context = makeContext(admin.external_id, 'requestId');

    const fileName = 'test.docs';
    const url = `https://blob.url/account-${account.id}/Templates`;

    // 事前にDBを確認
    {
      const templates = await getTemplateFiles(source, account.id);
      expect(templates.length).toBe(0);
    }

    await service.templateUploadFinished(
      context,
      admin.external_id,
      url,
      fileName,
    );

    //実行結果を確認
    {
      const templates = await getTemplateFiles(source, account.id);
      expect(templates.length).toBe(1);
      expect(templates[0].file_name).toBe(fileName);
      expect(templates[0].url).toBe(url);
    }
  });

  it('アップロード完了後のテンプレートファイル情報をDBに保存できる（更新）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    // 第五階層のアカウント作成
    const { account, admin } = await makeTestAccount(source, { tier: 5 });
    const context = makeContext(admin.external_id, 'requestId');

    const fileName = 'test.docs';
    const url = `https://blob.url/account-${account.id}/Templates`;

    await createTemplateFile(source, account.id, fileName, url);

    // 事前にDBを確認
    {
      const templates = await getTemplateFiles(source, account.id);
      expect(templates.length).toBe(1);
      expect(templates[0].file_name).toBe(fileName);
      expect(templates[0].url).toBe(url);
    }

    const updateUrl = `https://blob.update.url/account-${account.id}/Templates`;

    await service.templateUploadFinished(
      context,
      admin.external_id,
      updateUrl,
      fileName,
    );

    //実行結果を確認
    {
      const templates = await getTemplateFiles(source, account.id);
      expect(templates.length).toBe(1);
      expect(templates[0].file_name).toBe(fileName);
      expect(templates[0].url).toBe(updateUrl);
    }
  });

  it('DBへの保存に失敗した場合はエラーとなる', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<FilesService>(FilesService);
    // 第五階層のアカウント作成
    const { account, admin } = await makeTestAccount(source, { tier: 5 });
    const context = makeContext(admin.external_id, 'requestId');

    const fileName = 'test.docs';
    const url = `https://blob.url/account-${account.id}/Templates`;

    // 事前にDBを確認
    {
      const templates = await getTemplateFiles(source, account.id);
      expect(templates.length).toBe(0);
    }

    //DBアクセスに失敗するようにする
    const templatesService = module.get<TemplateFilesRepositoryService>(
      TemplateFilesRepositoryService,
    );
    templatesService.upsertTemplateFile = jest
      .fn()
      .mockRejectedValue('DB failed');

    try {
      await service.templateUploadFinished(
        context,
        admin.external_id,
        url,
        fileName,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
  });
});

const optionItemList = [
  {
    optionItemLabel: 'label_01',
    optionItemValue: 'value_01',
  },
  {
    optionItemLabel: 'label_02',
    optionItemValue: 'value_02',
  },
  {
    optionItemLabel: 'label_03',
    optionItemValue: 'value_03',
  },
  {
    optionItemLabel: 'label_04',
    optionItemValue: 'value_04',
  },
  {
    optionItemLabel: 'label_05',
    optionItemValue: 'value_05',
  },
  {
    optionItemLabel: 'label_06',
    optionItemValue: 'value_06',
  },
  {
    optionItemLabel: 'label_07',
    optionItemValue: 'value_07',
  },
  {
    optionItemLabel: 'label_08',
    optionItemValue: 'value_08',
  },
  {
    optionItemLabel: 'label_09',
    optionItemValue: 'value_09',
  },
  {
    optionItemLabel: 'label_10',
    optionItemValue: 'value_10',
  },
];
