import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import {
  makeAccountsServiceMock,
  makeBlobStorageServiceMockValue,
  makeDefaultAccountsRepositoryMockValue,
  makeDefaultAdB2cMockValue,
  makeDefaultLicensesRepositoryMockValue,
  makeDefaultSendGridlValue,
  makeDefaultUserGroupsRepositoryMockValue,
  makeDefaultUsersRepositoryMockValue,
} from './test/accounts.service.mock';
import { makeDefaultConfigValue } from '../users/test/users.service.mock';
import {
  createAccount,
  createLicense,
  createLicenseOrder,
  createUser,
  createLicenseSetExpiryDateAndStatus,
  getAccount,
  getUserFromExternalID,
  getAccounts,
  getUsers,
  getSortCriteria,
  createAccountAndAdminUser,
} from './test/utility';
import { DataSource } from 'typeorm';
import { makeTestingModule } from '../../common/test/modules';
import { AccountsService } from './accounts.service';
import { Context, makeContext } from '../../common/log';
import { TIERS } from '../../constants';
import { License } from '../../repositories/licenses/entity/license.entity';
import {
  overrideAccountsRepositoryService,
  overrideAdB2cService,
  overrideBlobstorageService,
  overrideSendgridService,
} from '../../common/test/overrides';
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
import { BlobstorageService } from '../../gateways/blobstorage/blobstorage.service';

