Merged PR 209: API実装(カードライセンス取り込みAPI)

## 概要
[Task2096: API実装(カードライセンス取り込みAPI)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2096)

- タスク 2096: API実装(カードライセンス取り込みAPI)
カードライセンス取り込みAPIを実装しました。

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

## UIの変更
なし

## 動作確認状況
ユニットテスト実施済み
ローカルでの動作確認実施済み

## 補足
なし
This commit is contained in:
oura.a 2023-07-07 09:02:44 +00:00
parent 773c8894e7
commit 365e4266e6
10 changed files with 435 additions and 8 deletions

View File

@ -38,4 +38,6 @@ export const ErrorCodes = [
'E010602', // タスク変更権限不足エラー
'E010603', // タスク不在エラー
'E010701', // Blobファイル不在エラー
'E010801', // ライセンス不在エラー
'E010802', // ライセンス取り込み済みエラー
] as const;

View File

@ -27,4 +27,6 @@ export const errors: Errors = {
E010602: 'No task edit permissions Error',
E010603: 'Task not found Error.',
E010701: 'File not found in Blob Storage Error.',
E010801: 'License not exist Error',
E010802: 'License already activated Error',
};

View File

@ -28,7 +28,7 @@ import { retrieveAuthorizationToken } from '../../common/http/helper';
import { AccessToken } from '../../common/token';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES } from '../../constants';
import { ADMIN_ROLES, TIER_1, TIER_5 } from '../../constants';
import jwt from 'jsonwebtoken';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ -112,7 +112,7 @@ export class LicensesController {
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// 第一階層以外は401を返す後々UseGuardsで弾く
if (payload.tier != 1) {
if (payload.tier != TIER_1) {
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
@ -159,6 +159,22 @@ export class LicensesController {
console.log(req.header('Authorization'));
console.log(body);
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// TODO 第五階層以外は401を返す後々UseGuardsで弾く
if (payload.tier != TIER_5) {
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
);
}
await this.licensesService.activateCardLicenseKey(
payload.userId,
body.cardLicenseKey,
);
return {};
}
}

View File

@ -3,6 +3,7 @@ import {
CreateOrdersRequest,
IssueCardLicensesRequest,
IssueCardLicensesResponse,
ActivateCardLicensesRequest,
} from './types/types';
import {
makeDefaultAccountsRepositoryMockValue,
@ -12,14 +13,23 @@ import {
} from './test/liscense.service.mock';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { HttpException, HttpStatus } from '@nestjs/common';
import { PoNumberAlreadyExistError } from '../../repositories/licenses/errors/types';
import {
PoNumberAlreadyExistError,
LicenseKeyAlreadyActivatedError,
LicenseNotExistError,
} from '../../repositories/licenses/errors/types';
import { LicensesService } from './licenses.service';
import { makeTestingModule } from '../../common/test/modules';
import { DataSource } from 'typeorm';
import {
createAccount,
createUser,
createCardLicense,
createLicense,
createCardLicenseIssue,
selectCardLicensesCount,
selectCardLicense,
selectLicense,
} from './test/utility';
describe('LicensesService', () => {
@ -177,6 +187,92 @@ describe('LicensesService', () => {
),
);
});
it('カードライセンス取り込みが完了する', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
expect(
await service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
).toEqual(undefined);
});
it('カードライセンス取り込みに失敗した場合、エラーになるDBエラー', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.activateCardLicense = new Error('DB failed');
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
it('カードライセンス取り込みに失敗した場合、エラーになる(ライセンスが存在しないエラー)', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.activateCardLicense =
new LicenseNotExistError();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
);
});
it('カードライセンス取り込みに失敗した場合、エラーになる(ライセンスが既に取り込まれているエラー)', async () => {
const lisencesRepositoryMockValue =
makeDefaultLicensesRepositoryMockValue();
lisencesRepositoryMockValue.activateCardLicense =
new LicenseKeyAlreadyActivatedError();
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
const accountsRepositoryMockValue =
makeDefaultAccountsRepositoryMockValue();
const service = await makeLicensesServiceMock(
lisencesRepositoryMockValue,
usersRepositoryMockValue,
accountsRepositoryMockValue,
);
const body = new ActivateCardLicensesRequest();
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
await expect(
service.activateCardLicenseKey(token.userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
);
});
});
describe('DBテスト', () => {
@ -214,4 +310,38 @@ describe('DBテスト', () => {
const dbSelectResult = await selectCardLicensesCount(source);
expect(dbSelectResult.count).toEqual(issueCount);
});
it('カードライセンス取り込みが完了する', async () => {
const module = await makeTestingModule(source);
const { accountId } = await createAccount(source);
const { externalId } = await createUser(
source,
accountId,
'userId',
'admin',
);
const cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const defaultAccountId = 150;
const licenseId = 50;
const issueId = 100;
await createLicense(source, licenseId, defaultAccountId);
await createCardLicense(source, licenseId, issueId, cardLicenseKey);
await createCardLicenseIssue(source, issueId);
const service = module.get<LicensesService>(LicensesService);
await service.activateCardLicenseKey(externalId, cardLicenseKey);
const dbSelectResultFromCardLicense = await selectCardLicense(
source,
cardLicenseKey,
);
const dbSelectResultFromLicense = await selectLicense(source, licenseId);
expect(
dbSelectResultFromCardLicense.cardLicense.activated_at,
).toBeDefined();
expect(dbSelectResultFromLicense.license.account_id).toEqual(accountId);
});
});

