Merged PR 817: API IF実装(親アカウント変更API)

## 概要
[Task3852: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3852)

- 親アカウント変更APIのIFを実装し、OpenAPIの生成もしました。
- 影響範囲(他の機能にも影響があるか)
    - なし

## レビューポイント
- controllerのメソッド名にほか良い案ないか?
- validationに過不足や間違いないか?
- ~~controllerのテストは正常系ひとつだけ追加しているが、他にあったほうがいいものあるか?~~
    - ~~個人的には、テスト追加してもnpmライブラリのvalidatorのテストになるだけな気がするため不要では?と思っています。~~
    - 「npmライブラリのvalidatorを正しいパラメータで正しく利用しているか」が目的であるとの認識を得たため異常系も追加しました。

## 動作確認状況
- apigenを実行してOpenAPI生成できることを確認、controllerテスト通ることを確認。
- 行った修正がデグレを発生させていないことを確認できるか
  - 新規APIのため無し
This commit is contained in:
Kentaro Fukunaga 2024-03-11 01:29:55 +00:00
parent 2e6b7c8ab5
commit f386a8f7e0
4 changed files with 213 additions and 0 deletions

View File

@ -1664,6 +1664,59 @@
"security": [{ "bearer": [] }]
}
},
"/accounts/parent/switch": {
"post": {
"operationId": "switchParent",
"summary": "",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/SwitchParentRequest" }
}
}
},
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SwitchParentResponse"
}
}
}
},
"400": {
"description": "パラメータ不正",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"401": {
"description": "認証エラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"500": {
"description": "想定外のサーバーエラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
}
},
"tags": ["accounts"],
"security": [{ "bearer": [] }]
}
},
"/users/confirm": {
"post": {
"operationId": "confirmUser",
@ -4541,6 +4594,23 @@
"required": ["accountId", "restricted"]
},
"UpdateRestrictionStatusResponse": { "type": "object", "properties": {} },
"SwitchParentRequest": {
"type": "object",
"properties": {
"to": {
"type": "number",
"description": "切り替え先の親アカウントID"
},
"children": {
"minItems": 1,
"description": "親を変更したいアカウントIDのリスト",
"type": "array",
"items": { "type": "integer" }
}
},
"required": ["to", "children"]
},
"SwitchParentResponse": { "type": "object", "properties": {} },
"ConfirmRequest": {
"type": "object",
"properties": { "token": { "type": "string" } },

View File

@ -3,6 +3,9 @@ import { AccountsController } from './accounts.controller';
import { AccountsService } from './accounts.service';
import { ConfigModule } from '@nestjs/config';
import { AuthService } from '../auth/auth.service';
import { SwitchParentRequest } from './types/types';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
describe('AccountsController', () => {
let controller: AccountsController;
@ -32,4 +35,39 @@ describe('AccountsController', () => {
it('should be defined', () => {
expect(controller).toBeDefined();
});
describe('valdation switchParentRequest', () => {
it('最低限の有効なリクエストが成功する', async () => {
const request = new SwitchParentRequest();
request.to = 1;
request.children = [2];
const valdationObject = plainToClass(SwitchParentRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(0);
});
it('子アカウントが指定されていない場合、リクエストが失敗する', async () => {
const request = new SwitchParentRequest();
request.to = 1;
request.children = [];
const valdationObject = plainToClass(SwitchParentRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('子アカウントが重複指定されている場合、リクエストが失敗する', async () => {
const request = new SwitchParentRequest();
request.to = 1;
request.children = [2, 2];
const valdationObject = plainToClass(SwitchParentRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
});
});

View File

@ -77,6 +77,8 @@ import {
UpdateFileDeleteSettingResponse,
UpdateRestrictionStatusRequest,
UpdateRestrictionStatusResponse,
SwitchParentRequest,
SwitchParentResponse,
} from './types/types';
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
import { AuthGuard } from '../../common/guards/auth/authguards';
@ -2319,4 +2321,84 @@ export class AccountsController {
return {};
}
@ApiResponse({
status: HttpStatus.OK,
type: SwitchParentResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'パラメータ不正',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({ operationId: 'switchParent' })
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({
roles: [ADMIN_ROLES.ADMIN],
tiers: [TIERS.TIER1, TIERS.TIER2],
}),
)
@Post('parent/switch')
async switchParent(
@Req() req: Request,
@Body() body: SwitchParentRequest,
): Promise<SwitchParentResponse> {
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const ip = retrieveIp(req);
if (!ip) {
throw new HttpException(
makeErrorResponse('E000401'),
HttpStatus.UNAUTHORIZED,
);
}
const requestId = retrieveRequestId(req);
if (!requestId) {
throw new HttpException(
makeErrorResponse('E000501'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId, requestId);
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
// TODO:service層を呼び出す。本実装時に以下は削除する。
const { to, children } = body;
this.logger.log(
`[${context.getTrackingId()}] to : ${to}, children : ${children.join(
', ',
)}`,
);
return {};
}
}

View File

@ -369,6 +369,27 @@ export class UpdateRestrictionStatusRequest {
restricted: boolean;
}
export class SwitchParentRequest {
@ApiProperty({ description: '切り替え先の親アカウントID' })
@Type(() => Number)
@IsInt()
@Min(1)
to: number;
@ApiProperty({
minItems: 1,
isArray: true,
type: 'integer',
description: '親を変更したいアカウントIDのリスト',
})
@ArrayMinSize(1)
@IsArray()
@IsInt({ each: true })
@Min(1, { each: true })
@IsUnique()
children: number[];
}
// ==============================
// RESPONSE
// ==============================
@ -686,6 +707,8 @@ export class UpdateFileDeleteSettingResponse {}
export class UpdateRestrictionStatusResponse {}
export class SwitchParentResponse {}
// ==============================
// Request/Response外の型
// TODO: Request/Response/その他の型を別ファイルに分ける