import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import {
  makeAuthServiceMock,
  makeDefaultAdB2cMockValue,
  makeDefaultConfigValue,
  makeDefaultGetPublicKeyFromJwk,
} from './test/auth.service.mock';
import { DataSource } from 'typeorm';
import { makeContext } from '../../common/log';
import { makeTestingModule } from '../../common/test/modules';
import { getAccount, makeTestAccount } from '../../common/test/utility';
import { AuthService } from './auth.service';
import {
  createTermInfo,
  deleteAccount,
  updateAccountDelegationPermission,
} from './test/utility';
import { v4 as uuidv4 } from 'uuid';
import { TIERS, USER_ROLES } from '../../constants';
import { decode, isVerifyError } from '../../common/jwt';
import { RefreshToken, AccessToken } from '../../common/token';
import { truncateAllTable } from '../../common/test/init';

describe('AuthService', () => {
  it('IDトークンの検証とペイロードの取得に成功する', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
    service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';

    const context = makeContext(`uuidv4`, 'requestId');
    expect(await service.getVerifiedIdToken(context, token)).toEqual(
      idTokenPayload,
    );
  });

  it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    const token = 'invalid.id.token';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(makeErrorResponse('E000101'), HttpStatus.UNAUTHORIZED),
    );
  });

  it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
    service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.r9x61Mf1S2qFgU_QDKB6tRFBmTQXyOEtpoacOlL_bQzFz1t3GsxMy6SJIvQQ-LtDgylQ1UCdMFiRuy4V8nyLuME0fR-9IkKsboGvwllHB_Isai3XFoja0jpDHMVby1m0B3Z9xOTb7YsaQGyEH-qs1TtnRm6Ny98h4Po80nK8HGefQZHBOlfQN_B1LiHwI3nLXV18NL-4olKXj2NloNRYtnWM0PaqDQcGvZFaSNvtrSYpo9ddD906QWDGVOQ7WvGSUgdNCoxX8Lb3r2-VSj6n84jpb-Y1Fz-GhLluNglAsBhasnJfUIvCIO3iG5pRyTYjHFAVHmzjr8xMOmhS3s41Jw';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(makeErrorResponse('E000102'), HttpStatus.UNAUTHORIZED),
    );
  });

  it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
    service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.fX2Gbd7fDPNE3Lw-xbum_5CVqQYqEmMhv_v5u8A-U81pmPD2P5rsJEJx66ns1taFLVaE3j9_OzotxrqjqqQqbACkagGcN5wvA3_ZIxyqmhrKYFJc53ZcO7d0pFWiQlluNBI_pnFNDlSMB2Ut8Th5aiPy2uamBM9wC99bcjo7HkHvTKBf6ljU6rPKoD51qGDWqNxjoH-hdSJ29wprvyxyk_yX6dp-cxXUj5DIgXYQuIZF71rdiPtGlAiyTBns8rS2QlEEXapZVlvYrK4mkpUXVDA7ifD8q6gAC2BStqHeys7CGp2MbV4ZwKCVbAUbMs6Tboh8rADZvQhuTEq7qlhZ-w';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(makeErrorResponse('E000103'), HttpStatus.UNAUTHORIZED),
    );
  });

  it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(makeErrorResponse('E000104'), HttpStatus.UNAUTHORIZED),
    );
  });

  it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    //JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
    service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaW52bGlkX2lzc3VlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.0bp3e1mDG78PX3lo8zgOLXGenIqG_Vi6kw7CbwauAQM-cnUZ_aVCoJ_dAv_QmPElOQKcCkRrAvAZ91FwuHDlBGuuDqx8OwqN0VaD-4NPouoAswj-9HNvBm8gUn-pGaXkvWt_72UdCJavZJjDj_RHur8y8kFt5Qeab3mUP2x-uNcV2Q2x3M_IIfcRiIZkRZm_azKfiVIy7tzoUFLDss97y938aR8imMVxazoSQvj7RWIWylgeRr9yVt7qYl18cnEVL0IGtslFbqhfNsiEmRCMsttm5kXs7E9B0bhhUe_xbJW9VumQ6G7dgMrswevp_jRgbpWJoZsgErtqIRl9Tc9ikA';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(makeErrorResponse('E000105'), HttpStatus.UNAUTHORIZED),
    );
  });

  it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。（メタデータ）', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    adb2cParam.getMetaData = new Error('failed get metadata');
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
  it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。（キーセット）', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    adb2cParam.getSignKeySets = new Error('failed get keyset');
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });

  it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => {
    const adb2cParam = makeDefaultAdB2cMockValue();
    const configMockValue = makeDefaultConfigValue();
    adb2cParam.getSignKeySets = [
      { kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' },
    ];
    const service = await makeAuthServiceMock(adb2cParam, configMockValue);
    const token =
      'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';

    const context = makeContext(`uuidv4`, 'requestId');
    await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
      new HttpException(
        makeErrorResponse('E009999'),
        HttpStatus.INTERNAL_SERVER_ERROR,
      ),
    );
  });
});

