Merged PR 774: 画面実装(csv読み込み部分切り出し)

## 概要
[タスク 3754: 画面実装(csv読み込み部分切り出し)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3754)

- CSVファイルの内容を入力として、JSONに変換する処理&テストを実装
- 画面実装時の想定としては、以下の流れと想定しており、本Taskの範囲は1のみ
  1. csv->jsonへパースが出来るか?(csvの形式として合っていて読み込み可能か?)
  2. json内の各パラメータは問題ないか?(データの制限や組み合わせは問題ないか?)
- 使い方を示す & 動作確認のために、client側でもテストを実施できるよう修正(※pipelineでは実行されない)

## レビューポイント
- テストケースを見て、使い方は分かるか
- CSVの形式自体が想定とズレていた場合は入力を弾く必要がある想定だが、間違っていないか
- 利用ライブラリはメジャーかつ便利そうなものを選定したが、問題なさそうか

## 動作確認状況
- npm run testを通過
This commit is contained in:
湯本 開 2024-02-28 09:03:27 +00:00
parent 71127a6db9
commit 363f12f86f
12 changed files with 5522 additions and 736 deletions

View File

@ -0,0 +1,5 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,8 @@
"typecheck": "tsc --noEmit",
"codegen": "sh codegen.sh",
"lint": "eslint --cache . --ext .js,.ts,.tsx",
"lint:fix": "npm run lint -- --fix"
"lint:fix": "npm run lint -- --fix",
"test": "jest"
},
"dependencies": {
"@azure/msal-browser": "^2.33.0",
@ -25,7 +26,6 @@
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.45",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.6",
@ -38,6 +38,7 @@
"jwt-decode": "^3.1.2",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"papaparse": "^5.4.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-google-recaptcha-v3": "^1.10.0",
@ -56,8 +57,10 @@
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@mdx-js/react": "^2.1.2",
"@openapitools/openapi-generator-cli": "^2.5.2",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.14.191",
"@types/luxon": "^3.2.0",
"@types/papaparse": "^5.3.14",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/redux-mock-store": "^1.0.3",
@ -67,16 +70,18 @@
"babel-loader": "^8.2.5",
"eslint": "^8.19.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-config-prettier": "^8.10.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"jest": "^29.7.0",
"license-checker": "^25.0.1",
"prettier": "^2.7.1",
"prettier": "^2.8.8",
"redux-mock-store": "^1.5.4",
"sass": "^1.58.3",
"ts-jest": "^29.1.2",
"typescript": "^4.7.4",
"vite": "^4.1.4",
"vite-plugin-env-compatible": "^1.1.1",
@ -99,4 +104,4 @@
}
]
}
}
}

View File

