Merged PR 349: API実装(ライセンス割り当て解除API)

## 概要
[Task2450: API実装(ライセンス割り当て解除API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2450)

ライセンス割り当て解除APIを実装しました。

## レビューポイント
なし

## UIの変更
なし

## 動作確認状況
ローカルでUT、動作確認済み

## 補足
なし
This commit is contained in:
oura.a 2023-08-24 08:24:06 +00:00
parent 177c8beb41
commit 0fd1ff2b6a
7 changed files with 193 additions and 6 deletions

View File

@ -46,4 +46,5 @@ export const ErrorCodes = [
'E010804', // ライセンス不足エラー
'E010805', // ライセンス有効期限切れエラー
'E010806', // ライセンス割り当て不可エラー
'E010807', // ライセンス割り当て解除済みエラー
] as const;

View File

@ -35,4 +35,5 @@ export const errors: Errors = {
E010804: 'License shortage Error',
E010805: 'License is expired Error',
E010806: 'License is unavailable Error',
E010807: 'License is already deallocated Error',
};

View File

@ -844,3 +844,107 @@ describe('ライセンス割り当て', () => {
);
});
});
describe('ライセンス割り当て解除', () => {
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 { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
const date = new Date();
date.setDate(date.getDate() + 30);
await createLicense(
source,
1,
date,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
const service = module.get<UsersService>(UsersService);
await service.deallocateLicense(makeContext('trackingId'), userId);
// 割り当て解除したライセンスの状態確認
const deallocatedLicense = await selectLicense(source, 1);
expect(deallocatedLicense.license.allocated_user_id).toBe(null);
expect(deallocatedLicense.license.status).toBe(
LICENSE_ALLOCATED_STATUS.REUSABLE,
);
expect(deallocatedLicense.license.expiry_date).toEqual(date);
// ライセンス履歴テーブルの状態確認
const licenseAllocationHistory = await selectLicenseAllocationHistory(
source,
userId,
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.user_id).toBe(
userId,
);
expect(licenseAllocationHistory.licenseAllocationHistory.license_id).toBe(
1,
);
expect(licenseAllocationHistory.licenseAllocationHistory.is_allocated).toBe(
false,
);
expect(
licenseAllocationHistory.licenseAllocationHistory.switch_from_type,
).toBe('NONE');
});
it('ライセンスが既に割り当て解除されていた場合、エラーとなる', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { userId } = await createUser(source, accountId, 'userId', 'admin');
await createUser(source, accountId, 'userId2', 'admin');
const date = new Date();
date.setDate(date.getDate() + 30);
await createLicense(
source,
1,
date,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
2,
);
await createLicense(
source,
2,
date,
accountId,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.REUSABLE,
userId,
);
await createLicenseAllocationHistory(source, 1, userId, 1, 'NONE');
const service = module.get<UsersService>(UsersService);
await expect(
service.deallocateLicense(makeContext('trackingId'), userId),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST),
);
});
});

View File

@ -461,13 +461,12 @@ export class UsersController {
@Body() body: DeallocateLicenseRequest,
@Req() req: Request,
): Promise<DeallocateLicenseResponse> {
//API実装時に詳細をかいていく
//const accessToken = retrieveAuthorizationToken(req);
//const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
const accessToken = retrieveAuthorizationToken(req);
const { userId } = jwt.decode(accessToken, { json: true }) as AccessToken;
//const context = makeContext(userId);
const context = makeContext(userId);
//await this.usersService.deallocateLicense(context, body.userId);
await this.usersService.deallocateLicense(context, body.userId);
return {};
}
}

View File

@ -41,6 +41,7 @@ import { DateWithZeroTime } from '../licenses/types/types';
import { Context } from '../../common/log';
import { UserRoles } from '../../common/types/role';
import {
LicenseAlreadyDeallocatedError,
LicenseExpiredError,
LicenseUnavailableError,
} from '../../repositories/licenses/errors/types';
@ -886,7 +887,7 @@ export class UsersService {
this.logger.log(
`[IN] [${context.trackingId}] ${this.allocateLicense.name} | params: { ` +
`userId: ${userId}, ` +
`newLicenseId: ${newLicenseId}, `,
`newLicenseId: ${newLicenseId}, };`,
);
try {
@ -918,4 +919,40 @@ export class UsersService {
);
}
}
/**
*
* @param context
* @param userId
*/
async deallocateLicense(context: Context, userId: number): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deallocateLicense.name} | params: { ` +
`userId: ${userId}, };`,
);
try {
await this.licensesRepository.deallocateLicense(userId);
} catch (e) {
this.logger.error(`error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseAlreadyDeallocatedError:
throw new HttpException(
makeErrorResponse('E010807'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deallocateLicense.name}`,
);
}
}
}

View File

@ -18,3 +18,6 @@ export class LicensesShortageError extends Error {}
export class LicenseExpiredError extends Error {}
// ライセンス割り当て不可エラー
export class LicenseUnavailableError extends Error {}
// ライセンス割り当て解除済みエラー
export class LicenseAlreadyDeallocatedError extends Error {}

View File

@ -25,6 +25,7 @@ import {
OrderNotFoundError,
LicenseExpiredError,
LicenseUnavailableError,
LicenseAlreadyDeallocatedError,
} from './errors/types';
import {
AllocatableLicenseInfo,
@ -541,4 +542,45 @@ export class LicensesRepositoryService {
await licenseAllocationHistoryRepo.save(allocationHistory);
});
}
/**
*
* @param userId
*/
async deallocateLicense(userId: number): Promise<void> {
await this.dataSource.transaction(async (entityManager) => {
const licenseRepo = entityManager.getRepository(License);
const licenseAllocationHistoryRepo = entityManager.getRepository(
LicenseAllocationHistory,
);
// 対象ユーザーのライセンス割り当て状態を取得
const allocatedLicense = await licenseRepo.findOne({
where: {
allocated_user_id: userId,
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
},
});
// ライセンスが割り当てられていない場合はエラー
if (!allocatedLicense) {
throw new LicenseAlreadyDeallocatedError(
`License is already deallocated. userId: ${userId}`,
);
}
// ライセンスの割り当てを解除
allocatedLicense.status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocatedLicense.allocated_user_id = null;
await licenseRepo.save(allocatedLicense);
// ライセンス割り当て履歴テーブルへ登録
const deallocationHistory = new LicenseAllocationHistory();
deallocationHistory.user_id = userId;
deallocationHistory.license_id = allocatedLicense.id;
deallocationHistory.is_allocated = false;
deallocationHistory.executed_at = new Date();
deallocationHistory.switch_from_type = SWITCH_FROM_TYPE.NONE;
await licenseAllocationHistoryRepo.save(deallocationHistory);
});
}
}