describe('checkIsAcceptedLatestVersion', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });
  it('同意済み利用規約バージョンが最新のときにチェックが通ること（第五）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin } = await makeTestAccount(source, {
      tier: 5,
    });
    const context = makeContext(uuidv4(), 'requestId');

    const idToken = {
      emails: [],
      sub: admin.external_id,
      exp: 0,
      iat: 0,
    };

    await createTermInfo(source, 'EULA', '1.0');
    await createTermInfo(source, 'PrivacyNotice', '1.0');
    await createTermInfo(source, 'DPA', '1.0');
    const result = await service.isAcceptedLatestVersion(context, idToken);
    expect(result).toBe(true);
  });

  it('同意済み利用規約バージョンが最新のときにチェックが通ること（第一～第四）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin } = await makeTestAccount(source, {
      tier: 4,
    });
    const context = makeContext(uuidv4(), 'requestId');

    const idToken = {
      emails: [],
      sub: admin.external_id,
      exp: 0,
      iat: 0,
    };

    await createTermInfo(source, 'EULA', '1.0');
    await createTermInfo(source, 'PrivacyNotice', '1.0');
    await createTermInfo(source, 'DPA', '1.0');
    const result = await service.isAcceptedLatestVersion(context, idToken);
    expect(result).toBe(true);
  });

  it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと（第五）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin } = await makeTestAccount(source, {
      tier: 5,
    });
    const context = makeContext(uuidv4(), 'requestId');

    const idToken = {
      emails: [],
      sub: admin.external_id,
      exp: 0,
      iat: 0,
    };

    await createTermInfo(source, 'EULA', '1.1');
    await createTermInfo(source, 'PrivacyNotice', '1.0');
    await createTermInfo(source, 'DPA', '1.0');
    const result = await service.isAcceptedLatestVersion(context, idToken);
    expect(result).toBe(false);
  });

  it('同意済み利用規約（EULA）バージョンが最新でないときにチェックが通らないこと（第一～第四）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin } = await makeTestAccount(source, {
      tier: 4,
    });
    const context = makeContext(uuidv4(), 'requestId');

    const idToken = {
      emails: [],
      sub: admin.external_id,
      exp: 0,
      iat: 0,
    };

    await createTermInfo(source, 'EULA', '1.1');
    await createTermInfo(source, 'PrivacyNotice', '1.0');
    await createTermInfo(source, 'DPA', '1.0');
    const result = await service.isAcceptedLatestVersion(context, idToken);
    expect(result).toBe(false);
  });

  it('同意済み利用規約バージョン（DPA）が最新でないときにチェックが通らないこと（第一～第四）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin } = await makeTestAccount(source, {
      tier: 4,
    });
    const context = makeContext(uuidv4(), 'requestId');

    const idToken = {
      emails: [],
      sub: admin.external_id,
      exp: 0,
      iat: 0,
    };

    await createTermInfo(source, 'EULA', '1.0');
    await createTermInfo(source, 'PrivacyNotice', '1.0');
    await createTermInfo(source, 'DPA', '1.1');
    const result = await service.isAcceptedLatestVersion(context, idToken);
    expect(result).toBe(false);
  });

  it('同意済みプライバシーポリシーが最新でないときにチェックが通らないこと（第一～第四）', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin } = await makeTestAccount(source, {
      tier: 4,
    });
    const context = makeContext(uuidv4(), 'requestId');

    const idToken = {
      emails: [],
      sub: admin.external_id,
      exp: 0,
      iat: 0,
    };

    await createTermInfo(source, 'EULA', '1.0');
    await createTermInfo(source, 'PrivacyNotice', '1.1');
    await createTermInfo(source, 'DPA', '1.0');
    const result = await service.isAcceptedLatestVersion(context, idToken);
    expect(result).toBe(false);
  });
});