describe('createAccount', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });

  it('アカウントを作成できる', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);

    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@example.com';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: externalId };
      },
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        return;
      },
      createMailContentFromEmailConfirm: async () => {
        return { html: '', text: '', subject: '' };
      },
    });
    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
    });

    const { accountId, externalUserId, userId } = await service.createAccount(
      makeContext('uuid'),
      companyName,
      country,
      dealerAccountId,
      email,
      password,
      username,
      role,
      acceptedTermsVersion,
    );
    // 作成したアカウントのIDが返ってくるか確認
    expect(accountId).toBe(1);
    expect(externalUserId).toBe(externalId);
    expect(userId).toBe(1);

    // DB内が想定通りになっているか確認
    const account = await getAccount(source, accountId);
    const user = await getUserFromExternalID(source, externalUserId);
    expect(account.company_name).toBe(companyName);
    expect(account.country).toBe(country);
    expect(account.parent_account_id).toBe(dealerAccountId);
    expect(account.tier).toBe(TIERS.TIER5);
    expect(account.primary_admin_user_id).toBe(user.id);
    expect(account.secondary_admin_user_id).toBe(null);
    expect(user.accepted_terms_version).toBe(acceptedTermsVersion);
    expect(user.account_id).toBe(accountId);
    expect(user.role).toBe(role);
  });

  it('アカウントを作成がAzure AD B2Cへの通信失敗によって失敗すると500エラーが発生する', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);

    overrideAdB2cService(service, {
      createUser: async () => {
        throw new Error();
      },
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        return;
      },
      createMailContentFromEmailConfirm: async () => {
        return { html: '', text: '', subject: '' };
      },
    });

    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@example.com';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'admin none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        throw new Error();
      },
    });
    overrideSendgridService(service, {});
    overrideBlobstorageService(service, {});

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    // DB内が想定通りになっているか確認
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(0);
    const users = await getUsers(source);
    expect(users.length).toBe(0);
  });

  it('アカウントを作成がメールアドレス重複によって失敗すると400エラーが発生する', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);

    overrideAdB2cService(service, {
      createUser: async () => {
        // EmailのConflictエラーを返す
        return { reason: 'email', message: 'dummy' };
      },
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        return;
      },
      createMailContentFromEmailConfirm: async () => {
        return { html: '', text: '', subject: '' };
      },
    });

    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@example.com';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'admin none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        // EmailのConflictエラーを返す
        return { reason: 'email', message: 'dummy' };
      },
    });
    overrideSendgridService(service, {});
    overrideBlobstorageService(service, {});

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010301'));
      } else {
        fail();
      }
    }

    // DB内が想定通りになっているか確認
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(0);
    const users = await getUsers(source);
    expect(users.length).toBe(0);
  });
  it('アカウントを作成がDBへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2Cユーザーを削除され、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@example.com';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: externalId };
      },
      deleteUser: jest.fn(),
    });
    overrideSendgridService(service, {});

    overrideAccountsRepositoryService(service, {
      createAccount: async () => {
        throw new Error();
      },
    });

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
    // DB内が想定通りになっているか確認
    // DBのデータ作成で失敗しているので、DB内は空
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(0);
    const users = await getUsers(source);
    expect(users.length).toBe(0);
    const sortCriteria = await getSortCriteria(source);
    expect(sortCriteria.length).toBe(0);
    // ADB2Cユーザー削除メソッドが呼ばれているか確認
    expect(b2cService.deleteUser).toBeCalledWith(
      externalId,
      makeContext('uuid'),
    );
  });
  it('アカウントを作成がDBへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、ADB2Cユーザー削除で失敗した場合、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@dummy.dummy';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: externalId };
      },
      deleteUser: jest.fn().mockRejectedValue(new Error()),
    });
    overrideSendgridService(service, {});

    overrideAccountsRepositoryService(service, {
      createAccount: async () => {
        throw new Error();
      },
    });

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
    // DB内が想定通りになっているか確認
    // DBのデータ作成で失敗しているので、DB内は空
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(0);
    const users = await getUsers(source);
    expect(users.length).toBe(0);
    const sortCriteria = await getSortCriteria(source);
    expect(sortCriteria.length).toBe(0);
    // ADB2Cユーザー削除メソッドが呼ばれているか確認
    expect(b2cService.deleteUser).toBeCalledWith(
      externalId,
      makeContext('uuid'),
    );
  });

  it('アカウントを作成がBlobStorageへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2C,DB上のデータが削除され、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    b2cService.deleteUser = jest.fn(); // リカバリ処理の確認のため、deleteUserをモック化
    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@dummy.dummy';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: externalId };
      },
      deleteUser: async () => {
        return;
      },
    });
    overrideSendgridService(service, {});
    overrideBlobstorageService(service, {
      createContainer: async () => {
        throw new Error();
      },
    });

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
    // DB内が想定通りになっているか確認
    // リカバリ処理が走っているため、アカウント・ユーザーは削除されている
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(0);
    const users = await getUsers(source);
    expect(users.length).toBe(0);
    const sortCriteria = await getSortCriteria(source);
    expect(sortCriteria.length).toBe(0);
    // ADB2Cユーザー削除メソッドが呼ばれているか確認
    expect(b2cService.deleteUser).toBeCalledWith(
      externalId,
      makeContext('uuid'),
    );
  });

  it('アカウントを作成がBlobStorageへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@dummy.dummy';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: externalId };
      },
      deleteUser: jest.fn().mockRejectedValue(new Error()),
    });
    overrideSendgridService(service, {});
    overrideBlobstorageService(service, {
      createContainer: async () => {
        throw new Error();
      },
    });
    overrideAccountsRepositoryService(service, {
      deleteAccount: async () => {
        throw new Error();
      },
    });

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
    // DB内が想定通りになっているか確認
    // DB上のデータのリカバリ処理に失敗したため、DB上のデータは削除されない
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(1);
    const users = await getUsers(source);
    expect(users.length).toBe(1);
    const sortCriteria = await getSortCriteria(source);
    expect(sortCriteria.length).toBe(1);
    // ADB2Cユーザー削除メソッドが呼ばれているか確認
    expect(b2cService.deleteUser).toBeCalledWith(
      externalId,
      makeContext('uuid'),
    );
  });

  it('アカウントを作成がSendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2C,DB上のデータとBlobストレージのコンテナが削除され、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const blobstorageService =
      module.get<BlobstorageService>(BlobstorageService);
    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@dummy.dummy';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async (
        _context: Context,
        _email: string,
        _password: string,
        _username: string,
      ) => {
        // ユーザー作成時に指定したパラメータが正しく渡されていることを確認
        expect(email).toEqual(_email);
        expect(username).toEqual(_username);

        return { sub: externalId };
      },
      deleteUser: jest.fn(),
    });
    overrideSendgridService(service, {
      sendMail: async () => {
        throw new Error();
      },
      createMailContentFromEmailConfirm: async () => {
        return {
          html: 'dummy_html',
          subject: 'dummy_subject',
          text: 'dummy_text',
        };
      },
    });
    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
      deleteContainer: jest.fn(),
    });
    overrideAccountsRepositoryService(service, {});

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
    // DB内が想定通りになっているか確認
    // リカバリ処理によってADB2C,DB上のデータとBlobストレージのコンテナが削除される
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(0);
    const users = await getUsers(source);
    expect(users.length).toBe(0);
    const sortCriteria = await getSortCriteria(source);
    expect(sortCriteria.length).toBe(0);
    // ADB2Cユーザー削除メソッドが呼ばれているか確認
    expect(b2cService.deleteUser).toBeCalledWith(
      externalId,
      makeContext('uuid'),
    );
    // Blobストレージのコンテナ削除メソッドが呼ばれているか確認
    expect(blobstorageService.deleteContainer).toBeCalledWith(
      makeContext('uuid'),
      1, //新規作成したアカウントのID
      country,
    );
  });

  it('アカウントを作成がSendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const blobstorageService =
      module.get<BlobstorageService>(BlobstorageService);
    const externalId = 'test_external_id';
    const companyName = 'test_company_name';
    const country = 'US';
    const dealerAccountId = 1;
    const email = 'dummy@dummy.dummy';
    const password = 'dummy_password';
    const username = 'dummy_username';
    const role = 'none';
    const acceptedTermsVersion = '1.0.0';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: externalId };
      },
      deleteUser: jest.fn().mockRejectedValue(new Error()),
    });
    overrideSendgridService(service, {
      sendMail: async () => {
        throw new Error();
      },
      createMailContentFromEmailConfirm: async () => {
        return {
          html: 'dummy_html',
          subject: 'dummy_subject',
          text: 'dummy_text',
        };
      },
    });
    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
      deleteContainer: jest
        .fn()
        .mockRejectedValue(new Error('BlobStorage Error')),
    });
    overrideAccountsRepositoryService(service, {
      deleteAccount: async () => {
        throw new Error();
      },
    });

    try {
      await service.createAccount(
        makeContext('uuid'),
        companyName,
        country,
        dealerAccountId,
        email,
        password,
        username,
        role,
        acceptedTermsVersion,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }
    // DB内が想定通りになっているか確認
    // リカバリ処理によってADB2C,DB上のデータとBlobストレージのコンテナが削除されない
    const accounts = await getAccounts(source);
    expect(accounts.length).toBe(1);
    const users = await getUsers(source);
    expect(users.length).toBe(1);
    const sortCriteria = await getSortCriteria(source);
    expect(sortCriteria.length).toBe(1);
    // ADB2Cユーザー削除メソッドが呼ばれているか確認
    expect(b2cService.deleteUser).toBeCalledWith(
      externalId,
      makeContext('uuid'),
    );
    // Blobストレージのコンテナ削除メソッドが呼ばれているか確認
    expect(blobstorageService.deleteContainer).toBeCalledWith(
      makeContext('uuid'),
      1, //新規作成したアカウントのID
      country,
    );
  });
});