@ -0,0 +1,134 @@
/* eslint-disable @typescript-eslint/naming-convention */
// Jestによるparser.tsのテスト
import fs from "fs";
import { CSVType, parseCSV } from "./parser";
describe("parse", () => {
it("指定形式のCSV文字列をパースできる", async () => {
const text = fs.readFileSync("src/common/test/test_001.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: "HOGE",
auto_renew: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
it("指定形式のヘッダでない場合、例外が送出される | author_id(値がoptionial)がない", async () => {
const text = fs.readFileSync("src/common/test/test_002.csv", "utf-8");
try {
await parseCSV(text);
fail("例外が発生しませんでした");
} catch (e) {
expect(e).toEqual(new Error("Invalid CSV format"));
}
});
it("指定形式のヘッダでない場合、例外が送出される | email(値が必須)がない", async () => {
const text = fs.readFileSync("src/common/test/test_003.csv", "utf-8");
try {
await parseCSV(text);
fail("例外が発生しませんでした");
} catch (e) {
expect(e).toEqual(new Error("Invalid CSV format"));
}
});
it("指定形式のヘッダでない場合、例外が送出される | emailがスペルミス", async () => {
const text = fs.readFileSync("src/common/test/test_004.csv", "utf-8");
try {
await parseCSV(text);
fail("例外が発生しませんでした");
} catch (e) {
expect(e).toEqual(new Error("Invalid CSV format"));
}
});
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(文字列)はnullとなる", async () => {
const text = fs.readFileSync("src/common/test/test_005.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: null,
auto_renew: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
it("指定形式のCSV文字列をパースできる | 抜けているパラメータ(数値)はnullとなる", async () => {
const text = fs.readFileSync("src/common/test/test_006.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: null,
author_id: "HOGE",
auto_renew: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
];
expect(actualData).toEqual(expectData);
});
it("指定形式のCSV文字列をパースできる | 余計なパラメータがあっても問題はない", async () => {
const text = fs.readFileSync("src/common/test/test_007.csv", "utf-8");
const actualData = await parseCSV(text);
const expectData: CSVType[] = [
{
name: "hoge",
email: "sample@example.com",
role: 1,
author_id: "HOGE",
auto_renew: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd",
prompt: 0,
},
{
name: "hoge2",
email: "sample2@example.com",
role: 1,
author_id: "HOGE2",
auto_renew: 1,
notification: 1,
encryption: 1,
encryption_password: "abcd2",
prompt: 0,
},
];
expect(actualData.length).toBe(expectData.length);
// 余計なパラメータ格納用に __parsed_extra: string[] というプロパティが作られてしまうので、既知のプロパティ毎に比較
for (let i = 0; i < actualData.length; i += 1) {
const actualValue = actualData[i];
const expectValue = expectData[i];
expect(actualValue.author_id).toEqual(expectValue.author_id);
expect(actualValue.auto_renew).toEqual(expectValue.auto_renew);
expect(actualValue.email).toEqual(expectValue.email);
expect(actualValue.encryption).toEqual(expectValue.encryption);
expect(actualValue.encryption_password).toEqual(
expectValue.encryption_password
);
expect(actualValue.name).toEqual(expectValue.name);
expect(actualValue.notification).toEqual(expectValue.notification);
expect(actualValue.prompt).toEqual(expectValue.prompt);
expect(actualValue.role).toEqual(expectValue.role);
}
});
});

View File

@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/naming-convention */
import Papa, { ParseResult } from "papaparse";
export type CSVType = {
name: string | null;
email: string | null;
role: number | null;
author_id: string | null;
auto_renew: number | null;
notification: number;
encryption: number | null;
encryption_password: string | null;
prompt: number | null;
};
// CSVTypeのプロパティ名を文字列の配列で定義する
const CSVTypeFields: (keyof CSVType)[] = [
"name",
"email",
"role",
"author_id",
"auto_renew",
"notification",
"encryption",
"encryption_password",
"prompt",
];
// 2つの配列が等しいかどうかを判定する
const equals = (lhs: string[], rhs: string[]) => {
if (lhs.length !== rhs.length) return false;
for (let i = 0; i < lhs.length; i += 1) {
if (lhs[i] !== rhs[i]) return false;
}
return true;
};
/** CSVファイルをCSVType型に変換するパーサー */
export const parseCSV = async (csvString: string): Promise<CSVType[]> =>
new Promise((resolve, reject) => {
Papa.parse<CSVType>(csvString, {
download: false,
worker: true,
header: true,
dynamicTyping: true,
complete: (results: ParseResult<CSVType>) => {
// ヘッダーがCSVTypeFieldsと一致しない場合はエラーを返す
if (!equals(results.meta.fields ?? [], CSVTypeFields)) {
reject(new Error("Invalid CSV format"));
}
resolve(results.data);
},
error: (error: Error) => {
reject(error);
},
});
});

View File

@ -0,0 +1,2 @@
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
1 name email role author_id auto_renew notification encryption encryption_password prompt
2 hoge sample@example.com 1 HOGE 1 1 1 abcd 0

View File

@ -0,0 +1,2 @@
name,email,role,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -0,0 +1,2 @@
name,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
Can't render this file because it has a wrong number of fields in line 2.

View File

@ -0,0 +1,2 @@
name,emeil,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0
1 name emeil role author_id auto_renew notification encryption encryption_password prompt
2 hoge sample@example.com 1 HOGE 1 1 1 abcd 0

View File

@ -0,0 +1,2 @@
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,,1,1,1,abcd,0
1 name email role author_id auto_renew notification encryption encryption_password prompt
2 hoge sample@example.com 1 1 1 1 abcd 0

View File

@ -0,0 +1,2 @@
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,,"HOGE",1,1,1,abcd,0
1 name email role author_id auto_renew notification encryption encryption_password prompt
2 hoge sample@example.com HOGE 1 1 1 abcd 0

View File

@ -0,0 +1,3 @@
name,email,role,author_id,auto_renew,notification,encryption,encryption_password,prompt
hoge,sample@example.com,1,"HOGE",1,1,1,abcd,0,x
hoge2,sample2@example.com,1,"HOGE2",1,1,1,abcd2,0,1,32,4,aa
Can't render this file because it has a wrong number of fields in line 2.