describe('generateDelegationRefreshToken', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });
  it('代行操作が許可されたパートナーの代行操作用リフレッシュトークンを取得できること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    const { admin: partnerAdmin, account: partnerAccount } =
      await makeTestAccount(
        source,
        {
          tier: 5,
          parent_account_id: parentAccount.id,
          delegation_permission: true,
        },
        { role: USER_ROLES.NONE },
      );

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

    const delegationRefreshToken = await service.generateDelegationRefreshToken(
      context,
      parentAdmin.external_id,
      partnerAccount.id,
    );

    // 取得できた代行操作用リフレッシュトークンをデコード
    const decodeToken = decode<RefreshToken>(delegationRefreshToken);
    if (isVerifyError(decodeToken)) {
      fail();
    }

    expect(decodeToken.role).toBe('none admin');
    expect(decodeToken.tier).toBe(TIERS.TIER5);
    expect(decodeToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeToken.delegateUserId).toBe(parentAdmin.external_id);
  });
  it('代行操作が許可されていない場合、400エラーとなること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    const { account: partnerAccount } = await makeTestAccount(
      source,
      {
        tier: 5,
        parent_account_id: parentAccount.id,
        delegation_permission: false,
      },
      { role: USER_ROLES.NONE },
    );

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

    try {
      await service.generateDelegationRefreshToken(
        context,
        parentAdmin.external_id,
        partnerAccount.id,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010503'));
      } else {
        fail();
      }
    }
  });

  it('代行操作対象が存在しない場合、400エラーとなること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    await makeTestAccount(
      source,
      {
        tier: 5,
        parent_account_id: parentAccount.id,
        delegation_permission: false,
      },
      { role: USER_ROLES.NONE },
    );

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

    try {
      await service.generateDelegationRefreshToken(
        context,
        parentAdmin.external_id,
        9999,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
      } else {
        fail();
      }
    }
  });
});

describe('generateDelegationAccessToken', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

  afterAll(async () => {
    await source?.destroy();
    source = null;
  });
  it('代行操作用リフレッシュトークンから代行操作用アクセストークンを取得できること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    const { admin: partnerAdmin, account: partnerAccount } =
      await makeTestAccount(
        source,
        {
          tier: 5,
          parent_account_id: parentAccount.id,
          delegation_permission: true,
        },
        { role: USER_ROLES.NONE },
      );

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

    const delegationRefreshToken = await service.generateDelegationRefreshToken(
      context,
      parentAdmin.external_id,
      partnerAccount.id,
    );

    // 取得できた代行操作用リフレッシュトークンをデコード
    const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
    if (isVerifyError(decodeRefreshToken)) {
      fail();
    }

    expect(decodeRefreshToken.role).toBe('none admin');
    expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
    expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);

    const delegationAccessToken = await service.generateDelegationAccessToken(
      context,
      delegationRefreshToken,
    );

    // 取得できた代行操作用アクセストークンをデコード
    const decodeAccessToken = decode<AccessToken>(delegationAccessToken);
    if (isVerifyError(decodeAccessToken)) {
      fail();
    }

    expect(decodeAccessToken.role).toBe('none admin');
    expect(decodeAccessToken.tier).toBe(TIERS.TIER5);
    expect(decodeAccessToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeAccessToken.delegateUserId).toBe(parentAdmin.external_id);
  });

  it('代行操作用リフレッシュトークンの形式が不正な場合、エラーとなること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin } = await makeTestAccount(source, {
      tier: 4,
    });

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

    try {
      await service.generateDelegationAccessToken(context, 'invalid token');
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.UNAUTHORIZED);
        expect(e.getResponse()).toEqual(makeErrorResponse('E000101'));
      } else {
        fail();
      }
    }
  });
});