describe('createPartnerAccount', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });

  it('パートナーを追加できる', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const adminExternalId = 'ADMIN0001';

    // 第一階層のアカウントを作成
    const { accountId: parentAccountId, tier } =
      await createAccountAndAdminUser(source, adminExternalId);

    const context = makeContext('uuid');
    const companyName = 'test_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const adminName = 'partner_admin';
    const pertnerExternalId = 'PARTNER0001';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: pertnerExternalId };
      },
    });

    overrideSendgridService(service, {
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
      sendMail: async () => {
        return;
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
    }

    const { accountId } = await service.createPartnerAccount(
      context,
      companyName,
      country,
      email,
      adminName,
      adminExternalId,
      tier,
    );

    // DB上に作成されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(2);

      const createdUser = await getUserFromExternalID(
        source,
        pertnerExternalId,
      );
      const createdAccount = await getAccount(source, accountId);
      expect(createdAccount.company_name).toBe(companyName);
      expect(createdAccount.country).toBe(country);
      expect(createdAccount.parent_account_id).toBe(parentAccountId);
      expect(createdAccount.tier).toBe(2);
      expect(createdAccount.primary_admin_user_id).toBe(createdUser.id);
      expect(createdAccount.secondary_admin_user_id).toBe(null);
    }
  });

  it('Azure AD B2Cへの接続に失敗した結果アカウントの追加に失敗した場合、エラーとなる(500エラー)', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const adminExternalId = 'ADMIN0001';

    // 第一階層のアカウントを作成
    const { tier } = await createAccountAndAdminUser(source, adminExternalId);

    const context = makeContext('uuid');
    const companyName = 'test_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const adminName = 'partner_admin';

    overrideAdB2cService(service, {
      createUser: async () => {
        throw new Error();
      },
    });

    overrideSendgridService(service, {
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
      sendMail: async () => {
        return;
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        adminName,
        adminExternalId,
        tier,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    // DB上に作成されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
    }
  });

  it('DBへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2Cユーザーを削除され、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const adminExternalId = 'ADMIN0001';

    // 第一階層のアカウントを作成
    const { tier } = await createAccountAndAdminUser(source, adminExternalId);

    const context = makeContext('uuid');
    const companyName = 'test_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const adminName = 'partner_admin';
    const partnerExternalId = 'partner_external_id';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: partnerExternalId };
      },
      deleteUser: jest.fn(),
    });

    overrideSendgridService(service, {
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
      sendMail: async () => {
        return;
      },
    });
    overrideAccountsRepositoryService(service, {
      createAccount: async () => {
        throw new Error('DB Error');
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        adminName,
        adminExternalId,
        tier,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    // DB上に作成されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      expect(accounts[0].tier).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
      expect(users[0].external_id).toBe(adminExternalId);
      // ADB2Cユーザー削除メソッドが呼ばれているか確認
      expect(b2cService.deleteUser).toBeCalledWith(partnerExternalId, context);
    }
  });

  it('DBへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、ADB2Cユーザー削除で失敗した場合、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const adminExternalId = 'ADMIN0001';

    // 第一階層のアカウントを作成
    const { tier } = await createAccountAndAdminUser(source, adminExternalId);

    const context = makeContext('uuid');
    const companyName = 'test_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const adminName = 'partner_admin';
    const partnerExternalId = 'partner_external_id';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: partnerExternalId };
      },
      deleteUser: jest.fn().mockRejectedValue(new Error('ADB2C Error')),
    });

    overrideSendgridService(service, {
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
      sendMail: async () => {
        return;
      },
    });
    overrideAccountsRepositoryService(service, {
      createAccount: async () => {
        throw new Error('DB Error');
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        adminName,
        adminExternalId,
        tier,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    // DB上に作成されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      expect(accounts[0].tier).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
      expect(users[0].external_id).toBe(adminExternalId);
      // ADB2Cユーザー削除メソッドが呼ばれているか確認
      expect(b2cService.deleteUser).toBeCalledWith(partnerExternalId, context);
    }
  });

  it('BlobStorageへの通信失敗が原因でアカウントの追加に失敗した場合、リカバリ処理としてADB2C,DB上のデータが削除され、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);

    const parentExternalId = 'parent_external_id';

    await createAccountAndAdminUser(source, parentExternalId);

    const context = makeContext(parentExternalId);
    const partnerExternalId = 'partner_external_id';
    const companyName = 'partner_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const username = 'partner_username';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: partnerExternalId };
      },
      deleteUser: jest.fn(),
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        return;
      },
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        throw new Error();
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        username,
        parentExternalId,
        TIERS.TIER1,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      expect(accounts[0].tier).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
      expect(users[0].external_id).toBe(parentExternalId);
      // ADB2Cユーザー削除メソッドが呼ばれているか確認
      expect(b2cService.deleteUser).toBeCalledWith(partnerExternalId, context);
    }
  });

  it('BlobStorageへの通信失敗が原因でアカウントの追加に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);

    const parentExternalId = 'parent_external_id';

    await createAccountAndAdminUser(source, parentExternalId);

    const context = makeContext(parentExternalId);
    const partnerExternalId = 'partner_external_id';
    const companyName = 'partner_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const username = 'partner_username';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: partnerExternalId };
      },
      deleteUser: jest.fn().mockRejectedValue(new Error('ADB2C Error')),
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        return;
      },
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
    });

    overrideAccountsRepositoryService(service, {
      deleteAccount: jest.fn().mockRejectedValue(new Error('DB Error')),
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        throw new Error();
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        username,
        parentExternalId,
        TIERS.TIER1,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    {
      // DB上に作成されたデータが想定通りであるか確認
      // リカバリ処理が失敗した場合、DB上のデータは削除されない
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(2);
      expect(accounts[0].tier).toBe(1);
      expect(accounts[1].tier).toBe(2);
      const users = await getUsers(source);
      expect(users.length).toBe(2);
      expect(users[0].external_id).toBe(parentExternalId);
      expect(users[1].external_id).toBe(partnerExternalId);
      // ADB2Cユーザー削除メソッドが呼ばれているか確認
      expect(b2cService.deleteUser).toBeCalledWith(partnerExternalId, context);
    }
  });

  it('SendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理としてADB2C,DB上のデータ,コンテナが削除され、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const blobstorageService =
      module.get<BlobstorageService>(BlobstorageService);

    const parentExternalId = 'parent_external_id';

    await createAccountAndAdminUser(source, parentExternalId);

    const context = makeContext(parentExternalId);
    const partnerExternalId = 'partner_external_id';
    const companyName = 'partner_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const username = 'partner_username';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: partnerExternalId };
      },
      deleteUser: jest.fn(),
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        throw new Error();
      },
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
      deleteContainer: jest.fn(),
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        username,
        parentExternalId,
        TIERS.TIER1,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    {
      // DB上に作成されたデータが想定通りであるか確認
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      expect(accounts[0].tier).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
      expect(users[0].external_id).toBe(parentExternalId);
      // ADB2Cユーザー削除メソッドが呼ばれているか確認
      expect(b2cService.deleteUser).toBeCalledWith(partnerExternalId, context);
      // コンテナ削除メソッドが呼ばれているか確認
      expect(blobstorageService.deleteContainer).toBeCalledWith(
        context,
        2, //新規作成したパートナーのアカウントID
        country,
      );
    }
  });

  it('SendGridへの通信失敗によって500エラーが発生した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、500エラーが返却される', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const b2cService = module.get<AdB2cService>(AdB2cService);
    const blobstorageService =
      module.get<BlobstorageService>(BlobstorageService);

    const parentExternalId = 'parent_external_id';

    await createAccountAndAdminUser(source, parentExternalId);

    const context = makeContext(parentExternalId);
    const partnerExternalId = 'partner_external_id';
    const companyName = 'partner_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const username = 'partner_username';

    overrideAdB2cService(service, {
      createUser: async () => {
        return { sub: partnerExternalId };
      },
      deleteUser: jest.fn().mockRejectedValue(new Error('ADB2C Error')),
    });

    overrideSendgridService(service, {
      sendMail: async () => {
        throw new Error();
      },
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
    });

    overrideAccountsRepositoryService(service, {
      deleteAccount: jest.fn().mockRejectedValue(new Error('DB Error')),
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
      deleteContainer: jest.fn().mockRejectedValue(new Error('Blob Error')),
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        username,
        parentExternalId,
        TIERS.TIER1,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
        expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
      } else {
        fail();
      }
    }

    {
      // DB上に作成されたデータが想定通りであるか確認
      // リカバリ処理が失敗したため、DB上のデータは削除されていない
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(2);
      expect(accounts[0].tier).toBe(1);
      expect(accounts[1].tier).toBe(2);
      const users = await getUsers(source);
      expect(users.length).toBe(2);
      expect(users[0].external_id).toBe(parentExternalId);
      expect(users[1].external_id).toBe(partnerExternalId);
      // ADB2Cユーザー削除メソッドが呼ばれているか確認
      expect(b2cService.deleteUser).toBeCalledWith(partnerExternalId, context);
      // コンテナ削除メソッドが呼ばれているか確認
      expect(blobstorageService.deleteContainer).toBeCalledWith(
        context,
        2, //新規作成したパートナーのアカウントID
        country,
      );
    }
  });

  it('既に登録済みのメールアドレスが原因でアカウントの追加に失敗した場合、エラーとなる(400エラー)', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const adminExternalId = 'ADMIN0001';

    // 第一階層のアカウントを作成
    const { tier } = await createAccountAndAdminUser(source, adminExternalId);

    const context = makeContext('uuid');
    const companyName = 'test_company_name';
    const country = 'US';
    const email = 'partner@example.com';
    const adminName = 'partner_admin';

    overrideAdB2cService(service, {
      createUser: async () => {
        // Conflictエラーを返す
        return { reason: 'email', message: 'dummy' };
      },
    });

    overrideSendgridService(service, {
      createMailContentFromEmailConfirmForNormalUser: async () => {
        return { html: '', text: '', subject: '' };
      },
      sendMail: async () => {
        return;
      },
    });

    overrideBlobstorageService(service, {
      createContainer: async () => {
        return;
      },
    });

    // DB上に用意されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
    }

    try {
      await service.createPartnerAccount(
        context,
        companyName,
        country,
        email,
        adminName,
        adminExternalId,
        tier,
      );
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010301'));
      } else {
        fail();
      }
    }

    // DB上に作成されたデータが想定通りであるか確認
    {
      const accounts = await getAccounts(source);
      expect(accounts.length).toBe(1);
      const users = await getUsers(source);
      expect(users.length).toBe(1);
    }
  });
});