View File

@ -4,7 +4,11 @@ import { AccessToken } from '../../common/token';
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
import { PoNumberAlreadyExistError } from '../../repositories/licenses/errors/types';
import {
PoNumberAlreadyExistError,
LicenseNotExistError,
LicenseKeyAlreadyActivatedError,
} from '../../repositories/licenses/errors/types';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { UserNotFoundError } from '../../repositories/users/errors/types';
import { IssueCardLicensesResponse } from './types/types';
@ -140,4 +144,71 @@ export class LicensesService {
);
}
}
/**
* card license activate
* @param externalId
* @param cardLicenseKey
*/
async activateCardLicenseKey(
externalId: string,
cardLicenseKey: string,
): Promise<void> {
this.logger.log(
`[IN] ${this.activateCardLicenseKey.name}, argCardLicenseKey: ${cardLicenseKey}`,
);
let myAccountId: number;
// ユーザIDからアカウントIDを取得する
try {
myAccountId = (
await this.usersRepository.findUserByExternalId(externalId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
// カードライセンスを取り込む
try {
await this.licensesRepository.activateCardLicense(
myAccountId,
cardLicenseKey,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('cardLicenseKey activate failed');
switch (e.constructor) {
case LicenseNotExistError:
throw new HttpException(
makeErrorResponse('E010801'),
HttpStatus.BAD_REQUEST,
);
case LicenseKeyAlreadyActivatedError:
throw new HttpException(
makeErrorResponse('E010802'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
this.logger.log(`[OUT] ${this.activateCardLicenseKey.name}`);
return;
}
}

View File

@ -9,6 +9,7 @@ import { AccountsRepositoryService } from '../../../repositories/accounts/accoun
export type LicensesRepositoryMockValue = {
order: undefined | Error;
createCardLicenses: string[] | Error;
activateCardLicense: undefined | Error;
};
export type AccountsRepositoryMockValue = {
@ -45,7 +46,7 @@ export const makeLicensesServiceMock = async (
export const makeLicensesRepositoryMock = (
value: LicensesRepositoryMockValue,
) => {
const { order, createCardLicenses } = value;
const { order, createCardLicenses, activateCardLicense } = value;
return {
order:
order instanceof Error
@ -57,6 +58,10 @@ export const makeLicensesRepositoryMock = (
: jest
.fn<Promise<string[]>, []>()
.mockResolvedValue(createCardLicenses),
activateCardLicense:
activateCardLicense instanceof Error
? jest.fn<Promise<void>, []>().mockRejectedValue(activateCardLicense)
: jest.fn<Promise<void>, []>().mockResolvedValue(activateCardLicense),
};
};
@ -100,6 +105,7 @@ export const makeDefaultLicensesRepositoryMockValue =
'XYLEWNY2LR6Q657CZE41',
'AEJWRFFSWRQYQQJ6WVLV',
],
activateCardLicense: undefined,
};
};
export const makeDefaultUsersRepositoryMockValue =

View File

@ -1,7 +1,11 @@
import { DataSource } from 'typeorm';
import { User } from '../../../repositories/users/entity/user.entity';
import { Account } from '../../../repositories/accounts/entity/account.entity';
import { CardLicense } from '../../../repositories/licenses/entity/license.entity';
import {
License,
CardLicense,
CardLicenseIssue,
} from '../../../repositories/licenses/entity/license.entity';
export const createAccount = async (
datasource: DataSource,
@ -49,9 +53,92 @@ export const createUser = async (
return { userId: user.id, externalId: external_id };
};
export const createLicense = async (
datasource: DataSource,
licenseId: number,
accountId: number,
): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId,
expiry_date: null,
account_id: accountId,
type: 'card',
status: 'Unallocated',
allocated_user_id: null,
order_id: null,
deleted_at: null,
delete_order_id: null,
created_by: 'test_runner',
created_at: new Date(),
updated_by: 'updater',
updated_at: new Date(),
});
identifiers.pop() as License;
};
export const createCardLicense = async (
datasource: DataSource,
licenseId: number,
issueId: number,
cardLicenseKey: string,
): Promise<void> => {
const { identifiers } = await datasource.getRepository(CardLicense).insert({
license_id: licenseId,
issue_id: issueId,
card_license_key: cardLicenseKey,
activated_at: null,
created_by: 'test_runner',
created_at: new Date(),
updated_by: 'updater',
updated_at: new Date(),
});
identifiers.pop() as CardLicense;
};
export const createCardLicenseIssue = async (
datasource: DataSource,
issueId: number,
): Promise<void> => {
const { identifiers } = await datasource
.getRepository(CardLicenseIssue)
.insert({
id: issueId,
issued_at: new Date(),
created_by: 'test_runner',
created_at: new Date(),
updated_by: 'updater',
updated_at: new Date(),
});
identifiers.pop() as CardLicenseIssue;
};
export const selectCardLicensesCount = async (
datasource: DataSource,
): Promise<{ count: number }> => {
const count = await datasource.getRepository(CardLicense).count();
return { count: count };
};
export const selectCardLicense = async (
datasource: DataSource,
cardLicenseKey: string,
): Promise<{ cardLicense: CardLicense }> => {
const cardLicense = await datasource.getRepository(CardLicense).findOne({
where: {
card_license_key: cardLicenseKey,
},
});
return { cardLicense };
};
export const selectLicense = async (
datasource: DataSource,
id: number,
): Promise<{ license: License }> => {
const license = await datasource.getRepository(License).findOne({
where: {
id: id,
},
});
return { license };
};

View File

@ -3,6 +3,7 @@ import {
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: 'license_orders' })
@ -63,6 +64,18 @@ export class License {
@Column({ nullable: true })
delete_order_id: number;
@Column({ nullable: true })
created_by: string;
@CreateDateColumn()
created_at: Date;
@Column({ nullable: true })
updated_by: string;
@UpdateDateColumn()
updated_at: Date;
}
@Entity({ name: 'licenses_history' })
export class LicenseHistory {
@ -92,6 +105,18 @@ export class CardLicenseIssue {
@Column()
issued_at: Date;
@Column({ nullable: true })
created_by: string;
@CreateDateColumn()
created_at: Date;
@Column({ nullable: true })
updated_by: string;
@UpdateDateColumn()
updated_at: Date;
}
@Entity({ name: 'card_licenses' })
@ -107,4 +132,16 @@ export class CardLicense {
@Column({ nullable: true })
activated_at: Date;
@Column({ nullable: true })
created_by: string;
@CreateDateColumn()
created_at: Date;
@Column({ nullable: true })
updated_by: string;
@UpdateDateColumn({})
updated_at: Date;
}

View File

@ -1,2 +1,8 @@
// POナンバーがすでに存在するエラー
export class PoNumberAlreadyExistError extends Error {}
// 取り込むカードライセンスが存在しないエラー
export class LicenseNotExistError extends Error {}
// 取り込むライセンスが既に取り込み済みのエラー
export class LicenseKeyAlreadyActivatedError extends Error {}

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, In } from 'typeorm';
import {
LicenseOrder,
@ -13,11 +13,16 @@ import {
LICENSE_STATUS_ISSUED,
LICENSE_TYPE,
} from '../../constants';
import { PoNumberAlreadyExistError } from './errors/types';
import {
PoNumberAlreadyExistError,
LicenseNotExistError,
LicenseKeyAlreadyActivatedError,
} from './errors/types';
@Injectable()
export class LicensesRepositoryService {
constructor(private dataSource: DataSource) {}
private readonly logger = new Logger(LicensesRepositoryService.name);
async order(
poNumber: string,
@ -185,4 +190,69 @@ export class LicensesRepositoryService {
return licenseKeys;
}
/**
*
* @param accountId
* @param licenseKey
* @returns void
*/
async activateCardLicense(
accountId: number,
licenseKey: string,
): Promise<void> {
await this.dataSource.transaction(async (entityManager) => {
const cardLicenseRepo = entityManager.getRepository(CardLicense);
// カードライセンステーブルを検索
const targetCardLicense = await cardLicenseRepo.findOne({
where: {
card_license_key: licenseKey,
},
});
// カードライセンスが存在しなければエラー
if (!targetCardLicense) {
this.logger.error(
`card license key not exist. card_licence_key: ${licenseKey}`,
);
throw new LicenseNotExistError();
}
// 既に取り込み済みならエラー
if (targetCardLicense.activated_at) {
this.logger.error(
`card license already activated. card_licence_key: ${licenseKey}`,
);
throw new LicenseKeyAlreadyActivatedError();
}
const licensesRepo = entityManager.getRepository(License);
// ライセンステーブルを検索
const targetLicense = await licensesRepo.findOne({
where: {
id: targetCardLicense.license_id,
},
});
// ライセンスが存在しなければエラー
if (!targetLicense) {
this.logger.error(
`license not exist. licence_id: ${targetCardLicense.license_id}`,
);
throw new LicenseNotExistError();
}
// ライセンステーブルを更新する
targetLicense.account_id = accountId;
await licensesRepo.save(targetLicense);
// カードライセンステーブルを更新する
targetCardLicense.activated_at = new Date();
await cardLicenseRepo.save(targetCardLicense);
this.logger.log(
`activate success. licence_id: ${targetCardLicense.license_id}`,
);
});
return;
}
}