Skip to content

Commit 9f6ae19

Browse files
feat: メンバーリポジトリのバリデーションと例外処理を強化
- メールアドレスのドメインバリデーションを追加 - メンバー作成・更新時のエラーハンドリングを改善 - Discordアカウント連携時のエラーチェックを強化 - テストケースを拡張して新しいバリデーションをカバー
1 parent 33f8bb0 commit 9f6ae19

File tree

5 files changed

+358
-157
lines changed

5 files changed

+358
-157
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"vitest": "^3.0.5"
2828
},
2929
"dependencies": {
30-
"@shizuoka-its/core": "^1.1.0-alpha.0",
30+
"@shizuoka-its/core": "^1.1.0",
3131
"cron": "^3.1.7",
3232
"discord.js": "^14.16.3",
3333
"dotenv": "^16.4.5",

src/application/repository/IMemberRepository.ts

+6
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,25 @@ export interface IMemberRepository {
5858
* メンバーを作成する
5959
* @param {MemberCreateInput} arg メンバー作成のための入力
6060
* @returns {Member} 作成されたメンバー
61+
* @throws {Error} メールアドレスが重複している場合に発生
62+
* @throws {Error} メールアドレスのドメインがshizuoka.ac.jpでない場合に発生
6163
*/
6264
insert(arg: MemberCreateInput): Promise<Member>;
6365
/**
6466
* メンバーを更新する
6567
* @param {MemberUpdateInput} arg メンバー更新のための入力
6668
* @returns {Member} 更新されたメンバー
69+
* @throws {Error} メンバーが存在しない場合に発生
70+
* @throws {Error} メールアドレスが重複している場合に発生
71+
* @throws {Error} メールアドレスのドメインがshizuoka.ac.jpでない場合に発生
6772
*/
6873
update(arg: MemberUpdateInput): Promise<Member>;
6974
/**
7075
* メンバーにDiscordアカウントを紐づける
7176
* @param {ConnectDiscordAccountInput} arg 紐づけるDiscordアカウント
7277
* @returns {Member} 更新されたメンバー
7378
* @throws {Error} メンバーが既にDiscordアカウントを持っている場合に発生 // TODO: 複数アカウントに対応する https://github.com/su-its/its-discord/issues/70
79+
* @throws {Error} メンバーが存在しない場合に発生
7480
*/
7581
connectDiscordAccount(arg: ConnectDiscordAccountInput): Promise<Member>;
7682
}

src/application/repository/__tests__/memberRepository.test.ts

+217-65
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import type { PrismaClient } from "@shizuoka-its/core";
1+
import {
2+
type PrismaClient,
3+
PrismaRecordDoesNotExistError,
4+
PrismaRuntime,
5+
PrismaUniqueConstraintError,
6+
} from "@shizuoka-its/core";
27
import { beforeEach, describe, expect, it, vi } from "vitest";
38
import Department from "../../../domain/entities/department";
49
import { RepositoryError } from "../RepositoryError";
@@ -27,7 +32,7 @@ describe("MemberRepository", () => {
2732
name: "Test User",
2833
studentId: "19XX0000",
2934
department: Department.CS,
30-
email: "test@example.com",
35+
email: "test@shizuoka.ac.jp",
3136
discordAccounts: [
3237
{
3338
id: "discord-id",
@@ -153,7 +158,7 @@ describe("MemberRepository", () => {
153158
name: "Test User",
154159
student_number: "19XX0000",
155160
department: Department.CS,
156-
mail: "test@example.com",
161+
mail: "test@shizuoka.ac.jp",
157162
};
158163

159164
const result = await repository.insert(input);
@@ -169,84 +174,231 @@ describe("MemberRepository", () => {
169174
});
170175
expect(result.id).toBe(mockMember.id);
171176
});
172-
});
173-
174-
describe("メンバー更新機能", () => {
175-
it("メンバー情報を更新できる", async () => {
176-
mockPrismaClient.member.update.mockResolvedValue(mockMember);
177177

178+
it("Emailが静大ドメイン以外の場合はエラーを投げる", async () => {
178179
const input = {
179-
id: "test-id",
180-
name: "Updated Name",
181-
student_number: "20XX0000",
180+
name: "Test User",
181+
student_number: "19XX0000",
182+
department: Department.CS,
183+
182184
};
183185

184-
const result = await repository.update(input);
185-
186-
expect(mockPrismaClient.member.update).toHaveBeenCalledWith({
187-
where: { id: input.id },
188-
data: {
189-
name: input.name,
190-
studentId: input.student_number,
191-
department: undefined,
192-
email: undefined,
193-
},
194-
include: { discordAccounts: true },
195-
});
196-
expect(result.id).toBe(mockMember.id);
186+
await expect(repository.insert(input)).rejects.toThrow(
187+
"Invalid email domain",
188+
);
197189
});
198-
});
199190

200-
describe("Discordアカウント連携機能", () => {
201-
it("Discordアカウントを紐付けできる", async () => {
202-
mockPrismaClient.discordAccount.findMany.mockResolvedValue([]);
203-
mockPrismaClient.member.update.mockResolvedValue(mockMember);
191+
it("重複したEmailでメンバーを作成しようとした場合はエラーを投げる", async () => {
192+
mockPrismaClient.member.create.mockRejectedValue(
193+
new PrismaUniqueConstraintError(
194+
new PrismaRuntime.PrismaClientKnownRequestError("P2002", {
195+
clientVersion: "2.24.1",
196+
code: "P2002",
197+
meta: {
198+
target: ["email"],
199+
},
200+
}),
201+
),
202+
);
204203

205204
const input = {
206-
memberId: "test-id",
207-
discordAccount: {
208-
id: "discord-id",
209-
nickName: "Discord User",
210-
},
205+
name: "Test User",
206+
student_number: "19XX0000",
207+
department: Department.CS,
208+
211209
};
212210

213-
const result = await repository.connectDiscordAccount(input);
211+
await expect(repository.insert(input)).rejects.toThrow(
212+
"The specified email address is already registered",
213+
);
214+
});
214215

215-
expect(mockPrismaClient.member.update).toHaveBeenCalledWith({
216-
where: { id: input.memberId },
217-
data: {
218-
discordAccounts: {
219-
connectOrCreate: {
220-
where: { id: input.discordAccount.id },
221-
create: {
222-
id: input.discordAccount.id,
223-
nickName: input.discordAccount.nickName,
224-
},
225-
},
216+
describe("メンバー更新機能", () => {
217+
it("メンバー情報を更新できる", async () => {
218+
mockPrismaClient.member.update.mockResolvedValue(mockMember);
219+
220+
const input = {
221+
id: "test-id",
222+
name: "Updated Name",
223+
student_number: "20XX0000",
224+
};
225+
226+
const result = await repository.update(input);
227+
228+
expect(mockPrismaClient.member.update).toHaveBeenCalledWith({
229+
where: { id: input.id },
230+
data: {
231+
name: input.name,
232+
studentId: input.student_number,
233+
department: undefined,
234+
email: undefined,
226235
},
227-
},
228-
include: { discordAccounts: true },
236+
include: { discordAccounts: true },
237+
});
238+
expect(result.id).toBe(mockMember.id);
229239
});
230-
expect(result.discordId).toBe(input.discordAccount.id);
231-
});
232240

233-
// TODO: 複数アカウントに対応する https://github.com/su-its/its-discord/issues/70
234-
it("既にDiscordアカウントが紐付けられている場合はエラーを投げる", async () => {
235-
mockPrismaClient.discordAccount.findMany.mockResolvedValue([
236-
{ id: "existing-discord-id" },
237-
]);
241+
it("Emailが静大ドメイン以外に更新しようとした場合はエラーを投げる", async () => {
242+
const input = {
243+
id: "test-id",
244+
name: "Test User",
245+
student_number: "19XX0000",
246+
department: Department.CS,
247+
248+
};
249+
250+
await expect(repository.update(input)).rejects.toThrow(
251+
"Invalid email domain",
252+
);
253+
});
238254

239-
const input = {
240-
memberId: "test-id",
241-
discordAccount: {
242-
id: "new-discord-id",
243-
nickName: "Discord User",
244-
},
245-
};
255+
it("存在しないメンバーを更新しようとした場合はエラーを投げる", async () => {
256+
const input = {
257+
id: "non-existent-id",
258+
name: "Test User",
259+
};
260+
261+
mockPrismaClient.member.update.mockRejectedValue(
262+
new PrismaRecordDoesNotExistError(
263+
new PrismaRuntime.PrismaClientKnownRequestError("P2001", {
264+
clientVersion: "2.24.1",
265+
code: "P2001",
266+
meta: {
267+
target: ["id"],
268+
},
269+
}),
270+
),
271+
);
246272

247-
await expect(repository.connectDiscordAccount(input)).rejects.toThrow(
248-
"Member already has a Discord account",
249-
);
273+
await expect(repository.update(input)).rejects.toThrow(
274+
"Member to update not found",
275+
);
276+
});
277+
278+
it("重複したEmailでメンバーを更新しようとした場合はエラーを投げる", async () => {
279+
mockPrismaClient.member.update.mockRejectedValue(
280+
new PrismaUniqueConstraintError(
281+
new PrismaRuntime.PrismaClientKnownRequestError("P2002", {
282+
clientVersion: "2.24.1",
283+
code: "P2002",
284+
meta: {
285+
target: ["email"],
286+
},
287+
}),
288+
),
289+
);
290+
291+
const input = {
292+
id: "test-id",
293+
name: "Test User",
294+
student_number: "19XX0000",
295+
department: Department.CS,
296+
297+
};
298+
299+
await expect(repository.update(input)).rejects.toThrow(
300+
"The specified email address is already registered",
301+
);
302+
});
303+
304+
describe("Discordアカウント連携機能", () => {
305+
it("Discordアカウントを紐付けできる", async () => {
306+
mockPrismaClient.discordAccount.findMany.mockResolvedValue([]);
307+
mockPrismaClient.member.update.mockResolvedValue(mockMember);
308+
309+
const input = {
310+
memberId: "test-id",
311+
discordAccount: {
312+
id: "discord-id",
313+
nickName: "Discord User",
314+
},
315+
};
316+
317+
const result = await repository.connectDiscordAccount(input);
318+
319+
expect(mockPrismaClient.member.update).toHaveBeenCalledWith({
320+
where: { id: input.memberId },
321+
data: {
322+
discordAccounts: {
323+
connectOrCreate: {
324+
where: { id: input.discordAccount.id },
325+
create: {
326+
id: input.discordAccount.id,
327+
nickName: input.discordAccount.nickName,
328+
},
329+
},
330+
},
331+
},
332+
include: { discordAccounts: true },
333+
});
334+
expect(result.discordId).toBe(input.discordAccount.id);
335+
});
336+
337+
// TODO: 複数アカウントに対応する https://github.com/su-its/its-discord/issues/70
338+
it("既にDiscordアカウントが紐付けられている場合はエラーを投げる", async () => {
339+
mockPrismaClient.discordAccount.findMany.mockResolvedValue([
340+
{ id: "existing-discord-id" },
341+
]);
342+
343+
const input = {
344+
memberId: "test-id",
345+
discordAccount: {
346+
id: "new-discord-id",
347+
nickName: "Discord User",
348+
},
349+
};
350+
351+
await expect(repository.connectDiscordAccount(input)).rejects.toThrow(
352+
"Member already has a Discord account",
353+
);
354+
});
355+
356+
it("存在しないメンバーを紐付けようとした場合はエラーを投げる", async () => {
357+
mockPrismaClient.discordAccount.findMany.mockResolvedValue([]);
358+
mockPrismaClient.member.update.mockRejectedValue(
359+
new PrismaRecordDoesNotExistError(
360+
new PrismaRuntime.PrismaClientKnownRequestError("P2001", {
361+
clientVersion: "2.24.1",
362+
code: "P2001",
363+
meta: {
364+
target: ["id"],
365+
},
366+
}),
367+
),
368+
);
369+
370+
const input = {
371+
memberId: "non-existent-id",
372+
discordAccount: {
373+
id: "discord-id",
374+
nickName: "Discord User",
375+
},
376+
};
377+
378+
await expect(repository.connectDiscordAccount(input)).rejects.toThrow(
379+
"Member to connect Discord account not found",
380+
);
381+
});
382+
383+
it("想定していないエラーが投げられた場合はそのまま投げる", async () => {
384+
mockPrismaClient.discordAccount.findMany.mockResolvedValue([]);
385+
mockPrismaClient.member.update.mockRejectedValue(
386+
new Error("Unexpected error"),
387+
);
388+
389+
const input = {
390+
memberId: "test-id",
391+
discordAccount: {
392+
id: "discord-id",
393+
nickName: "Discord User",
394+
},
395+
};
396+
397+
await expect(repository.connectDiscordAccount(input)).rejects.toThrow(
398+
"Unexpected error",
399+
);
400+
});
401+
});
250402
});
251403
});
252404
});

0 commit comments

Comments
 (0)