describe('AccountsService', () => {
  it('アカウントに紐づくライセンス情報を取得する', async () => {
    const accountId = 1;
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );
    expect(await service.getLicenseSummary(accountId)).toEqual(
      expectedAccountLisenceCounts,
    );
  });

  it('ライセンス情報が取得できない場合、エラーとなる', async () => {
    const accountId = 1;
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    accountsRepositoryMockValue.getLicenseSummaryInfo = null;
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );
    await expect(service.getLicenseSummary(accountId)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });

  it('アカウントに紐づくタイピストユーザーを取得できる', async () => {
    const externalId = 'externalId';
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );
    expect(await service.getTypists(externalId)).toEqual([
      { id: 1, name: 'Typist1' },
      { id: 2, name: 'Typist2' },
      { id: 3, name: 'Typist3' },
    ]);
  });
  it('ユーザー取得に失敗した場合、エラーとなる', async () => {
    const externalId = 'externalId';
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    usersRepositoryMockValue.findTypistUsers = new Error();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );
    await expect(service.getTypists(externalId)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
  it('ADB2Cからのユーザーの取得に失敗した場合、エラーとなる', async () => {
    const externalId = 'externalId';
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    adb2cParam.getUsers = new Error();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );
    await expect(service.getTypists(externalId)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });

  it('アカウントに紐づくユーザーグループを取得できる', async () => {
    const externalId = 'externalId';
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );

    expect(await service.getTypistGroups(externalId)).toEqual([
      { id: 1, name: 'GroupA' },
      { id: 2, name: 'GroupB' },
    ]);
  });
  it('ユーザーの取得に失敗した場合、エラーとなること', async () => {
    const externalId = 'externalId';
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    usersRepositoryMockValue.findUserByExternalId = new Error('DB failed');
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );

    await expect(service.getTypistGroups(externalId)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
  it('ユーザーグループの取得に失敗した場合、エラーとなること', async () => {
    const externalId = 'externalId';
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    userGroupsRepositoryMockValue.getUserGroups = new Error('DB failed');
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );

    await expect(service.getTypistGroups(externalId)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
});

const expectedAccountLisenceCounts = {
  totalLicense: 1,
  allocatedLicense: 2,
  reusableLicense: 3,
  freeLicense: 4,
  expiringWithin14daysLicense: 5,
  issueRequesting: 6,
  numberOfRequesting: 7,
  shortage: 0,
  storageSize: 0,
  usedSize: 0,
  isStorageAvailable: false,
};

describe('getPartnerAccount', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });

  it('パラメータのアカウント自身と子アカウントに紐つくライセンス情報を取得する', async () => {
    const module = await makeTestingModule(source);

    // 親アカウントと子アカウント2つ作成
    const { accountId: parentAccountId } = await createAccount(
      source,
      0,
      1,
      'PARENTCORP',
    );
    const { accountId: childAccountId1 } = await createAccount(
      source,
      parentAccountId,
      2,
      'CHILDCORP1',
    );
    const { accountId: childAccountId2 } = await createAccount(
      source,
      parentAccountId,
      2,
      'CHILDCORP2',
    );

    // 第二にリクエストを投げる用の第三を作成
    const { accountId: childAccountId3 } = await createAccount(
      source,
      childAccountId1,
      3,
      'CHILDCORP3',
    );
    const { accountId: childAccountId4 } = await createAccount(
      source,
      childAccountId2,
      3,
      'CHILDCORP4',
    );

    // 所有ライセンスを追加（親：3、子1：1、子2：2）
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);

    await createLicense(source, childAccountId1);

    await createLicense(source, childAccountId2);
    await createLicense(source, childAccountId2);

    // ライセンス注文を追加（子1→親：10ライセンス、子2→親：5ライセンス）
    await createLicenseOrder(
      source,
      childAccountId1,
      parentAccountId,
      10,
      'TEST222',
    );
    await createLicenseOrder(source, childAccountId2, parentAccountId, 5);

    // ライセンス注文を追加（子3→子1：10ライセンス、子4→子2：10ライセンス）
    await createLicenseOrder(source, childAccountId3, childAccountId1, 10);
    await createLicenseOrder(source, childAccountId4, childAccountId2, 10);

    const service = module.get<AccountsService>(AccountsService);
    const accountId = parentAccountId;
    const offset = 0;
    const limit = 20;

    const response = await service.getPartnerLicenses(limit, offset, accountId);

    expect(response.total).toBe(2);

    expect(response.ownPartnerLicense.companyName).toBe('PARENTCORP');
    expect(response.ownPartnerLicense.tier).toBe(1);
    expect(response.ownPartnerLicense.stockLicense).toBe(3);
    expect(response.ownPartnerLicense.issuedRequested).toBe(15);
    expect(response.ownPartnerLicense.shortage).toBe(12);

    expect(response.childrenPartnerLicenses[0].companyName).toBe('CHILDCORP1');
    expect(response.childrenPartnerLicenses[0].tier).toBe(2);
    expect(response.childrenPartnerLicenses[0].stockLicense).toBe(1);
    expect(response.childrenPartnerLicenses[0].issueRequesting).toBe(10);
    expect(response.childrenPartnerLicenses[0].shortage).toBe(9);

    expect(response.childrenPartnerLicenses[1].companyName).toBe('CHILDCORP2');
    expect(response.childrenPartnerLicenses[1].tier).toBe(2);
    expect(response.childrenPartnerLicenses[1].stockLicense).toBe(2);
    expect(response.childrenPartnerLicenses[1].shortage).toBe(8);
  });
});

