Merged PR 337: API修正(ユーザー追加)&テスト実装
## 概要 [Task2398: API修正(ユーザー追加)&テスト実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2398) - ユーザー追加のリカバリ処理を実装 - ADB2Cに追加したユーザー削除 - DBに追加したユーザー削除 ## レビューポイント - リカバリ処理に不足はないか - ログはこれでよいか - テストケースはこれで足りているか - ADB2Cのユーザー削除:OK , DB上のユーザーは削除:NO のケースはいるか等 ## UIの変更 ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
6f92ef9453
commit
76ed87d82a
@ -25,6 +25,7 @@ export const overrideAdB2cService = <TService>(
|
||||
password: string,
|
||||
username: string,
|
||||
) => Promise<{ sub: string } | ConflictError>;
|
||||
deleteUser?: (externalId: string, context: Context) => Promise<void>;
|
||||
},
|
||||
): void => {
|
||||
// テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得
|
||||
@ -35,6 +36,12 @@ export const overrideAdB2cService = <TService>(
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
if (overrides.deleteUser) {
|
||||
Object.defineProperty(obj, obj.deleteUser.name, {
|
||||
value: overrides.deleteUser,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -115,6 +122,7 @@ export const overrideUsersRepositoryService = <TService>(
|
||||
service: TService,
|
||||
overrides: {
|
||||
createNormalUser?: (user: newUser) => Promise<User>;
|
||||
deleteNormalUser?: (userId: number) => Promise<void>;
|
||||
},
|
||||
): void => {
|
||||
// テストコードでのみ許される強引な方法でprivateメンバ変数の参照を取得
|
||||
@ -125,6 +133,12 @@ export const overrideUsersRepositoryService = <TService>(
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
if (overrides.deleteNormalUser) {
|
||||
Object.defineProperty(obj, obj.deleteNormalUser.name, {
|
||||
value: overrides.deleteNormalUser,
|
||||
writable: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -40,6 +40,7 @@ import {
|
||||
} from '../../common/test/overrides';
|
||||
import { NewTrialLicenseExpirationDate } from '../licenses/types/types';
|
||||
import { License } from '../../repositories/licenses/entity/license.entity';
|
||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||
|
||||
describe('UsersService.confirmUser', () => {
|
||||
let source: DataSource = null;
|
||||
@ -709,9 +710,10 @@ describe('UsersService.createUser', () => {
|
||||
expect(users.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('DBネットワークエラーとなる場合、エラーとなる。', async () => {
|
||||
it('DBネットワークエラーとなる場合、リカバリ処理を実施し、ADB2Cに作成したユーザーを削除する', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { role: adminRole, tier } = await createAccountAndAdminUser(
|
||||
source,
|
||||
@ -746,6 +748,7 @@ describe('UsersService.createUser', () => {
|
||||
|
||||
return { sub: externalId };
|
||||
},
|
||||
deleteUser: jest.fn(),
|
||||
});
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async () => {
|
||||
@ -782,6 +785,99 @@ describe('UsersService.createUser', () => {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
|
||||
expect(b2cService.deleteUser).toBeCalledWith(
|
||||
externalId,
|
||||
makeContext('trackingId'),
|
||||
);
|
||||
});
|
||||
|
||||
it('DBネットワークエラーとなる場合、リカバリ処理を実施されるが、そのリカバリ処理に失敗した場合、ADB2Cのユーザーは削除されない', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const {
|
||||
accountId,
|
||||
role: adminRole,
|
||||
tier,
|
||||
} = await createAccountAndAdminUser(source, adminExternalId);
|
||||
|
||||
const token: AccessToken = {
|
||||
userId: adminExternalId,
|
||||
role: adminRole,
|
||||
tier: tier,
|
||||
};
|
||||
|
||||
const name = 'test_user1';
|
||||
const role = USER_ROLES.NONE;
|
||||
const email = 'test1@example.com';
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
|
||||
const externalId = '0001';
|
||||
|
||||
overrideAdB2cService(service, {
|
||||
createUser: async (
|
||||
_context: Context,
|
||||
_email: string,
|
||||
_password: string,
|
||||
_username: string,
|
||||
) => {
|
||||
// ユーザー作成時に指定したパラメータが正しく渡されていることを確認
|
||||
expect(email).toEqual(_email);
|
||||
expect(name).toEqual(_username);
|
||||
|
||||
return { sub: externalId };
|
||||
},
|
||||
deleteUser: jest.fn().mockRejectedValue(new Error('ADB2C error')),
|
||||
});
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async () => {
|
||||
return;
|
||||
},
|
||||
createMailContentFromEmailConfirmForNormalUser: async () => {
|
||||
return { html: '', text: '', subject: '' };
|
||||
},
|
||||
});
|
||||
|
||||
// DBエラーを発生させる
|
||||
overrideUsersRepositoryService(service, {
|
||||
createNormalUser: async () => {
|
||||
throw new Error('DB error');
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
autoRenew,
|
||||
licenseAlert,
|
||||
notification,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
// 新規ユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
expect(users.length).toEqual(1);
|
||||
//アカウントIDがテスト用の管理者ユーザーのものであることを確認
|
||||
expect(users[0].account_id).toEqual(accountId);
|
||||
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
|
||||
expect(b2cService.deleteUser).toBeCalledWith(
|
||||
externalId,
|
||||
makeContext('trackingId'),
|
||||
);
|
||||
});
|
||||
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => {
|
||||
@ -1051,14 +1147,16 @@ describe('UsersService.createUser', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('AuthorIDが重複している場合、エラーとなる。(insert失敗)', async () => {
|
||||
it('AuthorIDが重複している場合、エラー(insert失敗)となり、リカバリ処理が実行され、ADB2Cに追加したユーザーが削除される', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { role: adminRole, tier } = await createAccountAndAdminUser(
|
||||
source,
|
||||
adminExternalId,
|
||||
);
|
||||
const {
|
||||
accountId,
|
||||
role: adminRole,
|
||||
tier,
|
||||
} = await createAccountAndAdminUser(source, adminExternalId);
|
||||
|
||||
const token: AccessToken = {
|
||||
userId: adminExternalId,
|
||||
@ -1092,6 +1190,7 @@ describe('UsersService.createUser', () => {
|
||||
|
||||
return { sub: externalId };
|
||||
},
|
||||
deleteUser: jest.fn(),
|
||||
});
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async () => {
|
||||
@ -1132,6 +1231,183 @@ describe('UsersService.createUser', () => {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
// 新規にユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
expect(users.length).toEqual(1);
|
||||
expect(users[0].account_id).toEqual(accountId);
|
||||
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
|
||||
expect(b2cService.deleteUser).toBeCalledWith(
|
||||
externalId,
|
||||
makeContext('trackingId'),
|
||||
);
|
||||
});
|
||||
|
||||
it('メール送信に失敗した場合、リカバリ処理が実行され、ADB2C,DBのユーザーが削除される', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const {
|
||||
accountId,
|
||||
role: adminRole,
|
||||
tier,
|
||||
} = await createAccountAndAdminUser(source, adminExternalId);
|
||||
|
||||
const token: AccessToken = {
|
||||
userId: adminExternalId,
|
||||
role: adminRole,
|
||||
tier: tier,
|
||||
};
|
||||
|
||||
const name = 'test_user1';
|
||||
const role = USER_ROLES.NONE;
|
||||
const email = 'test1@example.com';
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
|
||||
const externalId = '0001';
|
||||
|
||||
overrideAdB2cService(service, {
|
||||
createUser: async (
|
||||
_context: Context,
|
||||
_email: string,
|
||||
_password: string,
|
||||
_username: string,
|
||||
) => {
|
||||
// ユーザー作成時に指定したパラメータが正しく渡されていることを確認
|
||||
expect(email).toEqual(_email);
|
||||
expect(name).toEqual(_username);
|
||||
|
||||
return { sub: externalId };
|
||||
},
|
||||
deleteUser: jest.fn(),
|
||||
});
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async () => {
|
||||
throw new Error();
|
||||
},
|
||||
createMailContentFromEmailConfirmForNormalUser: async () => {
|
||||
return { html: '', text: '', subject: '' };
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
autoRenew,
|
||||
licenseAlert,
|
||||
notification,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
// 新規ユーザーが登録されていないことを確認
|
||||
const users = await getUsers(source);
|
||||
expect(users.length).toEqual(1);
|
||||
//アカウントIDがテスト用の管理者ユーザーのものであることを確認
|
||||
expect(users[0].account_id).toEqual(accountId);
|
||||
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
|
||||
expect(b2cService.deleteUser).toBeCalledWith(
|
||||
externalId,
|
||||
makeContext('trackingId'),
|
||||
);
|
||||
});
|
||||
|
||||
it('メール送信に失敗した場合、リカバリ処理が実行されるが、そのリカバリ処理に失敗した場合、ADB2C,DBのユーザーが削除されない', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<UsersService>(UsersService);
|
||||
const b2cService = module.get<AdB2cService>(AdB2cService);
|
||||
|
||||
const adminExternalId = 'ADMIN0001';
|
||||
const { role: adminRole, tier } = await createAccountAndAdminUser(
|
||||
source,
|
||||
adminExternalId,
|
||||
);
|
||||
|
||||
const token: AccessToken = {
|
||||
userId: adminExternalId,
|
||||
role: adminRole,
|
||||
tier: tier,
|
||||
};
|
||||
|
||||
const name = 'test_user1';
|
||||
const role = USER_ROLES.NONE;
|
||||
const email = 'test1@example.com';
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
|
||||
const externalId = '0001';
|
||||
|
||||
overrideAdB2cService(service, {
|
||||
createUser: async (
|
||||
_context: Context,
|
||||
_email: string,
|
||||
_password: string,
|
||||
_username: string,
|
||||
) => {
|
||||
// ユーザー作成時に指定したパラメータが正しく渡されていることを確認
|
||||
expect(email).toEqual(_email);
|
||||
expect(name).toEqual(_username);
|
||||
|
||||
return { sub: externalId };
|
||||
},
|
||||
deleteUser: jest.fn().mockRejectedValue(new Error()),
|
||||
});
|
||||
overrideSendgridService(service, {
|
||||
sendMail: async () => {
|
||||
throw new Error();
|
||||
},
|
||||
createMailContentFromEmailConfirmForNormalUser: async () => {
|
||||
return { html: '', text: '', subject: '' };
|
||||
},
|
||||
});
|
||||
overrideUsersRepositoryService(service, {
|
||||
deleteNormalUser: async () => {
|
||||
throw new Error();
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await service.createUser(
|
||||
makeContext('trackingId'),
|
||||
token,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
autoRenew,
|
||||
licenseAlert,
|
||||
notification,
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
// リカバリ処理が失敗したため、DBのユーザーが削除されないことを確認
|
||||
const users = await getUsers(source);
|
||||
expect(users.length).toEqual(2);
|
||||
// ADB2Cに作成したユーザーを削除するメソッドが呼ばれていることを確認
|
||||
expect(b2cService.deleteUser).toBeCalledWith(
|
||||
externalId,
|
||||
makeContext('trackingId'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -229,6 +229,10 @@ export class UsersService {
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
this.logger.error('create user failed');
|
||||
//リカバリー処理
|
||||
//Azure AD B2Cに登録したユーザー情報を削除する
|
||||
await this.deleteB2cUser(externalUser.sub, context);
|
||||
|
||||
switch (e.code) {
|
||||
case 'ER_DUP_ENTRY':
|
||||
//AuthorID重複エラー
|
||||
@ -269,7 +273,11 @@ export class UsersService {
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
this.logger.error('create user failed');
|
||||
this.logger.error(`[NOT IMPLEMENT] [RECOVER] delete user: ${newUser.id}`);
|
||||
//リカバリー処理
|
||||
//Azure AD B2Cに登録したユーザー情報を削除する
|
||||
await this.deleteB2cUser(externalUser.sub, context);
|
||||
// DBからユーザーを削除する
|
||||
await this.deleteUser(newUser.id, context);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
@ -279,6 +287,35 @@ export class UsersService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Azure AD B2Cに登録したユーザー情報を削除する
|
||||
// TODO 「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
|
||||
private async deleteB2cUser(externalUserId: string, context: Context) {
|
||||
try {
|
||||
await this.adB2cService.deleteUser(externalUserId, context);
|
||||
this.logger.log(
|
||||
`[${context.trackingId}] delete externalUser: ${externalUserId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(`error=${error}`);
|
||||
this.logger.error(
|
||||
`[MANUAL_RECOVERY_REQUIRED] [${context.trackingId}] Failed to delete externalUser: ${externalUserId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// DBに登録したユーザー情報を削除する
|
||||
private async deleteUser(userId: number, context: Context) {
|
||||
try {
|
||||
await this.usersRepository.deleteNormalUser(userId);
|
||||
this.logger.log(`[${context.trackingId}] delete user: ${userId}`);
|
||||
} catch (error) {
|
||||
this.logger.error(`error=${error}`);
|
||||
this.logger.error(
|
||||
`[MANUAL_RECOVERY_REQUIRED] [${context.trackingId}] Failed to delete user: ${userId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// roleを受け取って、roleに応じたnewUserを作成して返却する
|
||||
private createNewUserInfo(
|
||||
role: UserRoles,
|
||||
|
||||
@ -381,4 +381,22 @@ export class UsersRepositoryService {
|
||||
return typists;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UserID指定のユーザーとソート条件を同時に削除する
|
||||
* @param userId
|
||||
* @returns delete
|
||||
*/
|
||||
async deleteNormalUser(userId: number): Promise<void> {
|
||||
await this.dataSource.transaction(async (entityManager) => {
|
||||
const usersRepo = entityManager.getRepository(User);
|
||||
const sortCriteriaRepo = entityManager.getRepository(SortCriteria);
|
||||
// ソート条件を削除
|
||||
await sortCriteriaRepo.delete({
|
||||
user_id: userId,
|
||||
});
|
||||
// プライマリ管理者を削除
|
||||
await usersRepo.delete({ id: userId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user