Merged PR 834: API IF実装

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

- 「プロダクト バックログ項目 1242: パートナーを削除したい」のAPI IFを作成しました
- 影響範囲(他の機能にも影響があるか)
  - 新規IFのため影響はなし

## レビューポイント
- controllerの試験実装が初なので、テストケース過不足ないか確認いただきたいです。

## UIの変更
- 無し

## クエリの変更
- 無し

## 動作確認状況
- ユニットテストが通ることを確認、ローカル環境でpostmanで呼び出せることを確認、SWAGGER UI上で追加されていることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ユニットテストが通ることを確認

## 補足
- 相談、参考資料などがあれば
This commit is contained in:
masaaki 2024-03-15 07:41:56 +00:00
parent 66c643677d
commit f80912c617
4 changed files with 216 additions and 1 deletions

View File

@ -1717,6 +1717,61 @@
"security": [{ "bearer": [] }]
}
},
"/accounts/partner/delete": {
"post": {
"operationId": "deletePartnerAccount",
"summary": "",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletePartnerAccountRequest"
}
}
}
},
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeletePartnerAccountResponse"
}
}
}
},
"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",
@ -4611,6 +4666,17 @@
"required": ["to", "children"]
},
"SwitchParentResponse": { "type": "object", "properties": {} },
"DeletePartnerAccountRequest": {
"type": "object",
"properties": {
"targetAccountId": {
"type": "number",
"description": "削除対象のアカウントID"
}
},
"required": ["targetAccountId"]
},
"DeletePartnerAccountResponse": { "type": "object", "properties": {} },
"ConfirmRequest": {
"type": "object",
"properties": { "token": { "type": "string" } },

View File

@ -3,7 +3,10 @@ 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 {
SwitchParentRequest,
DeletePartnerAccountRequest,
} from './types/types';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
@ -70,4 +73,60 @@ describe('AccountsController', () => {
expect(errors.length).toBe(1);
});
});
describe('valdation deletePartnerAccount', () => {
it('最低限の有効なリクエストが成功する', async () => {
const request = new DeletePartnerAccountRequest();
request.targetAccountId = 1;
const valdationObject = plainToClass(
DeletePartnerAccountRequest,
request,
);
const errors = await validate(valdationObject);
expect(errors.length).toBe(0);
});
it('削除対象アカウントが指定されていない場合、リクエストが失敗する', async () => {
const request = new DeletePartnerAccountRequest();
const valdationObject = plainToClass(
DeletePartnerAccountRequest,
request,
);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('削除対象アカウントが0の場合、リクエストが失敗する', async () => {
const request = new DeletePartnerAccountRequest();
request.targetAccountId = 0;
const valdationObject = plainToClass(
DeletePartnerAccountRequest,
request,
);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('削除対象アカウントが文字列(数値以外)の場合、リクエストが失敗する', async () => {
class DeletePartnerAccountRequestString {
targetAccountId: string;
}
const request = new DeletePartnerAccountRequestString();
request.targetAccountId = 'a';
const valdationObject = plainToClass(
DeletePartnerAccountRequest,
request,
);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
});
});

View File

@ -79,6 +79,8 @@ import {
UpdateRestrictionStatusResponse,
SwitchParentRequest,
SwitchParentResponse,
DeletePartnerAccountRequest,
DeletePartnerAccountResponse,
} from './types/types';
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
import { AuthGuard } from '../../common/guards/auth/authguards';
@ -2401,4 +2403,82 @@ export class AccountsController {
return {};
}
@Post('partner/delete')
@ApiResponse({
status: HttpStatus.OK,
type: DeletePartnerAccountResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description:
'実施者との親子関係不正や下位アカウント存在など削除実施条件に合致しない',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: '想定外のサーバーエラー',
type: ErrorResponse,
})
@ApiOperation({ operationId: 'deletePartnerAccount' })
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(
RoleGuard.requireds({
roles: [ADMIN_ROLES.ADMIN],
tiers: [TIERS.TIER1, TIERS.TIER2, TIERS.TIER3],
}),
)
async deletePartnerAccount(
@Req() req: Request,
@Body() body: DeletePartnerAccountRequest,
): Promise<DeletePartnerAccountResponse> {
const { targetAccountId } = body;
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層を呼び出す。本実装時に以下は削除する。
// await this.accountService.deletePartnerAccount(context, userId, targetAccountId);
return {};
}
}

View File

@ -390,6 +390,14 @@ export class SwitchParentRequest {
children: number[];
}
export class DeletePartnerAccountRequest {
@ApiProperty({ description: '削除対象のアカウントID' })
@Type(() => Number)
@IsInt()
@Min(1)
targetAccountId: number;
}
// ==============================
// RESPONSE
// ==============================
@ -709,6 +717,8 @@ export class UpdateRestrictionStatusResponse {}
export class SwitchParentResponse {}
export class DeletePartnerAccountResponse {}
// ==============================
// Request/Response外の型
// TODO: Request/Response/その他の型を別ファイルに分ける