describe('getPartnerAccount', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });

  it('パラメータのアカウント自身と子アカウントに紐つくライセンス情報を取得する(第五のshortage確認)', async () => {
    const module = await makeTestingModule(source);

    // 親アカウントと子アカウント２つ作成
    const { accountId: parentAccountId } = await createAccount(
      source,
      0,
      4,
      'PARENTCORP',
    );
    const { accountId: childAccountId1 } = await createAccount(
      source,
      parentAccountId,
      5,
      'CHILDCORP1',
    );
    const { accountId: childAccountId2 } = await createAccount(
      source,
      parentAccountId,
      5,
      'CHILDCORP2',
    );
    const { accountId: childAccountId3 } = await createAccount(
      source,
      parentAccountId,
      5,
      'CHILDCORP3',
    );

    // 有効期限が14日後のライセンスを追加（5ライセンス）
    const expiryDate = new Date();
    expiryDate.setDate(expiryDate.getDate() + 14);
    expiryDate.setHours(23, 59, 59, 999);
    for (let i = 0; i < 5; i++) {
      await createLicenseSetExpiryDateAndStatus(
        source,
        childAccountId1,
        expiryDate,
        'Allocated',
      );
      await createLicenseSetExpiryDateAndStatus(
        source,
        childAccountId2,
        expiryDate,
        'Allocated',
      );
    }

    // 有効期限が15日後のライセンスを追加
    expiryDate.setDate(expiryDate.getDate() + 15);
    expiryDate.setHours(23, 59, 59, 999);
    await createLicenseSetExpiryDateAndStatus(
      source,
      childAccountId3,
      expiryDate,
      'Allocated',
    );

    // 有効期限が迫っていないライセンスを追加（子1：各ステータスのライセンスを1つずつ、計4つ）
    const status = ['Unallocated', 'Allocated', 'Reusable', 'Deleted'];
    status.forEach(async (element) => {
      await createLicenseSetExpiryDateAndStatus(
        source,
        childAccountId1,
        new Date(2500, 1, 1, 23, 59, 59),
        element,
      );
    });

    // 有効期限が迫っていないライセンスを追加（子2：Unallocatedを10件）
    for (let i = 0; i < 10; i++) {
      await createLicenseSetExpiryDateAndStatus(
        source,
        childAccountId2,
        new Date(2500, 1, 1, 23, 59, 59),
        'Unallocated',
      );
    }

    // 有効期限未設定のライセンスを1件追加（子1）
    await createLicense(source, childAccountId1);

    const service = module.get<AccountsService>(AccountsService);
    const accountId = parentAccountId;
    const offset = 0;
    const limit = 20;

    const response = await service.getPartnerLicenses(limit, offset, accountId);

    // 有効期限間近（5件）＋ 有効期限間近でない（3件）'Unallocated', 'Allocated', 'Reusable'＋ 有効期限未設定（1件）　→　9件
    expect(response.childrenPartnerLicenses[0].stockLicense).toBe(9);
    // 有効期限間近（5件） - {有効期限間近でない未割当（2件）'Unallocated', 'Reusable'＋ 有効期限未設定（1件）}　→　2件
    expect(response.childrenPartnerLicenses[0].shortage).toBe(2);

    // 有効期限間近（5件）＋ 有効期限間近でない（10件）　→　15件
    expect(response.childrenPartnerLicenses[1].stockLicense).toBe(15);
    // 有効期限間近（5件）- （有効期限間近でない未割当（10件）'Unallocated'　＋　有効期限未設定（1件））　→　-5件　→　0件
    expect(response.childrenPartnerLicenses[1].shortage).toBe(0);

    expect(response.childrenPartnerLicenses[2].stockLicense).toBe(1);
    // 有効期限が15日後のものはshortageにカウントされない
    expect(response.childrenPartnerLicenses[2].shortage).toBe(0);
  });
});

