Merged PR 342: API修正(パートナー追加)&テスト実装

## 概要
[Task2399: API修正(パートナー追加)&テスト実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2399)

- パートナー追加にリカバリ処理を追加
- テスト修正

## レビューポイント
- テストケースは足りているか
- リカバリ処理に漏れはないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

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

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
saito.k 2023-08-22 02:06:53 +00:00
parent b8c2640719
commit f16a5414dc
2 changed files with 552 additions and 64 deletions

View File

@ -250,7 +250,6 @@ describe('createAccount', () => {
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);
@ -745,7 +744,7 @@ describe('createPartnerAccount', () => {
},
});
// DB上に容易されたデータが想定通りであるか確認
// DB上に用意されたデータが想定通りであるか確認
{
const accounts = await getAccounts(source);
expect(accounts.length).toBe(1);
@ -815,7 +814,7 @@ describe('createPartnerAccount', () => {
},
});
// DB上に容易されたデータが想定通りであるか確認
// DB上に用意されたデータが想定通りであるか確認
{
const accounts = await getAccounts(source);
expect(accounts.length).toBe(1);
@ -851,6 +850,507 @@ describe('createPartnerAccount', () => {
}
});
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);
@ -887,7 +1387,7 @@ describe('createPartnerAccount', () => {
},
});
// DB上に容易されたデータが想定通りであるか確認
// DB上に用意されたデータが想定通りであるか確認
{
const accounts = await getAccounts(source);
expect(accounts.length).toBe(1);
@ -922,61 +1422,6 @@ describe('createPartnerAccount', () => {
expect(users.length).toBe(1);
}
});
it('BlobStorageへの通信失敗が原因でアカウントの追加に失敗した場合、エラーとなる(500エラー)', async () => {
const module = await makeTestingModule(source);
const service = module.get<AccountsService>(AccountsService);
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 };
},
});
overrideSendgridService(service, {
sendMail: async () => {
return;
},
createMailContentFromEmailConfirmForNormalUser: async () => {
return { html: '', text: '', subject: '' };
},
});
overrideBlobstorageService(service, {
createContainer: async () => {
throw new Error();
},
});
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();
}
}
});
});
describe('AccountsService', () => {

View File

@ -530,6 +530,8 @@ export class AccountsService {
);
}
let account: Account;
let user: User;
try {
// アカウントと管理者をセットで作成
const { newAccount, adminUser } =
@ -542,19 +544,50 @@ export class AccountsService {
USER_ROLES.NONE,
null,
);
account = newAccount;
user = adminUser;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create partner account failed');
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
// 新規作成アカウント用のBlobコンテナを作成
await this.blobStorageService.createContainer(
context,
newAccount.id,
account.id,
country,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create partner container failed');
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントとユーザーを削除
await this.deleteAccount(account.id, user.id, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
try {
const from = this.configService.get<string>('MAIL_FROM') || '';
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
newAccount.id,
adminUser.id,
account.id,
user.id,
email,
);
await this.sendgridService.sendMail(
@ -565,10 +598,20 @@ export class AccountsService {
text,
html,
);
return { accountId: newAccount.id };
return { accountId: account.id };
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create partner account failed');
this.logger.error('create partner account send mail failed');
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントを削除
await this.deleteAccount(account.id, user.id, context);
// Blobコンテナを削除
await this.deleteBlobContainer(account.id, country, context);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,