Merged PR 819: [3848]アカウント削除処理修正

## 概要
[Task3847: [3848]アカウント削除処理修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3847)

- AccountArchiveエンティティを追加
- アカウント削除時、Accountをアーカイブする処理を追加
- アーカイブ関連テストを追加

## レビューポイント
- 実装の修正内容は問題なさそうか
- テストケースの修正内容は問題なさそうか
- クエリの変更内容の確認方法は問題なさそうか to 斎藤さん

## レビュー対象外
- テスト用ロガーに以下の比較用前処理を追加するべきだが、別タスクを作って対応予定
- 下記クエリの変更点にて、CommentOut判定に環境変数STAGEを使用している部分にRequestIdが表示されていない部分が存在するが、テスト用環境変数の変更は上記と同じく別タスクを作って対応予定
[タスク 3889: クエリ比較用ログ出力の仕組みを改良](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_workitems/edit/3889)

## クエリの変更
- ロガーを有効にした状態でテストを実行し、ログのUUIDと日付を処理して比較できるよう加工した
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/3847?csf=1&web=1&e=xlK011

## 動作確認状況
- npm run testで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - アカウント削除テストで発行されるクエリを比較し、AccountArchiveする対象を特定するためのAccountのSELECT、AccountArchiveのINSERTとSELECTのみが追加されている事が確認できたので、デグレはないと判断
This commit is contained in:
湯本 開 2024-03-11 02:08:29 +00:00
parent f386a8f7e0
commit ff4cd35ed3
5 changed files with 190 additions and 1 deletions

View File

@ -0,0 +1,65 @@
import { Logger, QueryRunner } from 'typeorm';
import * as fs from 'fs';
import * as path from 'path';
export class FileLogger implements Logger {
private logPath = path.join(__dirname, 'logs');
constructor() {
if (!fs.existsSync(this.logPath)) {
fs.mkdirSync(this.logPath, { recursive: true });
}
}
private writeToFile(message: string): void {
const logFile = path.join(
this.logPath,
`${new Date().toISOString().split('T')[0]}.log`,
);
fs.appendFileSync(logFile, `${message}\n`);
}
logQuery(query: string, parameters?: any[], queryRunner?: QueryRunner) {
this.writeToFile(
`Query: ${query} -- Parameters: ${JSON.stringify(parameters)}`,
);
}
logQueryError(
error: string,
query: string,
parameters?: any[],
queryRunner?: QueryRunner,
) {
this.writeToFile(
`ERROR: ${error} -- Query: ${query} -- Parameters: ${JSON.stringify(
parameters,
)}`,
);
}
logQuerySlow(
time: number,
query: string,
parameters?: any[],
queryRunner?: QueryRunner,
) {
this.writeToFile(
`SLOW QUERY: ${time}ms -- Query: ${query} -- Parameters: ${JSON.stringify(
parameters,
)}`,
);
}
logSchemaBuild(message: string, queryRunner?: QueryRunner) {
this.writeToFile(`Schema Build: ${message}`);
}
logMigration(message: string, queryRunner?: QueryRunner) {
this.writeToFile(`Migration: ${message}`);
}
log(level: 'log' | 'info' | 'warn', message: any, queryRunner?: QueryRunner) {
this.writeToFile(`${level.toUpperCase()}: ${message}`);
}
}

View File