describe('getOrderHistories', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });

  it('指定したアカウントIDの注文履歴情報を取得できる', async () => {
    const module = await makeTestingModule(source);

    const targetAccountId = 10;
    const targetParentAccountId = 14;
    const now = new Date(); // 現在の時刻を取得

    // 注文履歴情報の作成
    await createLicenseOrder(
      source,
      targetAccountId,
      targetParentAccountId,
      10,
      'TEST001',
      new Date(now.getTime() + 60 * 60 * 1000),
    );
    await createLicenseOrder(
      source,
      targetAccountId,
      targetParentAccountId,
      10,
      'TEST002',
      new Date(now.getTime() + 60 * 60 * 1000 * 2),
    );
    await createLicenseOrder(
      source,
      targetAccountId,
      targetParentAccountId,
      10,
      'TEST003',
      new Date(now.getTime() + 60 * 60 * 1000 * 3),
    );
    await createLicenseOrder(
      source,
      targetAccountId,
      targetParentAccountId,
      10,
      'TEST004',
      new Date(now.getTime() + 60 * 60 * 1000 * 4),
    );
    await createLicenseOrder(
      source,
      targetAccountId,
      targetParentAccountId,
      10,
      'TEST005',
      new Date(now.getTime() + 60 * 60 * 1000 * 5),
    );

    const service = module.get<AccountsService>(AccountsService);
    const accountId = targetAccountId;
    const offset = 1;
    const limit = 2;

    const response = await service.getOrderHistories(limit, offset, accountId);

    expect(response.total).toBe(5);

    expect(response.orderHistories[0].poNumber).toBe('TEST004');
    expect(response.orderHistories[1].poNumber).toBe('TEST003');
  });
  it('注文履歴情報の取得に失敗した場合、エラーとなる', async () => {
    const limit = 0;
    const offset = 0;
    const accountId = 0;
    const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
    usersRepositoryMockValue.findTypistUsers = new Error();
    const userGroupsRepositoryMockValue =
      makeDefaultUserGroupsRepositoryMockValue();
    const adb2cParam = makeDefaultAdB2cMockValue();
    const accountsRepositoryMockValue =
      makeDefaultAccountsRepositoryMockValue();
    const configMockValue = makeDefaultConfigValue();
    const sendGridMockValue = makeDefaultSendGridlValue();
    const blobStorageMockValue = makeBlobStorageServiceMockValue();
    const licensesRepositoryMockValue =
      makeDefaultLicensesRepositoryMockValue();
    licensesRepositoryMockValue.getLicenseOrderHistoryInfo = new Error(
      'DB failed',
    );
    const service = await makeAccountsServiceMock(
      accountsRepositoryMockValue,
      usersRepositoryMockValue,
      userGroupsRepositoryMockValue,
      adb2cParam,
      configMockValue,
      sendGridMockValue,
      blobStorageMockValue,
      licensesRepositoryMockValue,
    );
    await expect(
      service.getOrderHistories(limit, offset, accountId),
    ).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
});