describe('updateDelegationAccessToken', () => {
  let source: DataSource | null = null;
  beforeAll(async () => {
    if (source == null) {
      source = await (async () => {
        const s = new DataSource({
          type: 'mysql',
          host: 'test_mysql_db',
          port: 3306,
          username: 'user',
          password: 'password',
          database: 'odms',
          entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
          synchronize: false, // trueにすると自動的にmigrationが行われるため注意
        });
        return await s.initialize();
      })();
    }
  });

  beforeEach(async () => {
    if (source) {
      await truncateAllTable(source);
    }
  });

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

  it('代行操作用リフレッシュトークンから代行操作用アクセストークンを更新できること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    const { admin: partnerAdmin, account: partnerAccount } =
      await makeTestAccount(
        source,
        {
          tier: 5,
          parent_account_id: parentAccount.id,
          delegation_permission: true,
        },
        { role: USER_ROLES.NONE },
      );

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

    const delegationRefreshToken = await service.generateDelegationRefreshToken(
      context,
      parentAdmin.external_id,
      partnerAccount.id,
    );

    // 取得できた代行操作用リフレッシュトークンをデコード
    const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
    if (isVerifyError(decodeRefreshToken)) {
      fail();
    }

    expect(decodeRefreshToken.role).toBe('none admin');
    expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
    expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);

    const token = await service.updateDelegationAccessToken(
      context,
      decodeRefreshToken.delegateUserId,
      decodeRefreshToken.userId,
      delegationRefreshToken,
    );

    // 取得できた代行操作用リフレッシュトークンをデコード
    const decodeAccessToken = decode<RefreshToken>(token);
    if (isVerifyError(decodeAccessToken)) {
      fail();
    }

    expect(decodeAccessToken.role).toBe('none admin');
    expect(decodeAccessToken.tier).toBe(TIERS.TIER5);
    expect(decodeAccessToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeAccessToken.delegateUserId).toBe(parentAdmin.external_id);
  });

  it('代行操作対象アカウントの代行操作が許可されていない場合、エラーとなること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    const { admin: partnerAdmin, account: partnerAccount } =
      await makeTestAccount(
        source,
        {
          tier: 5,
          parent_account_id: parentAccount.id,
          delegation_permission: true,
        },
        { role: USER_ROLES.NONE },
      );

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

    const delegationRefreshToken = await service.generateDelegationRefreshToken(
      context,
      parentAdmin.external_id,
      partnerAccount.id,
    );

    // 取得できた代行操作用リフレッシュトークンをデコード
    const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
    if (isVerifyError(decodeRefreshToken)) {
      fail();
    }

    expect(decodeRefreshToken.role).toBe('none admin');
    expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
    expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);

    if (decodeRefreshToken.delegateUserId === undefined) {
      fail();
    }

    // 代行操作対象アカウントの代行操作を許可しないように変更
    await updateAccountDelegationPermission(source, partnerAccount.id, false);
    const account = await getAccount(source, partnerAccount.id);

    expect(account?.delegation_permission ?? true).toBeFalsy();

    try {
      await service.updateDelegationAccessToken(
        context,
        decodeRefreshToken.delegateUserId,
        decodeRefreshToken.userId,
        delegationRefreshToken,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.UNAUTHORIZED);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010503'));
      } else {
        fail();
      }
    }
  });
  it('代行操作対象アカウントが存在しない場合、エラーとなること', async () => {
    if (!source) fail();
    const module = await makeTestingModule(source);
    if (!module) fail();
    const service = module.get<AuthService>(AuthService);
    const { admin: parentAdmin, account: parentAccount } =
      await makeTestAccount(source, {
        tier: 4,
      });
    const { admin: partnerAdmin, account: partnerAccount } =
      await makeTestAccount(
        source,
        {
          tier: 5,
          parent_account_id: parentAccount.id,
          delegation_permission: true,
        },
        { role: USER_ROLES.NONE },
      );

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

    const delegationRefreshToken = await service.generateDelegationRefreshToken(
      context,
      parentAdmin.external_id,
      partnerAccount.id,
    );

    // 取得できた代行操作用リフレッシュトークンをデコード
    const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
    if (isVerifyError(decodeRefreshToken)) {
      fail();
    }

    expect(decodeRefreshToken.role).toBe('none admin');
    expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
    expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
    expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);

    if (decodeRefreshToken.delegateUserId === undefined) {
      fail();
    }

    // 代行操作対象アカウントを削除
    await deleteAccount(source, partnerAccount.id);

    try {
      await service.updateDelegationAccessToken(
        context,
        decodeRefreshToken.delegateUserId,
        partnerAdmin.external_id,
        delegationRefreshToken,
      );
      fail();
    } catch (e) {
      if (e instanceof HttpException) {
        expect(e.getStatus()).toEqual(HttpStatus.UNAUTHORIZED);
        expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
      } else {
        fail();
      }
    }
  });
});

const idTokenPayload = {
  exp: 9000000000,
  nbf: 1000000000,
  ver: '1.0',
  iss: 'issuer',
  sub: 'sub',
  aud: 'aud',
  nonce: 'defaultNonce',
  iat: 1000000000,
  auth_time: 1000000000,
  emails: ['xxx@xx.com'],
  tfp: 'signin_userflow',
};