@ -8,6 +8,7 @@ import {
USER_ROLES,
} from '../../constants';
import { License } from '../../repositories/licenses/entity/license.entity';
import { AccountArchive } from '../../repositories/accounts/entity/account_archive.entity';
type InitialTestDBState = {
tier1Accounts: { account: Account; users: User[] }[];
@ -398,6 +399,12 @@ export const getUsers = async (dataSource: DataSource): Promise<User[]> => {
* @param dataSource
* @returns 退
*/
export const getAccountArchive = async (
dataSource: DataSource,
): Promise<AccountArchive[]> => {
return await dataSource.getRepository(AccountArchive).find();
};
export const getUserArchive = async (
dataSource: DataSource,
): Promise<UserArchive[]> => {

View File

@ -39,6 +39,7 @@ import {
getUser,
getLicenses,
getUserArchive,
getAccountArchive,
} from '../../common/test/utility';
import { AccountsService } from './accounts.service';
import { Context, makeContext } from '../../common/log';
@ -7158,6 +7159,11 @@ describe('deleteAccountAndData', () => {
);
expect(LicenseAllocationHistoryRecordB.length).not.toBe(0);
const accountArchive = await getAccountArchive(source);
expect(accountArchive.length).toBe(1);
const archive = accountArchive.at(0);
expect(archive?.id).toBe(tier5AccountsA.account.id);
const UserArchive = await getUserArchive(source);
expect(UserArchive.length).toBe(2);
@ -7238,6 +7244,12 @@ describe('deleteAccountAndData', () => {
expect(accountRecord?.id).not.toBeNull();
const userRecord = await getUser(source, user?.id ?? 0);
expect(userRecord?.id).not.toBeNull();
// アーカイブが作成されていないことを確認
const accountArchive = await getAccountArchive(source);
expect(accountArchive.length).toBe(0);
const userArchive = await getUserArchive(source);
expect(userArchive.length).toBe(0);
});
it('ADB2Cユーザーの削除失敗時は、MANUAL_RECOVERY_REQUIREDを出して処理続行', async () => {
if (!source) fail();
@ -7296,6 +7308,17 @@ describe('deleteAccountAndData', () => {
expect(accountRecord).toBe(null);
const userRecord = await getUser(source, user?.id ?? 0);
expect(userRecord).toBe(null);
const accountArchive = await getAccountArchive(source);
expect(accountArchive.length).toBe(1);
const archive = accountArchive.at(0);
expect(archive?.id).toBe(tier5Accounts.account.id);
const userArchive = await getUserArchive(source);
expect(userArchive.length).toBe(2);
const expectUserIds = [tier5Accounts.admin.id, user.id].sort();
const userArchiveIds = userArchive.map((x) => x.id).sort();
expect(expectUserIds).toStrictEqual(userArchiveIds);
});
it('blobstorageコンテナを削除で失敗した場合は、MANUAL_RECOVERY_REQUIRED出して正常終了', async () => {
if (!source) fail();
@ -7355,6 +7378,17 @@ describe('deleteAccountAndData', () => {
expect(accountRecord).toBe(null);
const userRecord = await getUser(source, user?.id ?? 0);
expect(userRecord).toBe(null);
const accountArchive = await getAccountArchive(source);
expect(accountArchive.length).toBe(1);
const archive = accountArchive.at(0);
expect(archive?.id).toBe(tier5Accounts.account.id);
const userArchive = await getUserArchive(source);
expect(userArchive.length).toBe(2);
const expectUserIds = [tier5Accounts.admin.id, user.id].sort();
const userArchiveIds = userArchive.map((x) => x.id).sort();
expect(expectUserIds).toStrictEqual(userArchiveIds);
});
});
describe('getAccountInfoMinimalAccess', () => {

View File

@ -64,6 +64,7 @@ import {
PartnerInfoFromDb,
PartnerLicenseInfoForRepository,
} from '../../features/accounts/types/types';
import { AccountArchive } from './entity/account_archive.entity';
@Injectable()
export class AccountsRepositoryService {
@ -1156,6 +1157,23 @@ export class AccountsRepositoryService {
accountId: number,
): Promise<User[]> {
return await this.dataSource.transaction(async (entityManager) => {
// 削除対象のアカウントを退避テーブルに退避
const accountRepo = entityManager.getRepository(Account);
const account = await accountRepo.find({
where: {
id: accountId,
},
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
const accountArchiveRepo = entityManager.getRepository(AccountArchive);
await insertEntities(
AccountArchive,
accountArchiveRepo,
account,
this.isCommentOut,
context,
);
// 削除対象のユーザーを退避テーブルに退避
const users = await this.dataSource.getRepository(User).find({
where: {
@ -1209,7 +1227,6 @@ export class AccountsRepositoryService {
);
// アカウントを削除
const accountRepo = entityManager.getRepository(Account);
await deleteEntity(
accountRepo,
{ id: accountId },

View File

@ -0,0 +1,66 @@
import { bigintTransformer } from '../../../common/entity';
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: 'accounts_archive' })
export class AccountArchive {
@PrimaryGeneratedColumn()
id: number;
@Column({ nullable: true, type: 'bigint', transformer: bigintTransformer })
parent_account_id: number | null;
@Column()
tier: number;
@Column()
country: string;
@Column({ default: false })
delegation_permission: boolean;
@Column({ default: false })
locked: boolean;
@Column({ default: false })
verified: boolean;
@Column({ nullable: true, type: 'bigint', transformer: bigintTransformer })
primary_admin_user_id: number | null;
@Column({ nullable: true, type: 'bigint', transformer: bigintTransformer })
secondary_admin_user_id: number | null;
@Column({ nullable: true, type: 'bigint', transformer: bigintTransformer })
active_worktype_id: number | null;
@Column({ default: false })
auto_file_delete: boolean;
@Column({ default: 0 })
file_retention_days: number;
@Column({ nullable: true, type: 'datetime' })
deleted_at: Date | null;
@Column({ nullable: true, type: 'datetime' })
created_by: string | null;
@CreateDateColumn({
type: 'datetime',
})
created_at: Date;
@Column({ nullable: true, type: 'datetime' })
updated_by: string | null;
@UpdateDateColumn({
type: 'datetime',
})
updated_at: Date;
}