describe('issueLicense', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });

  it('指定した注文を発行済みにする', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const now = new Date();
    // 親と子アカウントを作成する
    const { accountId: parentAccountId } = await createAccount(
      source,
      0,
      2,
      'PARENTCORP',
    );
    const { accountId: childAccountId } = await createAccount(
      source,
      parentAccountId,
      3,
      'CHILDCORP1',
    );
    // 親と子のユーザーを作成する
    const { externalId } = await createUser(
      source,
      parentAccountId,
      'userId-parent',
      'admin',
    );

    // 親のライセンスを作成する（3個）
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    // 子から親への注文を作成する（2個）
    await createLicenseOrder(
      source,
      childAccountId,
      parentAccountId,
      2,
      'TEST001',
      new Date(now.getTime() + 60 * 60 * 1000),
    );

    const context = makeContext('userId-parent');

    // 注文を発行済みにする
    await service.issueLicense(
      context,
      childAccountId,
      externalId,
      2,
      'TEST001',
    );

    const issuedLicenses = await source.getRepository(License).find({
      where: {
        account_id: childAccountId,
        status: 'Unallocated',
        type: 'NORMAL',
      },
    });
    expect(issuedLicenses.length).toEqual(2);
  });
  it('既に注文が発行済みの場合、エラーとなる', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const now = new Date();
    // 親と子アカウントを作成する
    const { accountId: parentAccountId } = await createAccount(
      source,
      0,
      2,
      'PARENTCORP',
    );
    const { accountId: childAccountId } = await createAccount(
      source,
      parentAccountId,
      3,
      'CHILDCORP1',
    );
    // 親と子のユーザーを作成する
    const { externalId } = await createUser(
      source,
      parentAccountId,
      'userId-parent',
      'admin',
    );

    // 親のライセンスを作成する（3個）
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    // 子から親への注文を作成する（2個）
    await createLicenseOrder(
      source,
      childAccountId,
      parentAccountId,
      2,
      'TEST001',
      new Date(now.getTime() + 60 * 60 * 1000),
    );

    const context = makeContext('userId-parent');

    // 注文を発行済みにする
    await service.issueLicense(
      context,
      childAccountId,
      externalId,
      2,
      'TEST001',
    );

    //再度同じ処理を行う
    await expect(
      service.issueLicense(context, childAccountId, externalId, 2, 'TEST001'),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010803'), HttpStatus.BAD_REQUEST),
    );
  });
  it('ライセンスが不足している場合、エラーとなる', async () => {
    const module = await makeTestingModule(source);
    const service = module.get<AccountsService>(AccountsService);
    const now = new Date();
    // 親と子アカウントを作成する
    const { accountId: parentAccountId } = await createAccount(
      source,
      0,
      2,
      'PARENTCORP',
    );
    const { accountId: childAccountId } = await createAccount(
      source,
      parentAccountId,
      3,
      'CHILDCORP1',
    );
    // 親と子のユーザーを作成する
    const { externalId } = await createUser(
      source,
      parentAccountId,
      'userId-parent',
      'admin',
    );

    // 親のライセンスを作成する（3個）
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    await createLicense(source, parentAccountId);
    // 子から親への注文を作成する（4個）
    await createLicenseOrder(
      source,
      childAccountId,
      parentAccountId,
      4,
      'TEST001',
      new Date(now.getTime() + 60 * 60 * 1000),
    );

    const context = makeContext('userId-parent');

    // 注文を発行済みにする
    await expect(
      service.issueLicense(context, childAccountId, externalId, 2, 'TEST001'),
    ).rejects.toEqual(
      new HttpException(makeErrorResponse('E010804'), HttpStatus.BAD_REQUEST),
    );
  });
});

