Merged PR 865: IF実装・修正

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

- 音声ファイル名変更APIのIFを実装してリクエストパラメータのテストを実装しました。
- タスク一覧取得APIのレスポンスに生ファイル名を追加しました。
- OpenAPIの更新

## レビューポイント
- ファイル名変更APIのパスは適切でしょうか?
- バリデータのチェックは適切でしょうか?

## UIの変更
- なし

## クエリの変更
- IFなのでなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 既存テストを実施して、タスク一覧についてはレスポンス期待値を修正。
    - タスク一覧画面が正常に見えることを確認
This commit is contained in:
makabe.t 2024-04-12 01:36:49 +00:00
parent 33d4ab3d2f
commit f209c7359e
7 changed files with 244 additions and 0 deletions

View File

@ -2903,6 +2903,58 @@
"security": [{ "bearer": [] }]
}
},
"/files/rename": {
"post": {
"operationId": "fileRename",
"summary": "",
"description": "音声ファイルの表示ファイル名を変更します",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/FileRenameRequest" }
}
}
},
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/FileRenameResponse" }
}
}
},
"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": ["files"],
"security": [{ "bearer": [] }]
}
},
"/tasks": {
"get": {
"operationId": "getTasks",
@ -5283,6 +5335,18 @@
"required": ["name", "url"]
},
"TemplateUploadFinishedReqponse": { "type": "object", "properties": {} },
"FileRenameRequest": {
"type": "object",
"properties": {
"audioFileId": {
"type": "number",
"description": "ファイル名変更対象の音声ファイルID"
},
"fileName": { "type": "string", "description": "変更するファイル名" }
},
"required": ["audioFileId", "fileName"]
},
"FileRenameResponse": { "type": "object", "properties": {} },
"Assignee": {
"type": "object",
"properties": {
@ -5322,6 +5386,10 @@
"description": "音声ファイルのBlob Storage上での保存場所(ファイル名含む)のURL"
},
"fileName": { "type": "string", "description": "音声ファイル名" },
"rawFileName": {
"type": "string",
"description": "生Blob Storage上の音声ファイル名"
},
"audioDuration": {
"type": "string",
"description": "音声ファイルの録音時間(ミリ秒の整数値)"
@ -5382,6 +5450,7 @@
"optionItemList",
"url",
"fileName",
"rawFileName",
"audioDuration",
"audioCreatedDate",
"audioFinishedDate",

View File

@ -2,6 +2,9 @@ import { Test, TestingModule } from '@nestjs/testing';
import { FilesController } from './files.controller';
import { FilesService } from './files.service';
import { ConfigModule } from '@nestjs/config';
import { FileRenameRequest } from './types/types';
import { plainToClass } from 'class-transformer';
import { validate } from 'class-validator';
describe('FilesController', () => {
let controller: FilesController;
@ -28,3 +31,81 @@ describe('FilesController', () => {
expect(controller).toBeDefined();
});
});
describe('valdation FileRenameRequest', () => {
it('最低限の有効なリクエストが成功する', async () => {
const request = new FileRenameRequest();
request.audioFileId = 1;
request.fileName = 'fileName';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(0);
});
it('音声ファイルIDが指定されていない場合、リクエストが失敗する', async () => {
const request = new FileRenameRequest();
request.fileName = 'fileName';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('音声ファイルIDが0の場合、リクエストが失敗する', async () => {
const request = new FileRenameRequest();
request.audioFileId = 0;
request.fileName = 'fileName';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('音声ファイルIDが文字列の場合、リクエストが失敗する', async () => {
class InvalidFileRenameRequest {
audioFileId: string;
fileName: string;
}
const request = new InvalidFileRenameRequest();
request.audioFileId = 'invalid';
request.fileName = 'fileName';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('音声ファイル名が空文字の場合、リクエストが失敗する', async () => {
const request = new FileRenameRequest();
request.audioFileId = 1;
request.fileName = '';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
it('音声ファイル名が50文字の場合、リクエストに成功する', async () => {
const request = new FileRenameRequest();
request.audioFileId = 1;
request.fileName = 'ABCDEFGHI1ABCDEFGHI2ABCDEFGHI3ABCDEFGHI4ABCDEFGHI5';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(0);
});
it('音声ファイル名が51文字の場合、リクエストが失敗する', async () => {
const request = new FileRenameRequest();
request.audioFileId = 1;
request.fileName = 'ABCDEFGHI1ABCDEFGHI2ABCDEFGHI3ABCDEFGHI4ABCDEFGHI5A';
const valdationObject = plainToClass(FileRenameRequest, request);
const errors = await validate(valdationObject);
expect(errors.length).toBe(1);
});
});

View File

@ -27,6 +27,8 @@ import {
AudioUploadFinishedResponse,
AudioUploadLocationRequest,
AudioUploadLocationResponse,
FileRenameRequest,
FileRenameResponse,
TemplateDownloadLocationRequest,
TemplateDownloadLocationResponse,
TemplateUploadFinishedReqponse,
@ -533,4 +535,74 @@ export class FilesController {
await this.filesService.templateUploadFinished(context, userId, url, name);
return {};
}
@ApiResponse({
status: HttpStatus.OK,
type: FileRenameResponse,
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: 'fileRename',
description: '音声ファイルの表示ファイル名を変更します',
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@Post('rename')
async fileRename(
@Req() req: Request,
@Body() body: FileRenameRequest,
): Promise<FileRenameResponse> {
const { audioFileId, fileName } = 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: ファイル名変更処理を実装する
return {};
}
}

View File

@ -8,6 +8,7 @@ import {
IsInt,
IsNotEmpty,
IsNumberString,
IsString,
MaxLength,
Min,
MinLength,
@ -141,3 +142,18 @@ export class TemplateUploadFinishedRequest {
}
export class TemplateUploadFinishedReqponse {}
export class FileRenameRequest {
@ApiProperty({ description: 'ファイル名変更対象の音声ファイルID' })
@Type(() => Number)
@Min(1)
@IsInt()
audioFileId: number;
@ApiProperty({ description: '変更するファイル名' })
@IsString()
@MaxLength(50)
@MinLength(1)
fileName: string;
}
export class FileRenameResponse {}

View File

@ -104,6 +104,7 @@ describe('TasksService', () => {
authorId: 'AUTHOR',
comment: 'comment',
fileName: 'test.zip',
rawFileName: 'test.zip',
fileSize: 123000,
isEncrypted: true,
jobNumber: '00000001',
@ -365,6 +366,7 @@ describe('TasksService', () => {
authorId: 'AUTHOR',
comment: 'comment',
fileName: 'test.zip',
rawFileName: 'test.zip',
fileSize: 123000,
isEncrypted: true,
jobNumber: '00000001',
@ -500,6 +502,7 @@ describe('TasksService', () => {
authorId: 'AUTHOR',
comment: 'comment',
fileName: 'test.zip',
rawFileName: 'test.zip',
fileSize: 123000,
isEncrypted: true,
jobNumber: '00000001',

View File

@ -66,6 +66,7 @@ const createTask = (
audioFormat: file.audio_format,
comment: file.comment ?? '',
fileName: file.file_name,
rawFileName: file.raw_file_name,
fileSize: file.file_size,
isEncrypted: file.is_encrypted,
url: file.url,

View File

@ -110,6 +110,8 @@ export class Task {
url: string;
@ApiProperty({ description: '音声ファイル名' })
fileName: string;
@ApiProperty({ description: '生Blob Storage上の音声ファイル名' })
rawFileName: string;
@ApiProperty({
description: '音声ファイルの録音時間(ミリ秒の整数値)',
})