describe('getDealers', () => {
  let source: DataSource = null;
  beforeEach(async () => {
    source = new DataSource({
      type: 'sqlite',
      database: ':memory:',
      logging: false,
      entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
      synchronize: true, // trueにすると自動的にmigrationが行われるため注意
    });
    return source.initialize();
  });

  afterEach(async () => {
    await source.destroy();
    source = null;
  });
  it('Dealerを取得できる', async () => {
    const module = await makeTestingModule(source);
    const { accountId: accountId_1 } = await createAccount(
      source,
      1,
      TIERS.TIER4,
      'DEALER_1',
    );
    const { accountId: accountId_2 } = await createAccount(
      source,
      2,
      TIERS.TIER4,
      'DEALER_2',
    );
    const { accountId: accountId_3 } = await createAccount(
      source,
      3,
      TIERS.TIER4,
      'DEALER_3',
    );
    const service = module.get<AccountsService>(AccountsService);

    expect(await service.getDealers()).toEqual({
      dealers: [
        {
          country: 'JP',
          id: accountId_1,
          name: 'DEALER_1',
        },
        {
          country: 'JP',
          id: accountId_2,
          name: 'DEALER_2',
        },
        {
          country: 'JP',
          id: accountId_3,
          name: 'DEALER_3',
        },
      ],
    });
  });
  it('0件でもDealerを取得できる', async () => {
    const module = await makeTestingModule(source);

    const service = module.get<AccountsService>(AccountsService);

    expect(await service.getDealers()).toEqual({
      dealers: [],
    });
  });
});
