From a6218b0e7a0c1359e5b1648910fc9e26c8929f5a Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Thu, 7 Oct 2021 08:35:53 +0900 Subject: [PATCH 01/61] add comment --- app/server/src/__test__/chat.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index dab134e9..1bb433df 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -207,6 +207,8 @@ describe("機能テスト", () => { describe("ユーザーがルームに入る", () => { afterAll(() => { + // このステップで定義したlistenerが残っていると以降のテストに支障が出るため解除する。 + // 以降のテストでも同様にlistenerの解除を行なっている。 clientSockets[0].off("PUB_USER_COUNT") }) @@ -286,7 +288,9 @@ describe("機能テスト", () => { describe("トピックの状態遷移", () => { afterEach(async () => { + // listenerの解除 clientSockets[0].off("PUB_CHANGE_TOPIC_STATE") + // DBの処理に若干時間がかかるため、少し待つようにする await delay(100) }) From a0496abfafb8221663ad1fdc01d11726ae7d2330 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Thu, 7 Oct 2021 20:40:43 +0900 Subject: [PATCH 02/61] reimplement test for posting chatItem --- app/server/src/__test__/chat.ts | 279 +++++++++++++++++--------------- 1 file changed, 145 insertions(+), 134 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 1bb433df..a880df6a 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -19,14 +19,17 @@ import RoomFactory from "../infra/factory/RoomFactory" import AdminService from "../service/admin/AdminService" import { AdminEnterRoomResponse, + ChatItemModel, ErrorResponse, PubChangeTopicStateParam, + PubChatItemParam, RoomModel, ServerListenEventsMap, ServerPubEventsMap, SuccessResponse, } from "sushi-chat-shared" import delay from "../utils/delay" +import ChatItem from "../domain/chatItem/ChatItem" describe("機能テスト", () => { const MATCHING = { @@ -47,6 +50,14 @@ describe("機能テスト", () => { let pgPool: PGPool let roomData: RoomModel + let messageId: string + let reactionId: string + let questionId: string + let answerId: string + let message: ChatItemModel + let reaction: ChatItemModel + let question: ChatItemModel + let answer: ChatItemModel // テストのセットアップ beforeAll(async (done) => { @@ -76,6 +87,11 @@ describe("機能テスト", () => { adminAuth, ) + messageId = uuid() + reactionId = uuid() + questionId = uuid() + answerId = uuid() + const app = express() const httpServer = createServer(app) io = await createSocketIOServer( @@ -279,7 +295,7 @@ describe("機能テスト", () => { }) clientSockets[1].emit( "ENTER_ROOM", - { roomId: roomData.id, iconId: 2, speakerTopicId: 1 }, + { roomId: roomData.id, iconId: 2, speakerTopicId: 3 }, // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) @@ -411,139 +427,134 @@ describe("機能テスト", () => { ) }) }) - // - // describe("コメントを投稿する", () => { - // beforeAll(() => { - // clientSockets[2].emit( - // "ENTER_ROOM", - // { roomId, iconId: "3" }, - // (res: any) => {}, - // ) - // }) - // - // afterEach(() => { - // clientSockets[0].off("PUB_CHAT_ITEM") - // }) - // - // beforeEach(async () => await delay(100)) - // - // test("Messageの投稿", (resolve) => { - // clientSockets[0].on("PUB_CHAT_ITEM", (res) => { - // expect(res).toStrictEqual({ - // id: messageId, - // topicId: topics[0].id, - // type: "message", - // iconId: "2", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "コメント", - // target: null, - // }) - // resolve() - // }) - // clientSockets[1].emit("POST_CHAT_ITEM", { - // id: messageId, - // topicId: topics[0].id, - // type: "message", - // content: "コメント", - // }) - // }) - // - // test("Reactionの投稿", (resolve) => { - // clientSockets[0].on("PUB_CHAT_ITEM", (res) => { - // expect(res).toStrictEqual({ - // id: reactionId, - // topicId: topics[0].id, - // type: "reaction", - // iconId: "3", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // target: { - // id: messageId, - // topicId: topics[0].id, - // type: "message", - // iconId: "2", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "コメント", - // target: null, - // }, - // }) - // resolve() - // }) - // clientSockets[2].emit("POST_CHAT_ITEM", { - // id: reactionId, - // topicId: topics[0].id, - // type: "reaction", - // reactionToId: messageId, - // }) - // }) - // - // test("Questionの投稿", (resolve) => { - // clientSockets[0].on("PUB_CHAT_ITEM", (res) => { - // expect(res).toStrictEqual({ - // id: questionId, - // topicId: topics[0].id, - // type: "question", - // iconId: "2", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "質問", - // }) - // resolve() - // }) - // clientSockets[1].emit("POST_CHAT_ITEM", { - // id: questionId, - // topicId: topics[0].id, - // type: "question", - // content: "質問", - // }) - // }) - // - // test("Answerの投稿", (resolve) => { - // clientSockets[0].on("PUB_CHAT_ITEM", (res) => { - // expect(res).toStrictEqual({ - // id: answerId, - // topicId: topics[0].id, - // type: "answer", - // iconId: "3", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "回答", - // target: { - // id: questionId, - // topicId: topics[0].id, - // type: "question", - // iconId: "2", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "質問", - // }, - // }) - // resolve() - // }) - // clientSockets[2].emit("POST_CHAT_ITEM", { - // id: answerId, - // topicId: topics[0].id, - // type: "answer", - // content: "回答", - // target: questionId, - // }) - // }) - // }) - // + + describe("コメントを投稿する", () => { + beforeAll(() => { + clientSockets[2].emit( + "ENTER_ROOM", + { roomId: roomData.id, iconId: 3, speakerTopicId: 0 }, + () => {}, + ) + }) + + afterEach(() => { + // listenerを解除 + clientSockets[0].off("PUB_CHAT_ITEM") + + // beforeEach(async () => await delay(100)) + }) + + test("正常系_Messageの投稿", (resolve) => { + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { + expect(res).toStrictEqual({ + id: messageId, + topicId: roomData.topics[2].id, + createdAt: MATCHING.DATE, + type: "message", + senderType: "speaker", + iconId: 2, + content: "コメント", + timestamp: expect.any(Number), + }) + message = res + resolve() + }) + clientSockets[1].emit( + "POST_CHAT_ITEM", + { + id: messageId, + topicId: roomData.topics[2].id, + type: "message", + content: "コメント", + }, + () => {}, + ) + }) + + test("正常系_Reactionの投稿", (resolve) => { + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { + expect(res).toStrictEqual({ + id: reactionId, + topicId: roomData.topics[2].id, + createdAt: MATCHING.DATE, + type: "reaction", + senderType: "general", + iconId: 3, + quote: message, + timestamp: expect.any(Number), + }) + reaction = res + resolve() + }) + clientSockets[2].emit( + "POST_CHAT_ITEM", + { + id: reactionId, + topicId: roomData.topics[2].id, + type: "reaction", + quoteId: messageId, + }, + () => {}, + ) + }) + + test("正常系_Questionの投稿", (resolve) => { + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { + expect(res).toStrictEqual({ + id: questionId, + topicId: roomData.topics[2].id, + createdAt: MATCHING.DATE, + type: "question", + senderType: "speaker", + iconId: 2, + content: "質問", + timestamp: expect.any(Number), + }) + question = res + resolve() + }) + clientSockets[1].emit( + "POST_CHAT_ITEM", + { + id: questionId, + topicId: roomData.topics[2].id, + type: "question", + content: "質問", + }, + () => {}, + ) + }) + + test("正常系_Answerの投稿", (resolve) => { + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { + expect(res).toStrictEqual({ + id: answerId, + topicId: roomData.topics[2].id, + createdAt: MATCHING.DATE, + type: "answer", + senderType: "general", + iconId: 3, + content: "回答", + quote: question, + timestamp: expect.any(Number), + }) + answer = res + resolve() + }) + clientSockets[2].emit( + "POST_CHAT_ITEM", + { + id: answerId, + topicId: roomData.topics[2].id, + type: "answer", + content: "回答", + quoteId: questionId, + }, + () => {}, + ) + }) + }) + // describe("スタンプの投稿", () => { // test("スタンプを投稿する", (resolve) => { // clientSockets[0].on("PUB_STAMP", (res) => { From 29951bc9926fd1d3e230fff9359bab71b3b879e5 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Thu, 7 Oct 2021 21:07:36 +0900 Subject: [PATCH 03/61] add abnormal test --- app/server/src/__test__/chat.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index a880df6a..9e07fc67 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -440,8 +440,6 @@ describe("機能テスト", () => { afterEach(() => { // listenerを解除 clientSockets[0].off("PUB_CHAT_ITEM") - - // beforeEach(async () => await delay(100)) }) test("正常系_Messageの投稿", (resolve) => { @@ -553,6 +551,29 @@ describe("機能テスト", () => { () => {}, ) }) + + test("異常系_存在しないtopicには投稿できない", () => { + const notExistTopicId = 10 + + clientSockets[1].emit( + "POST_CHAT_ITEM", + { + id: uuid(), + topicId: notExistTopicId, + type: "message", + content: "存在しないトピックへの投稿", + }, + (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { + code: MATCHING.CODE, + message: MATCHING.TEXT, + }, + }) + }, + ) + }) }) // describe("スタンプの投稿", () => { From cff11246473612b561123706243e726de1cf3ee7 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Thu, 7 Oct 2021 21:16:45 +0900 Subject: [PATCH 04/61] add test for posting stamp --- app/server/src/__test__/chat.ts | 61 ++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 9e07fc67..6ca301ba 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -23,9 +23,11 @@ import { ErrorResponse, PubChangeTopicStateParam, PubChatItemParam, + PubStampParam, RoomModel, ServerListenEventsMap, ServerPubEventsMap, + StampModel, SuccessResponse, } from "sushi-chat-shared" import delay from "../utils/delay" @@ -58,6 +60,7 @@ describe("機能テスト", () => { let reaction: ChatItemModel let question: ChatItemModel let answer: ChatItemModel + let stamps: StampModel[] // テストのセットアップ beforeAll(async (done) => { @@ -576,22 +579,48 @@ describe("機能テスト", () => { }) }) - // describe("スタンプの投稿", () => { - // test("スタンプを投稿する", (resolve) => { - // clientSockets[0].on("PUB_STAMP", (res) => { - // expect(res).toStrictEqual([ - // { - // userId: clientSockets[2].id, - // timestamp: expect.any(Number), - // topicId: topics[0].id, - // }, - // ]) - // resolve() - // }) - // clientSockets[2].emit("POST_STAMP", { topicId: topics[0].id }) - // }) - // }) - // + describe("スタンプの投稿", () => { + afterAll(() => { + // listenerを解除 + clientSockets[0].off("PUB_STAMP") + }) + + test("正常系_スタンプを投稿する", (resolve) => { + clientSockets[0].on("PUB_STAMP", (res) => { + expect(res).toStrictEqual([ + { + id: MATCHING.UUID, + topicId: roomData.topics[0].id, + timestamp: expect.any(Number), + createdAt: MATCHING.DATE, + }, + ]) + stamps = res + resolve() + }) + clientSockets[2].emit( + "POST_STAMP", + { topicId: roomData.topics[0].id }, + () => {}, + ) + }) + + test("異常系_存在しないトピックにはスタンプを投稿できない", () => { + const notExistTopicId = 10 + + clientSockets[2].emit( + "POST_STAMP", + { topicId: notExistTopicId }, + (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { code: MATCHING.CODE, message: MATCHING.TEXT }, + }) + }, + ) + }) + }) + // describe("途中から入室した場合", () => { // beforeAll(async () => await delay(100)) // From 0de7158f7f53e2144cde83ed36cb01f7fe7640fd Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Thu, 7 Oct 2021 22:03:10 +0900 Subject: [PATCH 05/61] add abnormal test --- app/server/src/__test__/chat.ts | 50 +++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 6ca301ba..b37cd298 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -555,7 +555,29 @@ describe("機能テスト", () => { ) }) - test("異常系_存在しないtopicには投稿できない", () => { + test.skip("異常系_進行中でないtopicには投稿できない", (resolve) => { + clientSockets[1].emit( + "POST_CHAT_ITEM", + { + id: uuid(), + topicId: roomData.topics[0].id, + type: "message", + content: "ongoingでないトピックへの投稿", + }, + (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { + code: MATCHING.CODE, + message: MATCHING.TEXT, + }, + }) + resolve() + }, + ) + }) + + test("異常系_存在しないtopicには投稿できない", (resolve) => { const notExistTopicId = 10 clientSockets[1].emit( @@ -574,6 +596,7 @@ describe("機能テスト", () => { message: MATCHING.TEXT, }, }) + resolve() }, ) }) @@ -590,7 +613,7 @@ describe("機能テスト", () => { expect(res).toStrictEqual([ { id: MATCHING.UUID, - topicId: roomData.topics[0].id, + topicId: roomData.topics[2].id, timestamp: expect.any(Number), createdAt: MATCHING.DATE, }, @@ -598,17 +621,31 @@ describe("機能テスト", () => { stamps = res resolve() }) - clientSockets[2].emit( + clientSockets[1].emit( "POST_STAMP", - { topicId: roomData.topics[0].id }, + { topicId: roomData.topics[2].id }, () => {}, ) }) - test("異常系_存在しないトピックにはスタンプを投稿できない", () => { + test.skip("異常系_進行中でないトピックにはスタンプを投稿できない", (resolve) => { + clientSockets[1].emit( + "POST_STAMP", + { topicId: roomData.topics[0].id }, + (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { code: MATCHING.CODE, message: MATCHING.TEXT }, + }) + resolve() + }, + ) + }) + + test.skip("異常系_存在しないトピックにはスタンプを投稿できない", (resolve) => { const notExistTopicId = 10 - clientSockets[2].emit( + clientSockets[1].emit( "POST_STAMP", { topicId: notExistTopicId }, (res) => { @@ -616,6 +653,7 @@ describe("機能テスト", () => { result: "error", error: { code: MATCHING.CODE, message: MATCHING.TEXT }, }) + resolve() }, ) }) From 146f58b77dbd9dd68c0ccdecc515a1ff42e820a2 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Fri, 8 Oct 2021 21:19:04 +0900 Subject: [PATCH 06/61] don't unnecessary formating --- .../infra/repository/chatItem/ChatItemRepository.ts | 13 ++++++------- .../src/infra/repository/room/RoomRepository.ts | 5 ++--- app/server/src/utils/date.ts | 2 -- 3 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 app/server/src/utils/date.ts diff --git a/app/server/src/infra/repository/chatItem/ChatItemRepository.ts b/app/server/src/infra/repository/chatItem/ChatItemRepository.ts index 08abb410..706f360c 100644 --- a/app/server/src/infra/repository/chatItem/ChatItemRepository.ts +++ b/app/server/src/infra/repository/chatItem/ChatItemRepository.ts @@ -6,7 +6,6 @@ import Answer from "../../../domain/chatItem/Answer" import ChatItem from "../../../domain/chatItem/ChatItem" import PGPool from "../PGPool" import { ChatItemSenderType, ChatItemType } from "sushi-chat-shared" -import { formatDate } from "../../../utils/date" import User from "../../../domain/user/User" class ChatItemRepository implements IChatItemRepository { @@ -29,7 +28,7 @@ class ChatItemRepository implements IChatItemRepository { message.quote ? message.quote.id : null, message.content, message.timestamp, - formatDate(message.createdAt), + message.createdAt, ]) } catch (e) { ChatItemRepository.logError(e, "saveMessage()") @@ -55,7 +54,7 @@ class ChatItemRepository implements IChatItemRepository { ChatItemRepository.senderTypeMap[reaction.senderType], reaction.quote.id, reaction.timestamp, - formatDate(reaction.createdAt), + reaction.createdAt, ]) } catch (e) { ChatItemRepository.logError(e, "saveReaction()") @@ -81,7 +80,7 @@ class ChatItemRepository implements IChatItemRepository { ChatItemRepository.senderTypeMap[question.senderType], question.content, question.timestamp, - formatDate(question.createdAt), + question.createdAt, ]) } catch (e) { ChatItemRepository.logError(e, "saveQuestion()") @@ -108,7 +107,7 @@ class ChatItemRepository implements IChatItemRepository { answer.quote.id, answer.content, answer.timestamp, - formatDate(answer.createdAt), + answer.createdAt, ]) } catch (e) { ChatItemRepository.logError(e, "saveAnswer()") @@ -192,8 +191,8 @@ class ChatItemRepository implements IChatItemRepository { const timestamp = row.timestamp const createdAt = row.created_at const iconId = row.icon_id - const isAdmin = row.isAdmin - const isSystem = row.isSystem + const isAdmin = row.is_admin + const isSystem = row.is_system const systemUser = new User(userId, isAdmin, isSystem, roomId, iconId) diff --git a/app/server/src/infra/repository/room/RoomRepository.ts b/app/server/src/infra/repository/room/RoomRepository.ts index d3e4733e..396d4380 100644 --- a/app/server/src/infra/repository/room/RoomRepository.ts +++ b/app/server/src/infra/repository/room/RoomRepository.ts @@ -7,7 +7,6 @@ import IStampRepository from "../../../domain/stamp/IStampRepository" import Topic, { TopicTimeData } from "../../../domain/room/Topic" import PGPool from "../PGPool" import { RoomState, TopicState } from "sushi-chat-shared" -import { formatDate } from "../../../utils/date" import IAdminRepository from "../../../domain/admin/IAdminRepository" import User from "../../../domain/user/User" @@ -181,7 +180,7 @@ class RoomRepository implements IRoomRepository { room.startAt, room.finishAt, room.archivedAt, - formatDate(new Date()), + new Date(), room.id, ]) } @@ -204,7 +203,7 @@ class RoomRepository implements IRoomRepository { pgClient.query(topicQuery, [ RoomRepository.topicStateMap[t.state], room.topicTimeData[t.id].offsetTime, - formatDate(new Date()), + new Date(), room.id, t.id, ]), diff --git a/app/server/src/utils/date.ts b/app/server/src/utils/date.ts deleted file mode 100644 index 06660e55..00000000 --- a/app/server/src/utils/date.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const formatDate = (date: Date) => - date.toISOString().replace(/T/, " ").replace(/\..+/, "") From 1192aea4e25117e409a0c2b1c3a23eaf70851ebb Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Fri, 8 Oct 2021 21:19:17 +0900 Subject: [PATCH 07/61] make async --- app/server/src/ioServer.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/server/src/ioServer.ts b/app/server/src/ioServer.ts index ee96f09e..247126a0 100644 --- a/app/server/src/ioServer.ts +++ b/app/server/src/ioServer.ts @@ -154,9 +154,9 @@ const createSocketIOServer = async ( }) // トピック状態の変更 - socket.on("ADMIN_CHANGE_TOPIC_STATE", (received, callback) => { + socket.on("ADMIN_CHANGE_TOPIC_STATE", async (received, callback) => { try { - roomService.changeTopicState({ + await roomService.changeTopicState({ userId, topicId: received.topicId, state: received.state, @@ -223,9 +223,9 @@ const createSocketIOServer = async ( }) // スタンプを投稿する - socket.on("POST_STAMP", (received, callback) => { + socket.on("POST_STAMP", async (received, callback) => { try { - stampService.post({ + await stampService.post({ userId, topicId: received.topicId, }) @@ -235,9 +235,9 @@ const createSocketIOServer = async ( } }) - socket.on("POST_PINNED_MESSAGE", (received, callback) => { + socket.on("POST_PINNED_MESSAGE", async (received, callback) => { try { - chatItemService.pinChatItem({ chatItemId: received.chatItemId }) + await chatItemService.pinChatItem({ chatItemId: received.chatItemId }) callback({ result: "success", data: undefined }) } catch (e) { handleError(callback, "POST_PINNED_MESSAGE", e) @@ -246,9 +246,9 @@ const createSocketIOServer = async ( // ルームを終了する // eslint-disable-next-line @typescript-eslint/no-unused-vars - socket.on("ADMIN_FINISH_ROOM", (_, callback) => { + socket.on("ADMIN_FINISH_ROOM", async (_, callback) => { try { - roomService.finish({ userId: userId }) + await roomService.finish({ userId: userId }) callback({ result: "success", data: undefined }) } catch (e) { handleError(callback, "ADMIN_FINISH_ROOM", e) @@ -256,9 +256,9 @@ const createSocketIOServer = async ( }) //接続解除時に行う処理 - socket.on("disconnect", () => { + socket.on("disconnect", async () => { try { - userService.leaveRoom({ userId }) + await userService.leaveRoom({ userId }) } catch (e) { logError("disconnect", e) } From c1dffaf01081cb50d1ba683378d96e3e81ebc990 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Fri, 8 Oct 2021 21:30:35 +0900 Subject: [PATCH 08/61] add comments --- app/server/src/__test__/chat.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index b37cd298..f2f4f977 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -31,7 +31,6 @@ import { SuccessResponse, } from "sushi-chat-shared" import delay from "../utils/delay" -import ChatItem from "../domain/chatItem/ChatItem" describe("機能テスト", () => { const MATCHING = { @@ -436,6 +435,7 @@ describe("機能テスト", () => { clientSockets[2].emit( "ENTER_ROOM", { roomId: roomData.id, iconId: 3, speakerTopicId: 0 }, + // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) }) @@ -468,6 +468,7 @@ describe("機能テスト", () => { type: "message", content: "コメント", }, + // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) }) @@ -495,6 +496,7 @@ describe("機能テスト", () => { type: "reaction", quoteId: messageId, }, + // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) }) @@ -522,6 +524,7 @@ describe("機能テスト", () => { type: "question", content: "質問", }, + // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) }) @@ -551,10 +554,12 @@ describe("機能テスト", () => { content: "回答", quoteId: questionId, }, + // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) }) + // FIXME: アプリケーションの方が未対応 test.skip("異常系_進行中でないtopicには投稿できない", (resolve) => { clientSockets[1].emit( "POST_CHAT_ITEM", @@ -628,6 +633,7 @@ describe("機能テスト", () => { ) }) + // FIXME: アプリケーションの方が未対応 test.skip("異常系_進行中でないトピックにはスタンプを投稿できない", (resolve) => { clientSockets[1].emit( "POST_STAMP", From 281d8f7ceee50568e22a829c890ea4b895bbcd2d Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sat, 9 Oct 2021 09:28:54 +0900 Subject: [PATCH 09/61] test for pinning chatitem --- app/server/src/__test__/chat.ts | 39 ++++++++++++++++++- .../src/service/chatItem/ChatItemService.ts | 1 + 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index f2f4f977..d53f9f47 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -23,6 +23,7 @@ import { ErrorResponse, PubChangeTopicStateParam, PubChatItemParam, + PubPinnedMessageParam, PubStampParam, RoomModel, ServerListenEventsMap, @@ -430,7 +431,7 @@ describe("機能テスト", () => { }) }) - describe("コメントを投稿する", () => { + describe("ChatItemの投稿", () => { beforeAll(() => { clientSockets[2].emit( "ENTER_ROOM", @@ -607,7 +608,41 @@ describe("機能テスト", () => { }) }) - describe("スタンプの投稿", () => { + describe("ChatItemをピン留め", () => { + test("正常系_speakerがChatItemをピン留めする", (resolve) => { + clientSockets[0].on("PUB_PINNED_MESSAGE", (res) => { + expect(res).toStrictEqual({ + topicId: roomData.topics[2].id, + chatItemId: messageId, + }) + resolve() + }) + clientSockets[1].emit( + "POST_PINNED_MESSAGE", + { topicId: roomData.topics[2].id, chatItemId: messageId }, + (res) => { + expect(res).toStrictEqual({ result: "success" }) + }, + ) + }) + + // TODO: アプリケーション側が未対応 + test.skip("異常系_speaker以外はピン留めできない", (resolve) => { + clientSockets[2].emit( + "POST_PINNED_MESSAGE", + { topicId: roomData.topics[2].id, chatItemId: messageId }, + (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { code: MATCHING.CODE, message: MATCHING.TEXT }, + }) + resolve() + }, + ) + }) + }) + + describe("Stampの投稿", () => { afterAll(() => { // listenerを解除 clientSockets[0].off("PUB_STAMP") diff --git a/app/server/src/service/chatItem/ChatItemService.ts b/app/server/src/service/chatItem/ChatItemService.ts index 935b4144..13878631 100644 --- a/app/server/src/service/chatItem/ChatItemService.ts +++ b/app/server/src/service/chatItem/ChatItemService.ts @@ -170,6 +170,7 @@ class ChatItemService { chatItemId, this.chatItemRepository, ) + // TODO: speakerしかピン留めできないようにする this.chatItemDelivery.pinChatItem(pinnedChatItem) await this.chatItemRepository.pinChatItem(pinnedChatItem) From 7fa675ad3fe8bece8ed7df74693c9e2369bfd518 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sat, 9 Oct 2021 10:02:08 +0900 Subject: [PATCH 10/61] add test for pinning chatItem --- app/server/src/__test__/chat.ts | 82 +++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index d53f9f47..5637417d 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -56,10 +56,12 @@ describe("機能テスト", () => { let reactionId: string let questionId: string let answerId: string + let pinnedMessageId: string let message: ChatItemModel let reaction: ChatItemModel let question: ChatItemModel let answer: ChatItemModel + let pinnedMessage: ChatItemModel let stamps: StampModel[] // テストのセットアップ @@ -94,6 +96,7 @@ describe("機能テスト", () => { reactionId = uuid() questionId = uuid() answerId = uuid() + pinnedMessageId = uuid() const app = express() const httpServer = createServer(app) @@ -254,7 +257,11 @@ describe("機能テスト", () => { test("正常系_一般ユーザーがルームに入る", async (resolve) => { clientSockets[0].emit( "ENTER_ROOM", - { roomId: roomData.id, iconId: 1, speakerTopicId: 1 }, + { + roomId: roomData.id, + iconId: 1, + speakerTopicId: roomData.topics[0].id, + }, (res) => { expect(res).toStrictEqual({ result: "success", @@ -298,7 +305,11 @@ describe("機能テスト", () => { }) clientSockets[1].emit( "ENTER_ROOM", - { roomId: roomData.id, iconId: 2, speakerTopicId: 3 }, + { + roomId: roomData.id, + iconId: 2, + speakerTopicId: roomData.topics[2].id, + }, // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) @@ -435,7 +446,11 @@ describe("機能テスト", () => { beforeAll(() => { clientSockets[2].emit( "ENTER_ROOM", - { roomId: roomData.id, iconId: 3, speakerTopicId: 0 }, + { + roomId: roomData.id, + iconId: 3, + speakerTopicId: roomData.topics[0].id, + }, // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) @@ -609,7 +624,49 @@ describe("機能テスト", () => { }) describe("ChatItemをピン留め", () => { - test("正常系_speakerがChatItemをピン留めする", (resolve) => { + // 進行中でないTopicへのピン留めテストのためにChatItemを一つ投稿しておく + beforeAll((resolve) => { + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { + pinnedMessage = res + + // listenerを解除 + clientSockets[0].off("PUB_CHAT_ITEM") + resolve() + }) + + adminSocket.emit( + "ADMIN_CHANGE_TOPIC_STATE", + { topicId: roomData.topics[0].id, state: "ongoing" }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + () => {}, + ) + + clientSockets[2].emit( + "POST_CHAT_ITEM", + { + id: pinnedMessageId, + topicId: roomData.topics[0].id, + type: "message", + content: "ピン留めされるメッセージ", + quoteId: undefined, + }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + () => {}, + ) + + adminSocket.emit( + "ADMIN_CHANGE_TOPIC_STATE", + { topicId: roomData.topics[2].id, state: "ongoing" }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + () => {}, + ) + }) + + afterEach(() => { + clientSockets[0].off("PUB_PINNED_MESSAGE") + }) + + test("正常系_speakerが進行中のTopicにChatItemをピン留めする", (resolve) => { clientSockets[0].on("PUB_PINNED_MESSAGE", (res) => { expect(res).toStrictEqual({ topicId: roomData.topics[2].id, @@ -626,6 +683,23 @@ describe("機能テスト", () => { ) }) + test("正常系_speakerが進行中でないTopicにChatItemをピン留めする", (resolve) => { + clientSockets[0].on("PUB_PINNED_MESSAGE", (res) => { + expect(res).toStrictEqual({ + topicId: roomData.topics[0].id, + chatItemId: pinnedMessageId, + }) + resolve() + }) + clientSockets[2].emit( + "POST_PINNED_MESSAGE", + { topicId: roomData.topics[0].id, chatItemId: pinnedMessageId }, + (res) => { + expect(res).toStrictEqual({ result: "success" }) + }, + ) + }) + // TODO: アプリケーション側が未対応 test.skip("異常系_speaker以外はピン留めできない", (resolve) => { clientSockets[2].emit( From 28467a0a72cddcf3252fd1708f74a6294c75f17a Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 17 Oct 2021 21:22:49 +0900 Subject: [PATCH 11/61] can post chatitem to not ongoing topic --- app/server/src/__test__/chat.ts | 97 +++++++++++++-------------------- 1 file changed, 38 insertions(+), 59 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 5637417d..333fb2b6 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -56,12 +56,12 @@ describe("機能テスト", () => { let reactionId: string let questionId: string let answerId: string - let pinnedMessageId: string + let notOnGoingTopicMessageId: string let message: ChatItemModel let reaction: ChatItemModel let question: ChatItemModel let answer: ChatItemModel - let pinnedMessage: ChatItemModel + let notOnGoingTopicMessage: ChatItemModel let stamps: StampModel[] // テストのセットアップ @@ -92,12 +92,6 @@ describe("機能テスト", () => { adminAuth, ) - messageId = uuid() - reactionId = uuid() - questionId = uuid() - answerId = uuid() - pinnedMessageId = uuid() - const app = express() const httpServer = createServer(app) io = await createSocketIOServer( @@ -442,6 +436,7 @@ describe("機能テスト", () => { }) }) + // NOTE: roomData.topics[2]のトピックが進行中になっている前提 describe("ChatItemの投稿", () => { beforeAll(() => { clientSockets[2].emit( @@ -462,6 +457,8 @@ describe("機能テスト", () => { }) test("正常系_Messageの投稿", (resolve) => { + messageId = uuid() + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { expect(res).toStrictEqual({ id: messageId, @@ -490,6 +487,8 @@ describe("機能テスト", () => { }) test("正常系_Reactionの投稿", (resolve) => { + reactionId = uuid() + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { expect(res).toStrictEqual({ id: reactionId, @@ -518,6 +517,8 @@ describe("機能テスト", () => { }) test("正常系_Questionの投稿", (resolve) => { + questionId = uuid() + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { expect(res).toStrictEqual({ id: questionId, @@ -546,6 +547,8 @@ describe("機能テスト", () => { }) test("正常系_Answerの投稿", (resolve) => { + answerId = uuid() + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { expect(res).toStrictEqual({ id: answerId, @@ -575,25 +578,36 @@ describe("機能テスト", () => { ) }) - // FIXME: アプリケーションの方が未対応 - test.skip("異常系_進行中でないtopicには投稿できない", (resolve) => { - clientSockets[1].emit( + test("正常系_進行中でないtopicにも投稿できる", (resolve) => { + notOnGoingTopicMessageId = uuid() + + clientSockets[0].on("PUB_CHAT_ITEM", (res) => { + expect(res).toStrictEqual({ + id: notOnGoingTopicMessageId, + topicId: roomData.topics[0].id, + createdAt: MATCHING.DATE, + type: "message", + senderType: "speaker", + iconId: 3, + content: "ongoingでないトピックへの投稿", + timestamp: expect.any(Number), + }) + notOnGoingTopicMessage = res + resolve() + }) + + clientSockets[2].emit( "POST_CHAT_ITEM", { - id: uuid(), + id: notOnGoingTopicMessageId, topicId: roomData.topics[0].id, type: "message", content: "ongoingでないトピックへの投稿", }, (res) => { - expect(res).toStrictEqual({ - result: "error", - error: { - code: MATCHING.CODE, - message: MATCHING.TEXT, - }, + expect(res).toStrictEqual({ + result: "success", }) - resolve() }, ) }) @@ -624,44 +638,6 @@ describe("機能テスト", () => { }) describe("ChatItemをピン留め", () => { - // 進行中でないTopicへのピン留めテストのためにChatItemを一つ投稿しておく - beforeAll((resolve) => { - clientSockets[0].on("PUB_CHAT_ITEM", (res) => { - pinnedMessage = res - - // listenerを解除 - clientSockets[0].off("PUB_CHAT_ITEM") - resolve() - }) - - adminSocket.emit( - "ADMIN_CHANGE_TOPIC_STATE", - { topicId: roomData.topics[0].id, state: "ongoing" }, - // eslint-disable-next-line @typescript-eslint/no-empty-function - () => {}, - ) - - clientSockets[2].emit( - "POST_CHAT_ITEM", - { - id: pinnedMessageId, - topicId: roomData.topics[0].id, - type: "message", - content: "ピン留めされるメッセージ", - quoteId: undefined, - }, - // eslint-disable-next-line @typescript-eslint/no-empty-function - () => {}, - ) - - adminSocket.emit( - "ADMIN_CHANGE_TOPIC_STATE", - { topicId: roomData.topics[2].id, state: "ongoing" }, - // eslint-disable-next-line @typescript-eslint/no-empty-function - () => {}, - ) - }) - afterEach(() => { clientSockets[0].off("PUB_PINNED_MESSAGE") }) @@ -687,13 +663,16 @@ describe("機能テスト", () => { clientSockets[0].on("PUB_PINNED_MESSAGE", (res) => { expect(res).toStrictEqual({ topicId: roomData.topics[0].id, - chatItemId: pinnedMessageId, + chatItemId: notOnGoingTopicMessageId, }) resolve() }) clientSockets[2].emit( "POST_PINNED_MESSAGE", - { topicId: roomData.topics[0].id, chatItemId: pinnedMessageId }, + { + topicId: roomData.topics[0].id, + chatItemId: notOnGoingTopicMessageId, + }, (res) => { expect(res).toStrictEqual({ result: "success" }) }, From a878ac729161da038ac975f17badcb4b93f72b87 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:15:18 +0900 Subject: [PATCH 12/61] fix timestamp --- app/server/src/domain/room/Room.ts | 5 ++++- app/server/src/service/chatItem/ChatItemService.ts | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/server/src/domain/room/Room.ts b/app/server/src/domain/room/Room.ts index fba90dc7..15e07072 100644 --- a/app/server/src/domain/room/Room.ts +++ b/app/server/src/domain/room/Room.ts @@ -93,7 +93,10 @@ class RoomClass { return this._archivedAt } - public calcTimestamp = (topicId: number): number => { + public calcTimestamp = (topicId: number): number | null => { + if(this.topics.find(({ id }) => id === topicId)?.state !== "ongoing") { + return null + } const openedDate = this.findOpenedDateOrThrow(topicId) const offsetTime = this._topicTimeData[topicId].offsetTime const timestamp = new Date().getTime() - openedDate - offsetTime diff --git a/app/server/src/service/chatItem/ChatItemService.ts b/app/server/src/service/chatItem/ChatItemService.ts index 07f17a3f..8c73a0e0 100644 --- a/app/server/src/service/chatItem/ChatItemService.ts +++ b/app/server/src/service/chatItem/ChatItemService.ts @@ -61,7 +61,7 @@ class ChatItemService { content, quote, new Date(), - room.calcTimestamp(topicId), + room.calcTimestamp(topicId) ?? undefined, ) room.postChatItem(userId, message) From c3479c100dfb1f481e43cd33d64e02a041c31ee3 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Fri, 22 Oct 2021 17:35:18 +0900 Subject: [PATCH 13/61] fix --- app/server/src/domain/room/Room.ts | 4 ++-- app/server/src/service/chatItem/ChatItemService.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/server/src/domain/room/Room.ts b/app/server/src/domain/room/Room.ts index 15e07072..38e8bd10 100644 --- a/app/server/src/domain/room/Room.ts +++ b/app/server/src/domain/room/Room.ts @@ -94,7 +94,7 @@ class RoomClass { } public calcTimestamp = (topicId: number): number | null => { - if(this.topics.find(({ id }) => id === topicId)?.state !== "ongoing") { + if (this.topics.find(({ id }) => id === topicId)?.state !== "ongoing") { return null } const openedDate = this.findOpenedDateOrThrow(topicId) @@ -352,7 +352,7 @@ class RoomClass { content, null, new Date(), - this.calcTimestamp(topicId), + this.calcTimestamp(topicId) ?? undefined, ) this._chatItems.push(botMessage) diff --git a/app/server/src/service/chatItem/ChatItemService.ts b/app/server/src/service/chatItem/ChatItemService.ts index 8c73a0e0..c545ebfa 100644 --- a/app/server/src/service/chatItem/ChatItemService.ts +++ b/app/server/src/service/chatItem/ChatItemService.ts @@ -94,7 +94,7 @@ class ChatItemService { senderType, quote, new Date(), - room.calcTimestamp(topicId), + room.calcTimestamp(topicId) ?? undefined, ) room.postChatItem(userId, reaction) @@ -130,7 +130,7 @@ class ChatItemService { content, quote, new Date(), - room.calcTimestamp(topicId), + room.calcTimestamp(topicId) ?? undefined, ) room.postChatItem(userId, question) @@ -162,7 +162,7 @@ class ChatItemService { content, quote, new Date(), - room.calcTimestamp(topicId), + room.calcTimestamp(topicId) ?? undefined, ) room.postChatItem(userId, answer) From 402b0106cdf038510c316c42cb3946c0bc77cffc Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Fri, 22 Oct 2021 17:39:23 +0900 Subject: [PATCH 14/61] fix --- app/server/src/service/stamp/StampService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/server/src/service/stamp/StampService.ts b/app/server/src/service/stamp/StampService.ts index d3bdfb22..980e431b 100644 --- a/app/server/src/service/stamp/StampService.ts +++ b/app/server/src/service/stamp/StampService.ts @@ -24,7 +24,7 @@ class StampService { roomId, this.roomRepository, ) - const timestamp = room.calcTimestamp(topicId) + const timestamp = room.calcTimestamp(topicId) as number // NOTE: スタンプはongoing中しか送信できないため as number const stamp = this.stampFactory.create( id, From cc7b6dbe764269d7ac2b025b5f4154eb4a070863 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Fri, 22 Oct 2021 18:53:18 +0900 Subject: [PATCH 15/61] refactor ArrayRange --- app/server/src/utils/range.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/server/src/utils/range.ts b/app/server/src/utils/range.ts index ccfbf940..0bde4c61 100644 --- a/app/server/src/utils/range.ts +++ b/app/server/src/utils/range.ts @@ -1,7 +1,7 @@ /** - * [0, 1, 2, ..., n]という配列を作る関数 + * [0, 1, 2, ..., n-1]という配列を作る関数 * - * @param count 要素の数 - * @returns 配列 + * @param n 要素の数 + * @returns number[] */ -export const ArrayRange = (count: number) => [...Array(count)].map((_, i) => i) +export const ArrayRange = (n: number) => [...Array(n)].map((_, i) => i) From f2b61bb483b64eb52d8a64c4df56dd640ebb09da Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Fri, 22 Oct 2021 21:30:50 +0900 Subject: [PATCH 16/61] add tests --- app/server/src/__test__/chat.ts | 321 ++++++++++++-------------------- 1 file changed, 122 insertions(+), 199 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 333fb2b6..31690b04 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -20,18 +20,21 @@ import AdminService from "../service/admin/AdminService" import { AdminEnterRoomResponse, ChatItemModel, + EnterRoomResponse, ErrorResponse, PubChangeTopicStateParam, PubChatItemParam, PubPinnedMessageParam, PubStampParam, RoomModel, + RoomState, ServerListenEventsMap, ServerPubEventsMap, StampModel, SuccessResponse, } from "sushi-chat-shared" import delay from "../utils/delay" +import User from "../domain/user/User" describe("機能テスト", () => { const MATCHING = { @@ -41,6 +44,14 @@ describe("機能テスト", () => { TEXT: expect.stringMatching(/.+/), } + const SYSTEM_MESSAGE = { + start: + "【運営Bot】\n 発表が始まりました!\nコメントを投稿して盛り上げましょう 🎉🎉\n", + pause: "【運営Bot】\n発表が中断されました", + finish: + "【運営Bot】\n発表が終了しました!\n(引き続きコメントを投稿いただけます)", + } + // RESTクライアント let client: supertest.SuperTest // Socketサーバー @@ -753,203 +764,115 @@ describe("機能テスト", () => { }) }) - // describe("途中から入室した場合", () => { - // beforeAll(async () => await delay(100)) - // - // test("途中から入室した場合に履歴が見れる", (resolve) => { - // clientSockets[3].emit( - // "ENTER_ROOM", - // { roomId, iconId: "4" }, - // (res: any) => { - // expect(res).toStrictEqual({ - // // NOTE: changeTopicStateで現在開いているトピックを閉じた際のbotメッセージと、次のトピックが開いた際の - // // botメッセージが同時に追加されるが、それらがDBに格納される順序が不安定だったため、順序を考慮しないように - // // している。アプリケーションの挙動としてはそれらは別トピックに投稿されるメッセージのため、問題はないはず。 - // chatItems: expect.arrayContaining([ - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "1", - // type: "message", - // content: - // "【運営Bot】\n 発表が始まりました!\nコメントを投稿して盛り上げましょう 🎉🎉\n", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "1", - // type: "message", - // content: - // "【運営Bot】\n 発表が終了しました!\n(引き続きコメントを投稿いただけます)", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "2", - // type: "message", - // content: - // "【運営Bot】\n 発表が始まりました!\nコメントを投稿して盛り上げましょう 🎉🎉\n", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "2", - // type: "message", - // content: - // "【運営Bot】\n 発表が終了しました!\n(引き続きコメントを投稿いただけます)", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "3", - // type: "message", - // content: - // "【運営Bot】\n 発表が始まりました!\nコメントを投稿して盛り上げましょう 🎉🎉\n", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "3", - // type: "message", - // content: "【運営Bot】\n 発表が中断されました", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "0", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: expect.any(String), - // topicId: "1", - // type: "message", - // content: "【運営Bot】\n 発表が再開されました", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "2", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: messageId, - // topicId: "1", - // type: "message", - // content: "コメント", - // target: null, - // }, - // { - // timestamp: expect.any(Number), - // iconId: "3", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // target: { - // id: messageId, - // topicId: topics[0].id, - // type: "message", - // iconId: "2", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "コメント", - // target: null, - // }, - // id: reactionId, - // topicId: "1", - // type: "reaction", - // }, - // { - // timestamp: expect.any(Number), - // iconId: "2", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: questionId, - // topicId: "1", - // type: "question", - // content: "質問", - // }, - // { - // timestamp: expect.any(Number), - // iconId: "3", - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // id: answerId, - // topicId: "1", - // type: "answer", - // content: "回答", - // target: { - // id: questionId, - // topicId: topics[0].id, - // type: "question", - // iconId: "2", - // timestamp: expect.any(Number), - // createdAt: expect.stringMatching( - // /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/, - // ), - // content: "質問", - // }, - // }, - // ]), - // topics: [ - // { ...topics[0], state: "active" }, - // { ...topics[1], state: "finished" }, - // { ...topics[2], state: "paused" }, - // ...topics.slice(3), - // ], - // activeUserCount: 5, - // }) - // resolve() - // }, - // ) - // }) - // }) - // - // describe("ルームの終了・閉じる", () => { - // test("ルームを終了する", (resolve) => { - // clientSockets[0].on("PUB_FINISH_ROOM", () => { - // resolve() - // }) - // adminSocket.emit("ADMIN_FINISH_ROOM", {}) - // }) - // - // test("ルームを閉じる", (resolve) => { - // clientSockets[0].on("PUB_CLOSE_ROOM", () => { - // resolve() - // }) - // adminSocket.emit("ADMIN_CLOSE_ROOM", {}) - // }) - // }) + describe.skip("途中からルームに入る", () => { + const systemMessageBase: Omit = { + id: MATCHING.UUID, + createdAt: MATCHING.DATE, + type: "message", + senderType: "system", + iconId: User.SYSTEM_USER_ICON_ID.valueOf(), + timestamp: expect.any(Number), + } + + test("正常系_チャットやスタンプの履歴が見れる", (resolve) => { + clientSockets[3].emit( + "ENTER_ROOM", + { + roomId: roomData.id, + iconId: 4, + speakerTopicId: roomData.topics[0].id, + }, + (res) => { + expect(res).toStrictEqual({ + result: "success", + data: { + // トピックの終了と開始が同時に発生する時、system messageの順番を仕様上規定していないので、 + // 順番を考慮しないようにarrayContainingを使っている + chatItems: expect.arrayContaining([ + { + ...systemMessageBase, + topicId: roomData.topics[0].id, + content: SYSTEM_MESSAGE.start, + }, + { + ...systemMessageBase, + topicId: roomData.topics[0].id, + content: SYSTEM_MESSAGE.finish, + }, + { + ...systemMessageBase, + topicId: roomData.topics[1].id, + content: SYSTEM_MESSAGE.start, + }, + { + ...systemMessageBase, + topicId: roomData.topics[1].id, + content: SYSTEM_MESSAGE.finish, + }, + { + ...systemMessageBase, + topicId: roomData.topics[2].id, + content: SYSTEM_MESSAGE.start, + }, + { + ...systemMessageBase, + topicId: roomData.topics[2].id, + content: SYSTEM_MESSAGE.pause, + }, + { + ...systemMessageBase, + topicId: roomData.topics[2].id, + content: SYSTEM_MESSAGE.start, + }, + message, + reaction, + question, + answer, + notOnGoingTopicMessage, + ]), + stamps, + // ルームに参加しているユーザー(管理者ユーザー + 一般ユーザー) + システムユーザー + activeUserCount: 1 + clientSockets.length + 1, + pinnedChatItemIds: [messageId], + topicStates: [ + { + topicId: roomData.topics[0].id, + state: "finished", + }, + { topicId: roomData.topics[1].id, state: "finished" }, + + { topicId: roomData.topics[2].id, state: "ongoing" }, + ], + }, + }) + resolve() + }, + ) + }) + }) + + describe("roomの終了", () => { + test("正常系_roomを終了する", () => { + adminSocket.emit("ADMIN_FINISH_ROOM", {}, async (res) => { + expect(res).toStrictEqual({ + result: "success", + }) + + const roomRes = await client.get(`/room/${roomData.id}`) + expect(roomRes.body.data.state).toBe("finished") + }) + }) + + test("異常系_存在しないroomを終了しようとするとエラーが返る", () => { + adminSocket.emit("ADMIN_FINISH_ROOM", {}, async (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { + code: "400", + message: expect.any(String), + }, + }) + }) + }) + }) }) From 6c5137bb2b1adfc15b7fb9e3913312ce53c8a0e5 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sat, 27 Nov 2021 21:55:21 +0900 Subject: [PATCH 17/61] feat: test for ENTER_ROOM on the way --- app/server/src/__test__/chat.ts | 133 ++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 31690b04..b0ef7adc 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -44,12 +44,21 @@ describe("機能テスト", () => { TEXT: expect.stringMatching(/.+/), } - const SYSTEM_MESSAGE = { + const SYSTEM_MESSAGE_CONTENT = { start: "【運営Bot】\n 発表が始まりました!\nコメントを投稿して盛り上げましょう 🎉🎉\n", pause: "【運営Bot】\n発表が中断されました", finish: - "【運営Bot】\n発表が終了しました!\n(引き続きコメントを投稿いただけます)", + "【運営Bot】\n 発表が終了しました!\n(引き続きコメントを投稿いただけます)", + } + + const SYSTEM_MESSAGE_BASE: Omit = { + id: MATCHING.UUID, + createdAt: MATCHING.DATE, + type: "message", + senderType: "system", + iconId: User.SYSTEM_USER_ICON_ID.valueOf(), + timestamp: expect.any(Number), } // RESTクライアント @@ -74,6 +83,11 @@ describe("機能テスト", () => { let answer: ChatItemModel let notOnGoingTopicMessage: ChatItemModel let stamps: StampModel[] + let history: { + chatItems: ChatItemModel[] + stamps: StampModel[] + pinnedChatItemIds: string[] + } // テストのセットアップ beforeAll(async (done) => { @@ -728,6 +742,7 @@ describe("機能テスト", () => { clientSockets[1].emit( "POST_STAMP", { topicId: roomData.topics[2].id }, + // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) }) @@ -764,17 +779,62 @@ describe("機能テスト", () => { }) }) - describe.skip("途中からルームに入る", () => { - const systemMessageBase: Omit = { - id: MATCHING.UUID, - createdAt: MATCHING.DATE, - type: "message", - senderType: "system", - iconId: User.SYSTEM_USER_ICON_ID.valueOf(), - timestamp: expect.any(Number), - } - - test("正常系_チャットやスタンプの履歴が見れる", (resolve) => { + describe("途中からルームに入る", () => { + beforeAll(() => { + history = { + // トピックの終了と開始が同時に発生する時、system messageの順番を仕様上規定していないので、 + // 順番を考慮しないようにarrayContainingを使っている + // chatItems: expect.arrayContaining([ + chatItems: [ + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[0].id, + content: SYSTEM_MESSAGE_CONTENT.start, + }, + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[0].id, + content: SYSTEM_MESSAGE_CONTENT.finish, + }, + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[1].id, + content: SYSTEM_MESSAGE_CONTENT.start, + }, + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[1].id, + content: SYSTEM_MESSAGE_CONTENT.finish, + }, + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[2].id, + content: SYSTEM_MESSAGE_CONTENT.start, + }, + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[2].id, + content: SYSTEM_MESSAGE_CONTENT.pause, + }, + { + ...SYSTEM_MESSAGE_BASE, + topicId: roomData.topics[2].id, + content: SYSTEM_MESSAGE_CONTENT.start, + }, + message, + reaction, + question, + answer, + notOnGoingTopicMessage, + ], + stamps, + pinnedChatItemIds: [messageId], + } + }) + + // TODO: システムメッセージのsenderTypeがadminになってしまっていて落ちるのでskipしている。 + // アプリケーションコードの修正が必要 + test.skip("正常系_チャットやスタンプの履歴が見れる", (resolve) => { clientSockets[3].emit( "ENTER_ROOM", { @@ -786,54 +846,9 @@ describe("機能テスト", () => { expect(res).toStrictEqual({ result: "success", data: { - // トピックの終了と開始が同時に発生する時、system messageの順番を仕様上規定していないので、 - // 順番を考慮しないようにarrayContainingを使っている - chatItems: expect.arrayContaining([ - { - ...systemMessageBase, - topicId: roomData.topics[0].id, - content: SYSTEM_MESSAGE.start, - }, - { - ...systemMessageBase, - topicId: roomData.topics[0].id, - content: SYSTEM_MESSAGE.finish, - }, - { - ...systemMessageBase, - topicId: roomData.topics[1].id, - content: SYSTEM_MESSAGE.start, - }, - { - ...systemMessageBase, - topicId: roomData.topics[1].id, - content: SYSTEM_MESSAGE.finish, - }, - { - ...systemMessageBase, - topicId: roomData.topics[2].id, - content: SYSTEM_MESSAGE.start, - }, - { - ...systemMessageBase, - topicId: roomData.topics[2].id, - content: SYSTEM_MESSAGE.pause, - }, - { - ...systemMessageBase, - topicId: roomData.topics[2].id, - content: SYSTEM_MESSAGE.start, - }, - message, - reaction, - question, - answer, - notOnGoingTopicMessage, - ]), - stamps, + ...history, // ルームに参加しているユーザー(管理者ユーザー + 一般ユーザー) + システムユーザー activeUserCount: 1 + clientSockets.length + 1, - pinnedChatItemIds: [messageId], topicStates: [ { topicId: roomData.topics[0].id, From a019dd138e601460b0eba35a36a2ad7eb0dcff99 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 00:34:05 +0900 Subject: [PATCH 18/61] fix: insert server generated createdAt to Stamp --- app/server/src/infra/repository/stamp/StampRepository.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/server/src/infra/repository/stamp/StampRepository.ts b/app/server/src/infra/repository/stamp/StampRepository.ts index 6e29a18f..9381c312 100644 --- a/app/server/src/infra/repository/stamp/StampRepository.ts +++ b/app/server/src/infra/repository/stamp/StampRepository.ts @@ -9,7 +9,7 @@ class StampRepository implements IStampRepository { const pgClient = await this.pgPool.client() const query = - "INSERT INTO Stamps (id, room_id, topic_id, user_id, timestamp) VALUES ($1, $2, $3, $4, $5)" + "INSERT INTO Stamps (id, room_id, topic_id, user_id, timestamp, created_at) VALUES ($1, $2, $3, $4, $5, $6)" try { await pgClient.query(query, [ @@ -18,6 +18,7 @@ class StampRepository implements IStampRepository { stamp.topicId, stamp.userId, stamp.timestamp, + stamp.createdAt, ]) } catch (e) { StampRepository.logError(e, "store()") From 37494bba543c86fb5afd0ac66d69444c8da76362 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 00:34:51 +0900 Subject: [PATCH 19/61] feat: add test for get room history --- app/server/src/__test__/chat.ts | 66 ++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index b0ef7adc..65aeaf87 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -47,7 +47,8 @@ describe("機能テスト", () => { const SYSTEM_MESSAGE_CONTENT = { start: "【運営Bot】\n 発表が始まりました!\nコメントを投稿して盛り上げましょう 🎉🎉\n", - pause: "【運営Bot】\n発表が中断されました", + pause: "【運営Bot】\n 発表が中断されました", + restart: "【運営Bot】\n 発表が再開されました", finish: "【運営Bot】\n 発表が終了しました!\n(引き続きコメントを投稿いただけます)", } @@ -56,7 +57,9 @@ describe("機能テスト", () => { id: MATCHING.UUID, createdAt: MATCHING.DATE, type: "message", - senderType: "system", + // TODO: システムメッセージのsenderTypeがadminになってしまっている。 + // アプリケーションコードの修正が必要 + senderType: "admin", iconId: User.SYSTEM_USER_ICON_ID.valueOf(), timestamp: expect.any(Number), } @@ -86,7 +89,7 @@ describe("機能テスト", () => { let history: { chatItems: ChatItemModel[] stamps: StampModel[] - pinnedChatItemIds: string[] + pinnedChatItemIds: (string | null)[] } // テストのセットアップ @@ -793,13 +796,13 @@ describe("機能テスト", () => { }, { ...SYSTEM_MESSAGE_BASE, - topicId: roomData.topics[0].id, - content: SYSTEM_MESSAGE_CONTENT.finish, + topicId: roomData.topics[1].id, + content: SYSTEM_MESSAGE_CONTENT.start, }, { ...SYSTEM_MESSAGE_BASE, - topicId: roomData.topics[1].id, - content: SYSTEM_MESSAGE_CONTENT.start, + topicId: roomData.topics[0].id, + content: SYSTEM_MESSAGE_CONTENT.finish, }, { ...SYSTEM_MESSAGE_BASE, @@ -819,7 +822,7 @@ describe("機能テスト", () => { { ...SYSTEM_MESSAGE_BASE, topicId: roomData.topics[2].id, - content: SYSTEM_MESSAGE_CONTENT.start, + content: SYSTEM_MESSAGE_CONTENT.restart, }, message, reaction, @@ -828,13 +831,11 @@ describe("機能テスト", () => { notOnGoingTopicMessage, ], stamps, - pinnedChatItemIds: [messageId], + pinnedChatItemIds: [notOnGoingTopicMessageId, null, messageId], } }) - // TODO: システムメッセージのsenderTypeがadminになってしまっていて落ちるのでskipしている。 - // アプリケーションコードの修正が必要 - test.skip("正常系_チャットやスタンプの履歴が見れる", (resolve) => { + test("正常系_チャットやスタンプの履歴が見れる", (resolve) => { clientSockets[3].emit( "ENTER_ROOM", { @@ -867,7 +868,7 @@ describe("機能テスト", () => { }) describe("roomの終了", () => { - test("正常系_roomを終了する", () => { + test("正常系_roomを終了する", (done) => { adminSocket.emit("ADMIN_FINISH_ROOM", {}, async (res) => { expect(res).toStrictEqual({ result: "success", @@ -875,10 +876,13 @@ describe("機能テスト", () => { const roomRes = await client.get(`/room/${roomData.id}`) expect(roomRes.body.data.state).toBe("finished") + + done() }) }) - test("異常系_存在しないroomを終了しようとするとエラーが返る", () => { + // TODO: アプリケーション側で500エラーを返すようにしてしまっていたので、そちらを修正したらskipを外す + test.skip("異常系_終了しているroomを終了しようとするとエラーが返る", (done) => { adminSocket.emit("ADMIN_FINISH_ROOM", {}, async (res) => { expect(res).toStrictEqual({ result: "error", @@ -887,6 +891,40 @@ describe("機能テスト", () => { message: expect.any(String), }, }) + + done() + }) + }) + }) + + describe("roomの履歴確認", () => { + test("正常系_終了したルームのチャットアイテムの履歴を見れる", async () => { + const res = await client.get(`/room/${roomData.id}/history`) + + expect(res.statusCode).toBe(200) + expect(res.body).toMatchObject({ + result: "success", + data: { + chatItems: history.chatItems.map((c) => { + if (!c.quote) return c + + const chatItem: Record = { + createdAt: c.createdAt, + id: c.id, + senderType: c.senderType, + timestamp: c.timestamp, + topicId: c.topicId, + } + if (c.type === "message") { + chatItem.content = c.content + } + return chatItem + }), + pinnedChatItemIds: history.pinnedChatItemIds.filter( + (p): p is string => p !== null, + ), + stamps: history.stamps, + }, }) }) }) From 3eb01a0184a3d862a5e400bd719a58131497f266 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 00:48:42 +0900 Subject: [PATCH 20/61] fix: use arrayContaing for unstable order object --- app/server/src/__test__/chat.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 65aeaf87..4c7280f8 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -796,13 +796,13 @@ describe("機能テスト", () => { }, { ...SYSTEM_MESSAGE_BASE, - topicId: roomData.topics[1].id, - content: SYSTEM_MESSAGE_CONTENT.start, + topicId: roomData.topics[0].id, + content: SYSTEM_MESSAGE_CONTENT.finish, }, { ...SYSTEM_MESSAGE_BASE, - topicId: roomData.topics[0].id, - content: SYSTEM_MESSAGE_CONTENT.finish, + topicId: roomData.topics[1].id, + content: SYSTEM_MESSAGE_CONTENT.start, }, { ...SYSTEM_MESSAGE_BASE, @@ -847,7 +847,9 @@ describe("機能テスト", () => { expect(res).toStrictEqual({ result: "success", data: { - ...history, + chatItems: expect.arrayContaining(history.chatItems), + stamps: history.stamps, + pinnedChatItemIds: history.pinnedChatItemIds, // ルームに参加しているユーザー(管理者ユーザー + 一般ユーザー) + システムユーザー activeUserCount: 1 + clientSockets.length + 1, topicStates: [ From 749f209c9e478bea6aa2e82a1c65bfc658d46982 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 01:01:06 +0900 Subject: [PATCH 21/61] feat: add abnormal test --- app/server/src/__test__/chat.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 4c7280f8..aba3c73b 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -929,5 +929,13 @@ describe("機能テスト", () => { }, }) }) + + // TODO: アプリケーション側で適切なステータスコードを返すようになっていないので修正したらskipを外す + test.skip("異常系_存在しないルームの履歴は見れない", async () => { + const notExistRoomId = uuid() + const res = await client.get(`/room/${notExistRoomId}/history`) + + expect(res.statusCode).toBe(404) + }) }) }) From 858f920665daab0a98feee784c81ef34bbd1e08d Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 01:08:58 +0900 Subject: [PATCH 22/61] feat: add test for archive room rest api --- app/server/src/__test__/chat.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index aba3c73b..3bfdc9a3 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -938,4 +938,30 @@ describe("機能テスト", () => { expect(res.statusCode).toBe(404) }) }) + + describe("roomの公開停止", () => { + test("正常系_ルームを公開停止できる", async () => { + const res = await client + .put(`/room/${roomData.id}/archive`) + .set("Authorization", "Bearer token") + expect(res.statusCode).toBe(200) + + const room = await client.get(`/room/${roomData.id}`) + expect(room.body.data.state).toBe("archived") + }) + + // TODO: アプリケーション側で適切なステータスコードを返すようになっていないので修正したらskipを外す + test.skip("異常系_存在しないルームは公開停止できない", async () => { + const notExistRoomId = uuid() + const res = await client + .put(`/room/${notExistRoomId}/archive`) + .set("Authorization", "Bearer token") + expect(res.statusCode).toBe(404) + }) + + test("異常系_管理者ユーザーのトークンを渡さないとルームを公開停止できない", async () => { + const res = await client.put(`/room/${roomData.id}/archive`) + expect(res.statusCode).toBe(401) + }) + }) }) From 8670b480993f98dc7493bbd1617d81d215581ce5 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 01:20:12 +0900 Subject: [PATCH 23/61] fix: skip unstable test --- app/server/src/__test__/chat.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 3bfdc9a3..0372ea61 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -900,7 +900,10 @@ describe("機能テスト", () => { }) describe("roomの履歴確認", () => { - test("正常系_終了したルームのチャットアイテムの履歴を見れる", async () => { + // TODO: 結果が不安定でたまに失敗する(DBへのインサートの順序が実行時依存なのがおそらくの原因)のでskipにしている。 + // このエンドポイントのレスポンスの方を仕様通りに直せばchatItemsのパラメータにexpect.arrayContaining()が使えるので、 + // おそらくはそれで解決できそう。 + test.skip("正常系_終了したルームのチャットアイテムの履歴を見れる", async () => { const res = await client.get(`/room/${roomData.id}/history`) expect(res.statusCode).toBe(200) From f9314e320784e893d7b7750ea6fb31a1981549a7 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 01:29:00 +0900 Subject: [PATCH 24/61] fix: senderType of system message --- app/server/src/__test__/chat.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 06805ef8..7ab644b9 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -57,9 +57,7 @@ describe("機能テスト", () => { id: MATCHING.UUID, createdAt: MATCHING.DATE, type: "message", - // TODO: システムメッセージのsenderTypeがadminになってしまっている。 - // アプリケーションコードの修正が必要 - senderType: "admin", + senderType: "system", iconId: User.SYSTEM_USER_ICON_ID.valueOf(), timestamp: expect.any(Number), } From fee314271073f0273cf23b2539178f3cdf01d0fc Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sun, 28 Nov 2021 01:29:25 +0900 Subject: [PATCH 25/61] fix: pass stamp id from client --- app/server/src/__test__/chat.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 7ab644b9..178ef146 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -83,6 +83,7 @@ describe("機能テスト", () => { let question: ChatItemModel let answer: ChatItemModel let notOnGoingTopicMessage: ChatItemModel + let stampId: string let stamps: StampModel[] let history: { chatItems: ChatItemModel[] @@ -738,6 +739,8 @@ describe("機能テスト", () => { }) test("正常系_スタンプを投稿する", (resolve) => { + stampId = uuid() + clientSockets[0].on("PUB_STAMP", (res) => { expect(res).toStrictEqual([ { @@ -752,7 +755,7 @@ describe("機能テスト", () => { }) clientSockets[1].emit( "POST_STAMP", - { topicId: roomData.topics[2].id }, + { id: stampId, topicId: roomData.topics[2].id }, // eslint-disable-next-line @typescript-eslint/no-empty-function () => {}, ) @@ -760,9 +763,11 @@ describe("機能テスト", () => { // FIXME: アプリケーションの方が未対応 test.skip("異常系_進行中でないトピックにはスタンプを投稿できない", (resolve) => { + const dummyStampId = uuid() + clientSockets[1].emit( "POST_STAMP", - { topicId: roomData.topics[0].id }, + { id: dummyStampId, topicId: roomData.topics[0].id }, (res) => { expect(res).toStrictEqual({ result: "error", @@ -774,11 +779,12 @@ describe("機能テスト", () => { }) test.skip("異常系_存在しないトピックにはスタンプを投稿できない", (resolve) => { + const dummyStampId = uuid() const notExistTopicId = 10 clientSockets[1].emit( "POST_STAMP", - { topicId: notExistTopicId }, + { id: dummyStampId, topicId: notExistTopicId }, (res) => { expect(res).toStrictEqual({ result: "error", From fdb9787508b069d116358f85a866f45e0db68bfa Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Mon, 29 Nov 2021 00:24:56 +0900 Subject: [PATCH 26/61] =?UTF-8?q?[fix]=E6=95=B0=E5=AD=97=E3=81=A8textbox?= =?UTF-8?q?=E3=81=AE=E9=AB=98=E3=81=95=E5=90=88=E3=82=8F=E3=81=9B=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/assets/scss/home/home.scss | 7 +++---- app/front/pages/room/create.vue | 10 ++++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/front/assets/scss/home/home.scss b/app/front/assets/scss/home/home.scss index 89d018c6..40d0b601 100644 --- a/app/front/assets/scss/home/home.scss +++ b/app/front/assets/scss/home/home.scss @@ -125,12 +125,13 @@ display: grid; margin: 1rem 0; grid-template-columns: auto 1fr; + align-items: center; &__index { grid-column: 1; margin: 0 0.5rem 0 0; &--element { - margin: 0.2rem 0 0.9em 0; - padding: 0.5rem 0; + margin: 0 0 0.5rem 0; + padding: 0.92rem 0; } } &__list { @@ -138,7 +139,6 @@ &--element { display: flex; align-items: center; - width: 85%; margin: 0 0 0.5rem 0; &--input { display: flex; @@ -150,7 +150,6 @@ border: none; flex: 1; padding: 0; - @include white-text-box($expand: "true"); &:focus { outline: none; } diff --git a/app/front/pages/room/create.vue b/app/front/pages/room/create.vue index dcddb488..8e9d36dd 100644 --- a/app/front/pages/room/create.vue +++ b/app/front/pages/room/create.vue @@ -61,21 +61,23 @@ /> From 44d02974cbeb796f4ffa2bb08a6a0ccafd53324d Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Mon, 29 Nov 2021 00:46:27 +0900 Subject: [PATCH 27/61] =?UTF-8?q?[fix]=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92?= =?UTF-8?q?=E6=9E=A0=E7=B7=9A=E3=81=AB=E5=85=A5=E3=82=8C=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/assets/scss/home/home.scss | 6 ++++-- app/front/assets/scss/home/variables.scss | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/front/assets/scss/home/home.scss b/app/front/assets/scss/home/home.scss index 40d0b601..72850e65 100644 --- a/app/front/assets/scss/home/home.scss +++ b/app/front/assets/scss/home/home.scss @@ -131,7 +131,7 @@ margin: 0 0.5rem 0 0; &--element { margin: 0 0 0.5rem 0; - padding: 0.92rem 0; + padding: 0.55rem 0; } } &__list { @@ -147,6 +147,7 @@ padding: 0 !important; @include white-text-box($expand: "true"); input { + width: 100%; border: none; flex: 1; padding: 0; @@ -156,7 +157,7 @@ } } &--remove { - margin: 0.2rem; + margin: 0.2rem 0; @include material-icon-button( $size: "sm", $color: #f07b7b, @@ -169,6 +170,7 @@ } } &--sort { + margin: 0; cursor: grab !important; @include material-icon-button; &--dragging { diff --git a/app/front/assets/scss/home/variables.scss b/app/front/assets/scss/home/variables.scss index 922d94a3..57ba6347 100644 --- a/app/front/assets/scss/home/variables.scss +++ b/app/front/assets/scss/home/variables.scss @@ -89,8 +89,8 @@ $color: $text-gray, $border: "none" ) { - width: 36px; - height: 36px; + width: 24px; + height: 24px; display: inline-flex; align-items: center; justify-content: center; From ba0409d4eaedbe3803ae66303b5e4c78c2c0863f Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Mon, 6 Dec 2021 23:11:59 +0900 Subject: [PATCH 28/61] fix: apiClient --- app/front/apiClient/index.ts | 73 +++++++++++++++++++++++++++--------- app/front/pages/home.vue | 14 +++---- app/front/pages/index.vue | 22 ++++------- app/front/pages/invited.vue | 20 +++++----- app/shared/src/types/rest.ts | 11 +++--- 5 files changed, 86 insertions(+), 54 deletions(-) diff --git a/app/front/apiClient/index.ts b/app/front/apiClient/index.ts index 94892b28..f1772690 100644 --- a/app/front/apiClient/index.ts +++ b/app/front/apiClient/index.ts @@ -1,5 +1,5 @@ import { NuxtAxiosInstance } from "@nuxtjs/axios" -import { RestApi, PathsWithMethod } from "sushi-chat-shared" +import { RestApi, PathsWithMethod, EmptyRecord } from "sushi-chat-shared" type PathObject< Method extends "get" | "post" | "put", @@ -9,6 +9,13 @@ type PathObject< params: RestApi["params"] } +type PathOrPathObj< + Method extends "get" | "post" | "put", + Path extends PathsWithMethod, +> = Path extends `${string}:${string}` + ? PathObject + : Path | PathObject + /** * PathObjectからパスを組み立てる関数 * @param path PathObject @@ -32,7 +39,6 @@ const pathBuilder = (path: { * async asyncData({ app }) { * const sampleResponse = await app.$apiClient.get( * { pathname: "/room/:id/history", params: { id: "roomId" } }, - * {}, * ) * if (sampleResponse.result === "success") { * const rooms = sampleResponse.data @@ -65,18 +71,17 @@ export default class Repository { /** * getリクエストを行う * @param path エンドポイントを表すPathObjectまたはパス文字列(パスパラメータ(`:xyz`)を含まない場合は直接文字列を指定可能) - * @param data 送信するデータ + * @param query クエリパラメータ * @returns レスポンス */ public async get>( - path: Path extends `${string}:${string}` - ? PathObject<"get", Path> - : Path | PathObject<"get", Path>, - data: RestApi<"get", Path>["request"], + ...[path, query]: RestApi<"get", Path>["query"] extends EmptyRecord + ? [path: PathOrPathObj<"get", Path>] + : [path: PathOrPathObj<"get", Path>, query: RestApi<"get", Path>["query"]] ) { return await this.nuxtAxios.$get["response"]>( typeof path === "string" ? path : pathBuilder(path), - { data }, + { params: query }, ) } @@ -87,14 +92,31 @@ export default class Repository { * @returns レスポンス */ public async post>( - path: Path extends `${string}:${string}` - ? PathObject<"post", Path> - : Path | PathObject<"post", Path>, - data: RestApi<"post", Path>["request"], + ...[path, body, query]: RestApi<"post", Path>["request"] extends EmptyRecord + ? RestApi<"post", Path>["query"] extends EmptyRecord + ? [path: PathOrPathObj<"post", Path>] + : [ + path: PathOrPathObj<"post", Path>, + body: RestApi<"post", Path>["request"], + query: RestApi<"post", Path>["query"], + ] + : RestApi<"post", Path>["query"] extends EmptyRecord + ? [ + path: PathOrPathObj<"post", Path>, + body: RestApi<"post", Path>["request"], + ] + : [ + path: PathOrPathObj<"post", Path>, + body: RestApi<"post", Path>["request"], + query: RestApi<"post", Path>["query"], + ] ) { return await this.nuxtAxios.$post["response"]>( typeof path === "string" ? path : pathBuilder(path), - data, + body, + { + params: query, + }, ) } @@ -105,14 +127,31 @@ export default class Repository { * @returns レスポンス */ public async put>( - path: Path extends `${string}:${string}` - ? PathObject<"put", Path> - : Path | PathObject<"put", Path>, - data: RestApi<"put", Path>["request"], + ...[path, data, query]: RestApi<"put", Path>["request"] extends EmptyRecord + ? RestApi<"put", Path>["query"] extends EmptyRecord + ? [path: PathOrPathObj<"put", Path>] + : [ + path: PathOrPathObj<"put", Path>, + body: RestApi<"put", Path>["request"], + query: RestApi<"put", Path>["query"], + ] + : RestApi<"put", Path>["query"] extends EmptyRecord + ? [ + path: PathOrPathObj<"put", Path>, + body: RestApi<"put", Path>["request"], + ] + : [ + path: PathOrPathObj<"put", Path>, + body: RestApi<"put", Path>["request"], + query: RestApi<"put", Path>["query"], + ] ) { return await this.nuxtAxios.$put["response"]>( typeof path === "string" ? path : pathBuilder(path), data, + { + params: query, + }, ) } } diff --git a/app/front/pages/home.vue b/app/front/pages/home.vue index 18bf31e8..e5b70e60 100644 --- a/app/front/pages/home.vue +++ b/app/front/pages/home.vue @@ -126,7 +126,8 @@ export default Vue.extend({ layout: "home", middleware: "privateRoute", async asyncData({ app }): Promise { - const response = await app.$apiClient.get("/room", {}) + const response = await app.$apiClient.get("/room") + if (response.result === "success") { const rooms = response.data const ongoingRooms = rooms.filter((room) => room.state === "ongoing") @@ -174,13 +175,10 @@ export default Vue.extend({ } try { console.log(id) - const response = await this.$apiClient.put( - { - pathname: "/room/:id/archive", - params: { id }, - }, - {}, - ) + const response = await this.$apiClient.put({ + pathname: "/room/:id/archive", + params: { id }, + }) if (response.result === "success") { this.finishedRooms = this.finishedRooms.filter( diff --git a/app/front/pages/index.vue b/app/front/pages/index.vue index 5aa5c408..764f05bb 100644 --- a/app/front/pages/index.vue +++ b/app/front/pages/index.vue @@ -142,13 +142,10 @@ export default Vue.extend({ async checkStatusAndAction() { // ルーム情報取得・status更新 const res = await this.$apiClient - .get( - { - pathname: "/room/:id", - params: { id: this.room.id }, - }, - {}, - ) + .get({ + pathname: "/room/:id", + params: { id: this.room.id }, + }) .catch((e) => { throw new Error(e) }) @@ -321,13 +318,10 @@ export default Vue.extend({ startRoom() { // TODO: ルームの状態をindex、またはvuexでもつ this.$apiClient - .put( - { - pathname: "/room/:id/start", - params: { id: this.room.id }, - }, - {}, - ) + .put({ + pathname: "/room/:id/start", + params: { id: this.room.id }, + }) .then(() => { this.adminEnterRoom() this.roomState = "ongoing" diff --git a/app/front/pages/invited.vue b/app/front/pages/invited.vue index 7f9a1a34..45a2a013 100644 --- a/app/front/pages/invited.vue +++ b/app/front/pages/invited.vue @@ -39,13 +39,10 @@ export default Vue.extend({ InviteSuccess, }, async asyncData({ app, query }) { - const res = await app.$apiClient.get( - { - pathname: "/room/:id", - params: { id: query.roomId as string }, - }, - {}, - ) + const res = await app.$apiClient.get({ + pathname: "/room/:id", + params: { id: query.roomId as string }, + }) if (res.result === "error") { throw new Error("ルーム情報なし") } @@ -70,9 +67,14 @@ export default Vue.extend({ methods: { async regiaterAdmin() { const res = await this.$apiClient.post( - // @ts-ignore - `/room/${this.roomId}/invited?admin_invite_key=${this.adminInviteKey}`, + { + pathname: `/room/:id/invited`, + params: { id: this.roomId }, + }, {}, + { + admin_invite_key: this.adminInviteKey, + }, ) if (res.result === "error") { window.alert("処理に失敗しました") diff --git a/app/shared/src/types/rest.ts b/app/shared/src/types/rest.ts index 66b8d3ed..7aef88bb 100644 --- a/app/shared/src/types/rest.ts +++ b/app/shared/src/types/rest.ts @@ -8,7 +8,6 @@ export type RestApiDefinition = { methods: { get: { query: Empty - request: Empty response: "ok" } } @@ -18,7 +17,6 @@ export type RestApiDefinition = { methods: { get: { query: Empty - request: Empty response: SuccessResponse | ErrorResponse } post: { @@ -41,7 +39,6 @@ export type RestApiDefinition = { methods: { get: { query: Empty - request: Empty response: SuccessResponse | ErrorResponse } } @@ -65,7 +62,6 @@ export type RestApiDefinition = { methods: { get: { query: Empty - request: Empty response: | SuccessResponse<{ chatItems: ChatItemModel[] @@ -109,12 +105,16 @@ export type GeneralRestApiTypes = { [path: string]: { params: Record methods: { - [K in "get" | "post" | "put"]?: { + [K in "post" | "put"]?: { query: Record request: unknown response: unknown } } + get?: { + query: Record + response: unknown + } } } @@ -144,7 +144,6 @@ export type RestApi< > = Method extends "get" ? Path extends GetMethodPath ? { - request: RestApiDefinition[Path]["methods"]["get"]["request"] response: RestApiDefinition[Path]["methods"]["get"]["response"] params: RestApiDefinition[Path]["params"] query: RestApiDefinition[Path]["methods"]["get"]["query"] From e2a922aaa196466d82af20ae22e016fbc9bacb9c Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Mon, 6 Dec 2021 23:17:44 +0900 Subject: [PATCH 29/61] fix:server api type --- app/server/src/expressRoute.ts | 2 +- app/server/src/rest.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/server/src/expressRoute.ts b/app/server/src/expressRoute.ts index 14a5403e..2ef47eb1 100644 --- a/app/server/src/expressRoute.ts +++ b/app/server/src/expressRoute.ts @@ -16,7 +16,7 @@ export type Routes = Omit & { req: express.Request< RestApi<"get", Path>["params"], RestApi<"get", Path>["response"], - RestApi<"get", Path>["request"], + unknown, RestApi<"get", Path>["query"] >, res: express.Response["response"]>, diff --git a/app/server/src/rest.ts b/app/server/src/rest.ts index e1bdfb30..834889be 100644 --- a/app/server/src/rest.ts +++ b/app/server/src/rest.ts @@ -120,6 +120,7 @@ export const restSetup = ( adminRouter.get("/room", async (req, res) => { try { const rooms = await adminService.getManagedRooms({ + // @ts-ignore bodyをadminIdの受け渡しに利用しているため adminId: req.body.adminId, }) From 8c1d6526b72f051a7603fb4dd67e5998a7090c55 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Sat, 18 Dec 2021 10:42:26 +0900 Subject: [PATCH 30/61] test: add abnormal tests for application services --- app/server/src/__test__/testAdminService.ts | 9 +- .../src/__test__/testChatItemService.ts | 59 +++++ .../src/__test__/testRealTimeRoomService.ts | 26 +- .../src/__test__/testRestRoomService.ts | 244 +++++++++++++++++- app/server/src/__test__/testStampService.ts | 19 +- app/server/src/__test__/testUserService.ts | 240 ++++++++++++++--- 6 files changed, 551 insertions(+), 46 deletions(-) diff --git a/app/server/src/__test__/testAdminService.ts b/app/server/src/__test__/testAdminService.ts index 37141eaf..3e2a77eb 100644 --- a/app/server/src/__test__/testAdminService.ts +++ b/app/server/src/__test__/testAdminService.ts @@ -59,7 +59,7 @@ describe("AdminServiceのテスト", () => { }) }) - describe("getManagedRoomsのテスト", () => { + describe("fetchManagedRoomsのテスト", () => { test("正常系_管理しているroom一覧を取得する", async () => { // adminが既に登録されている状態にしておく adminRepository.createIfNotExist(admin) @@ -117,5 +117,12 @@ describe("AdminServiceのテスト", () => { expect(r.startAt).toBeNull() }) }) + + test("異常系_存在しないadminIdならエラーを投げる", async () => { + const notExistAdminId = uuid() + await expect(() => + adminService.getManagedRooms({ adminId: notExistAdminId }), + ).rejects.toThrow() + }) }) }) diff --git a/app/server/src/__test__/testChatItemService.ts b/app/server/src/__test__/testChatItemService.ts index a433e68f..0f85d968 100644 --- a/app/server/src/__test__/testChatItemService.ts +++ b/app/server/src/__test__/testChatItemService.ts @@ -163,6 +163,19 @@ describe("ChatItemServiceのテスト", () => { expect(deliveredMessage.content).toBe("テストメッセージ") expect(deliveredMessage.quote?.id).toBe(target.id) }) + + test("異常系_存在しないuserIdならエラーを投げる", async () => { + const notExistUserId = uuid() + await expect( + chatItemService.postMessage({ + chatItemId: uuid(), + userId: notExistUserId, + topicId: 1, + content: "テストメッセージ", + quoteId: null, + }), + ).rejects.toThrow() + }) }) describe("postReactionのテスト", () => { @@ -199,6 +212,19 @@ describe("ChatItemServiceのテスト", () => { expect(deliveredReaction.isPinned).toBeFalsy() expect(deliveredReaction.quote?.id).toBe(target.id) }) + + test("異常系_存在しないuserIdならエラーを投げる", async () => { + const notExistUserId = uuid() + await expect( + chatItemService.postMessage({ + chatItemId: uuid(), + userId: notExistUserId, + topicId: 1, + content: "テストメッセージ", + quoteId: null, + }), + ).rejects.toThrow() + }) }) describe("postQuestionのテスト", () => { @@ -236,6 +262,19 @@ describe("ChatItemServiceのテスト", () => { expect(deliveredQuestion.isPinned).toBeFalsy() expect(deliveredQuestion.content).toBe("テストクエスチョン") }) + + test("異常系_存在しないuserIdならエラーを投げる", async () => { + const notExistUserId = uuid() + await expect( + chatItemService.postMessage({ + chatItemId: uuid(), + userId: notExistUserId, + topicId: 1, + content: "テストメッセージ", + quoteId: null, + }), + ).rejects.toThrow() + }) }) describe("postAnswerのテスト", () => { @@ -275,6 +314,19 @@ describe("ChatItemServiceのテスト", () => { expect(deliveredAnswer.content).toBe("テストアンサー") expect((deliveredAnswer.quote as Question).id).toBe(target.id) }) + + test("異常系_存在しないuserIdならエラーを投げる", async () => { + const notExistUserId = uuid() + await expect( + chatItemService.postMessage({ + chatItemId: uuid(), + userId: notExistUserId, + topicId: 1, + content: "テストメッセージ", + quoteId: null, + }), + ).rejects.toThrow() + }) }) describe("pinChatItemのテスト", () => { @@ -284,5 +336,12 @@ describe("ChatItemServiceのテスト", () => { const pinned = await chatItemRepository.find(target.id) expect(pinned?.isPinned).toBeTruthy() }) + + test("異常系_存在しないchatItemIdならエラーを投げる", async () => { + const notExistChatItemId = uuid() + await expect( + chatItemService.pinChatItem({ chatItemId: notExistChatItemId }), + ).rejects.toThrow() + }) }) }) diff --git a/app/server/src/__test__/testRealTimeRoomService.ts b/app/server/src/__test__/testRealTimeRoomService.ts index 0b26039b..c7018d4f 100644 --- a/app/server/src/__test__/testRealTimeRoomService.ts +++ b/app/server/src/__test__/testRealTimeRoomService.ts @@ -226,6 +226,28 @@ describe("RealtimeRoomServiceのテスト", () => { deliveredChatItemContentsCount + 1, ) }) + + test("異常系_存在しないuserはトピックの状態を変更できない", async () => { + const notExistUserId = uuid() + + await expect(() => + roomService.finish({ userId: notExistUserId }), + ).rejects.toThrow() + }) + + test("異常系_adminでないuserはトピックの状態を変更できない", async () => { + const notAdminUser = new User( + uuid(), + false, + false, + adminUser.roomId, + NewIconId(1), + ) + + await expect(() => + roomService.finish({ userId: notAdminUser.id }), + ).rejects.toThrow() + }) }) describe("finishのテスト", () => { @@ -246,7 +268,7 @@ describe("RealtimeRoomServiceのテスト", () => { await expect(() => roomService.finish({ userId: notExistUserId }), - ).rejects.toThrowError() + ).rejects.toThrow() }) test("異常系_adminでないuserはroomをfinishできない", async () => { @@ -260,7 +282,7 @@ describe("RealtimeRoomServiceのテスト", () => { await expect(() => roomService.finish({ userId: notAdminUser.id }), - ).rejects.toThrowError() + ).rejects.toThrow() }) }) }) diff --git a/app/server/src/__test__/testRestRoomService.ts b/app/server/src/__test__/testRestRoomService.ts index fd95dbea..d642bdb3 100644 --- a/app/server/src/__test__/testRestRoomService.ts +++ b/app/server/src/__test__/testRestRoomService.ts @@ -79,7 +79,6 @@ describe("RestRoomServiceのテスト", () => { describe("startのテスト", () => { test("正常系_roomがstartする", async () => { - // startされるroomを作成しておく const room = new RoomClass( roomId, title, @@ -88,6 +87,8 @@ describe("RestRoomServiceのテスト", () => { topics, adminIds, ) + + // startされるroomを作成しておく roomRepository.build(room) expect(room.state).toBe("not-started") @@ -103,6 +104,89 @@ describe("RestRoomServiceのテスト", () => { expect(startedRoom.state).toBe("ongoing") expect(startedRoom.startAt).not.toBeNull() }) + + test("異常系_存在しないroomはstartできない", async () => { + const notExistRoomId = uuid() + await expect(() => + roomService.start({ id: notExistRoomId, adminId: admin.id }), + ).rejects.toThrow() + }) + + test("異常系_admin以外はstartできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + ) + + // startされるroomを作成しておく + roomRepository.build(room) + + const notExistAdminId = uuid() + await expect(() => + roomService.start({ id: roomId, adminId: notExistAdminId }), + ).rejects.toThrow() + }) + + test("異常系_すでにstartしたroomはstartできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "ongoing", + ) + + // startされるroomを作成しておく + roomRepository.build(room) + + await expect( + roomService.start({ id: roomId, adminId: admin.id }), + ).rejects.toThrow() + }) + + test("異常系_終了したroomはstartできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "finished", + ) + + // startされるroomを作成しておく + roomRepository.build(room) + + await expect( + roomService.start({ id: roomId, adminId: admin.id }), + ).rejects.toThrow() + }) + + test("異常系_archiveしたroomはstartできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "archived", + ) + + // startされるroomを作成しておく + roomRepository.build(room) + + await expect( + roomService.start({ id: roomId, adminId: admin.id }), + ).rejects.toThrow() + }) }) describe("inviteAdminのテスト", () => { @@ -135,6 +219,32 @@ describe("RestRoomServiceのテスト", () => { expect(invitedRoom.adminIds.size).toBe(2) expect(invitedRoom.adminIds.has(anotherAdminId)).toBeTruthy() }) + + test("異常系_存在しないroomにはinviteできない", async () => { + const notExistRoomId = uuid() + const anotherAdminId = uuid() + + await expect(() => + roomService.inviteAdmin({ + id: notExistRoomId, + adminId: anotherAdminId, + adminInviteKey: inviteKey, + }), + ).rejects.toThrow() + }) + + test("異常系_不正なinviteKeyではinviteできない", async () => { + const anotherAdminId = uuid() + const invalidInviteKey = uuid() + + await expect(() => + roomService.inviteAdmin({ + id: roomId, + adminId: anotherAdminId, + adminInviteKey: invalidInviteKey, + }), + ).rejects.toThrow() + }) }) describe("archiveのテスト", () => { @@ -161,6 +271,94 @@ describe("RestRoomServiceのテスト", () => { expect(room.state).toBe("archived") expect(room.archivedAt).not.toBeNull() }) + + test("異常系_存在しないRoomはarchiveできない", async () => { + const notExistRoomId = uuid() + await expect(() => + roomService.archive({ id: notExistRoomId, adminId: admin.id }), + ).rejects.toThrow() + }) + + test("異常系_admin以外はarchiveできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "finished", + startAt, + [], + finishAt, + ) + roomRepository.build(room) + + const notExistAdminId = uuid() + await expect(() => + roomService.archive({ id: roomId, adminId: notExistAdminId }), + ).rejects.toThrow() + }) + + test("異常系_startしていないroomはarchiveできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "not-started", + startAt, + [], + finishAt, + ) + roomRepository.build(room) + + await expect(() => + roomService.archive({ id: roomId, adminId: admin.id }), + ).rejects.toThrow() + }) + + test("異常系_進行中のroomはarchiveできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "ongoing", + startAt, + [], + finishAt, + ) + roomRepository.build(room) + + await expect(() => + roomService.archive({ id: roomId, adminId: admin.id }), + ).rejects.toThrow() + }) + + test("異常系_すでにarchiveされたroomはarchiveできない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + "archived", + startAt, + [], + finishAt, + ) + roomRepository.build(room) + + await expect(() => + roomService.archive({ id: roomId, adminId: admin.id }), + ).rejects.toThrow() + }) }) describe("checkAdminAndFindのテスト", () => { @@ -224,6 +422,45 @@ describe("RestRoomServiceのテスト", () => { startDate: undefined, }) }) + + test("異常系_不正なuserIdが渡されても秘匿情報を返さない", async () => { + const room = new RoomClass( + roomId, + title, + inviteKey, + description, + topics, + adminIds, + ) + roomRepository.build(room) + + const notExistAdminId = uuid() + const res = await roomService.checkAdminAndfind({ + id: roomId, + adminId: notExistAdminId, + }) + + expect(res).toStrictEqual({ + id: roomId, + title, + description, + topics: topics.map((topic, i) => ({ + id: i + 1, + order: i + 1, + title: topic.title, + })), + state: "not-started", + adminInviteKey: undefined, + startDate: undefined, + }) + }) + + test("異常系_存在しないroomの情報は取得できない", async () => { + const notExistRoomId = uuid() + await expect(() => + roomService.checkAdminAndfind({ id: notExistRoomId }), + ).rejects.toThrow() + }) }) describe("findのテスト", () => { @@ -244,5 +481,10 @@ describe("RestRoomServiceのテスト", () => { expect(res.stamps).toStrictEqual([]) expect(res.pinnedChatItemIds).toStrictEqual([1, 2].map(() => null)) }) + + test("異常系_存在しないroomの履歴は取得できない", async () => { + const notExistRoomId = uuid() + await expect(() => roomService.find(notExistRoomId)).rejects.toThrow() + }) }) }) diff --git a/app/server/src/__test__/testStampService.ts b/app/server/src/__test__/testStampService.ts index 327f3778..758c1831 100644 --- a/app/server/src/__test__/testStampService.ts +++ b/app/server/src/__test__/testStampService.ts @@ -13,12 +13,14 @@ import { v4 as uuid } from "uuid" import User from "../domain/user/User" import { NewIconId } from "../domain/user/IconId" import RoomClass from "../domain/room/Room" +import IUserRepository from "../domain/user/IUserRepository" describe("StampServiceのテスト", () => { let userId: string let roomId: string let stampRepository: IStampRepository + let userRepository: IUserRepository let stampDeliverySubscriber: Stamp[] let stampService: StampService @@ -28,7 +30,7 @@ describe("StampServiceのテスト", () => { const adminRepository = new EphemeralAdminRepository() const roomRepository = new EphemeralRoomRepository(adminRepository) - const userRepository = new EphemeralUserRepository() + userRepository = new EphemeralUserRepository() stampRepository = new EphemeralStampRepository() stampDeliverySubscriber = [] @@ -75,8 +77,7 @@ describe("StampServiceのテスト", () => { describe("postのテスト", () => { test("正常系_stampを投稿する", async () => { - const stampId = uuid() - await stampService.post({ id: stampId, userId, topicId: 1 }) + await stampService.post({ id: uuid(), userId, topicId: 1 }) // stampIntervalDeliveryのインターバル処理でスタンプで配信されるのを待つ await delay(200) @@ -88,11 +89,17 @@ describe("StampServiceのテスト", () => { expect(deliveredStamp.userId).toBe(userId) }) + test("異常系_存在しないuserはstampを投稿できない", async () => { + const notExistUserId = uuid() + await expect(() => + stampService.post({ id: uuid(), userId: notExistUserId, topicId: 1 }), + ).rejects.toThrow() + }) + test("異常系_OPEN状態でないTopicへはスタンプを投稿できない", async () => { - const stampId = uuid() await expect(() => - stampService.post({ id: stampId, userId, topicId: 2 }), - ).rejects.toThrowError() + stampService.post({ id: uuid(), userId, topicId: 2 }), + ).rejects.toThrow() }) }) }) diff --git a/app/server/src/__test__/testUserService.ts b/app/server/src/__test__/testUserService.ts index aeac742c..2083e907 100644 --- a/app/server/src/__test__/testUserService.ts +++ b/app/server/src/__test__/testUserService.ts @@ -17,8 +17,10 @@ import Admin from "../domain/admin/admin" describe("UserServiceのテスト", () => { let adminId: string let roomId: string - let userId: string - let iconId: IconId + let adminUserId: string + let adminUserIconId: IconId + let normalUserId: string + let normalUserIconId: IconId let userRepository: IUserRepository let roomRepository: IRoomRepository @@ -46,8 +48,12 @@ describe("UserServiceのテスト", () => { adminRepository.createIfNotExist(new Admin(adminId, adminName, [])) roomId = uuid() - userId = uuid() - iconId = NewIconId(Math.floor(Math.random() * 9) + 1) + + adminUserId = uuid() + adminUserIconId = NewIconId(Math.floor(Math.random() * 10) + 1) + + normalUserId = uuid() + normalUserIconId = NewIconId(Math.floor(Math.random() * 10) + 1) }) describe("adminEnterRoomのテスト", () => { @@ -63,18 +69,96 @@ describe("UserServiceのテスト", () => { ) roomRepository.build(room) - await userService.adminEnterRoom({ roomId, userId, idToken: "" }) + await userService.adminEnterRoom({ + roomId, + userId: adminUserId, + idToken: "", + }) - const user = await userRepository.find(userId) - if (!user) { - throw new Error(`User(${userId}) was not found.`) + const adminUser = await userRepository.find(adminUserId) + if (!adminUser) { + throw new Error(`User(${adminUserId}) was not found.`) } - expect(user.id).toBe(userId) - expect(user.roomId).toBe(roomId) - expect(user.speakAt).toBeUndefined() - expect(user.iconId).toBe(User.ADMIN_ICON_ID) - expect(user.isAdmin).toBeTruthy() + expect(adminUser.id).toBe(adminUserId) + expect(adminUser.roomId).toBe(roomId) + expect(adminUser.speakAt).toBeUndefined() + expect(adminUser.iconId).toBe(User.ADMIN_ICON_ID) + expect(adminUser.isAdmin).toBeTruthy() + }) + + test("異常系_存在しないroomに参加しようとするとエラーになる", async () => { + const notExistRoomId = uuid() + await expect(() => + userService.adminEnterRoom({ + roomId: notExistRoomId, + userId: adminUserId, + idToken: "", + }), + ).rejects.toThrow() + }) + + test("異常系_開始していないroomに参加しようとするとエラーになる", async () => { + const room = new RoomClass( + roomId, + "テストルーム", + uuid(), + "テスト用のルームです。", + [1, 2].map((i) => ({ title: `テストトピック${i}` })), + new Set([adminId]), + "not-started", + ) + roomRepository.build(room) + + await expect(() => + userService.adminEnterRoom({ + roomId, + userId: adminUserId, + idToken: "", + }), + ).rejects.toThrow() + }) + + test("異常系_終了したroomに参加しようとするとエラーになる", async () => { + const room = new RoomClass( + roomId, + "テストルーム", + uuid(), + "テスト用のルームです。", + [1, 2].map((i) => ({ title: `テストトピック${i}` })), + new Set([adminId]), + "finished", + ) + roomRepository.build(room) + + await expect(() => + userService.adminEnterRoom({ + roomId, + userId: adminUserId, + idToken: "", + }), + ).rejects.toThrow() + }) + + test("異常系_アーカイブされたroomに参加しようとするとエラーになる", async () => { + const room = new RoomClass( + roomId, + "テストルーム", + uuid(), + "テスト用のルームです。", + [1, 2].map((i) => ({ title: `テストトピック${i}` })), + new Set([adminId]), + "archived", + ) + roomRepository.build(room) + + await expect(() => + userService.adminEnterRoom({ + roomId, + userId: adminUserId, + idToken: "", + }), + ).rejects.toThrow() }) }) @@ -91,19 +175,96 @@ describe("UserServiceのテスト", () => { ) roomRepository.build(room) - const iconId = 1 - await userService.enterRoom({ userId, roomId, iconId }) + await userService.enterRoom({ + userId: normalUserId, + roomId, + iconId: normalUserIconId.valueOf(), + }) - const user = await userRepository.find(userId) - if (!user) { - throw new Error(`User(${userId}) was not found.`) + const normalUser = await userRepository.find(normalUserId) + if (!normalUser) { + throw new Error(`User(${normalUserId}) was not found.`) } - expect(user.id).toBe(userId) - expect(user.roomId).toBe(roomId) - expect(user.speakAt).toBeUndefined() - expect(user.iconId).toBe(iconId) - expect(user.isAdmin).toBeFalsy() + expect(normalUser.id).toBe(normalUserId) + expect(normalUser.roomId).toBe(roomId) + expect(normalUser.speakAt).toBeUndefined() + expect(normalUser.iconId).toBe(normalUserIconId) + expect(normalUser.isAdmin).toBeFalsy() + }) + + test("異常系_存在しないroomに参加しようとするとエラーになる", async () => { + const notExistRoomId = uuid() + await expect(() => + userService.adminEnterRoom({ + roomId: notExistRoomId, + userId: normalUserId, + idToken: "", + }), + ).rejects.toThrow() + }) + + test("異常系_開始していないroomに参加しようとするとエラーになる", async () => { + const room = new RoomClass( + roomId, + "テストルーム", + uuid(), + "テスト用のルームです。", + [1, 2].map((i) => ({ title: `テストトピック${i}` })), + new Set([adminId]), + "not-started", + ) + roomRepository.build(room) + + await expect(() => + userService.adminEnterRoom({ + roomId, + userId: normalUserId, + idToken: "", + }), + ).rejects.toThrow() + }) + + test("異常系_終了したroomに参加しようとするとエラーになる", async () => { + const room = new RoomClass( + roomId, + "テストルーム", + uuid(), + "テスト用のルームです。", + [1, 2].map((i) => ({ title: `テストトピック${i}` })), + new Set([adminId]), + "finished", + ) + roomRepository.build(room) + + await expect(() => + userService.adminEnterRoom({ + roomId, + userId: normalUserId, + idToken: "", + }), + ).rejects.toThrow() + }) + + test("異常系_アーカイブされたroomに参加しようとするとエラーになる", async () => { + const room = new RoomClass( + roomId, + "テストルーム", + uuid(), + "テスト用のルームです。", + [1, 2].map((i) => ({ title: `テストトピック${i}` })), + new Set([adminId]), + "archived", + ) + roomRepository.build(room) + + await expect(() => + userService.adminEnterRoom({ + roomId, + userId: normalUserId, + idToken: "", + }), + ).rejects.toThrow() }) }) @@ -121,31 +282,38 @@ describe("UserServiceのテスト", () => { [], null, null, - new Set([userId]), + new Set([adminUserId, normalUserId]), ) roomRepository.build(room) + + userRepository.create( + new User(adminUserId, true, false, roomId, adminUserIconId), + ) + userRepository.create( + new User(normalUserId, false, false, roomId, normalUserIconId), + ) }) test("正常系_管理者ユーザーがroomから退出する", async () => { - userRepository.create(new User(userId, true, false, roomId, iconId)) - - await userService.leaveRoom({ userId }) + await userService.leaveRoom({ userId: adminUserId }) - const user = await userRepository.find(userId) + const adminUser = await userRepository.find(adminUserId) - // roomから退出したuserは取得されない - expect(user).toBeNull() + // roomから退出したuserはfindで取得されない + expect(adminUser).toBeNull() }) test("正常系_一般ユーザーがroomから退出する", async () => { - userRepository.create(new User(userId, false, false, roomId, iconId)) - - await userService.leaveRoom({ userId }) + await userService.leaveRoom({ userId: normalUserId }) - const user = await userRepository.find(userId) + const normalUser = await userRepository.find(normalUserId) - // roomから退出したuserは取得されない - expect(user).toBeNull() + // roomから退出したuserはfindで取得されない + expect(normalUser).toBeNull() }) }) + + test("異常系_ roomに参加していないユーザーの場合、参加する前に通信が切断されてしまったと判断して何もしない", async () => { + await expect(() => userService.leaveRoom({ userId: uuid() })).resolves + }) }) From d516ce70528ce5220364d7551fed8fb66d0cc4a5 Mon Sep 17 00:00:00 2001 From: TOMOFUMI-KONDO Date: Tue, 28 Dec 2021 23:27:09 +0900 Subject: [PATCH 31/61] "feat: test chatitem to not-started topic" --- app/server/src/__test__/chat.ts | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 178ef146..27dec394 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -160,7 +160,7 @@ describe("機能テスト", () => { describe("room作成", () => { const title = "テストルーム" - const topics = [1, 2, 3].map((i) => ({ title: `テストトピック-${i}` })) + const topics = [1, 2, 3, 4].map((i) => ({ title: `テストトピック-${i}` })) const description = "これはテスト用のルームです。" test("正常系_管理者がroomを作成", async () => { @@ -672,6 +672,28 @@ describe("機能テスト", () => { }, ) }) + + test("異常系_未開始のtopicには投稿できない", (resolve) => { + clientSockets[1].emit( + "POST_CHAT_ITEM", + { + id: uuid(), + topicId: roomData.topics[3].id, + type: "message", + content: "未開始のtopicへの投稿", + }, + (res) => { + expect(res).toStrictEqual({ + result: "error", + error: { + code: MATCHING.CODE, + message: MATCHING.TEXT, + }, + }) + resolve() + }, + ) + }) }) describe("ChatItemをピン留め", () => { @@ -845,7 +867,7 @@ describe("機能テスト", () => { notOnGoingTopicMessage, ], stamps, - pinnedChatItemIds: [notOnGoingTopicMessageId, null, messageId], + pinnedChatItemIds: [notOnGoingTopicMessageId, null, messageId, null], } }) @@ -874,6 +896,7 @@ describe("機能テスト", () => { { topicId: roomData.topics[1].id, state: "finished" }, { topicId: roomData.topics[2].id, state: "ongoing" }, + { topicId: roomData.topics[3].id, state: "not-started" }, ], }, }) From 00f0eb3c040dd06076698f005bafcdb6b13e20b1 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Tue, 28 Dec 2021 23:57:10 +0900 Subject: [PATCH 32/61] =?UTF-8?q?fix:id=20token=E3=81=AE=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/apiClient/index.ts | 7 ++++- app/front/pages/index.vue | 42 +++++++++++++++++------------- app/front/plugins/apiClient.ts | 12 +-------- app/front/plugins/socket.ts | 19 ++++++++------ app/front/store/auth.ts | 21 +++------------ app/front/store/chatItems.ts | 22 ++++++++++------ app/front/store/pinnedChatItems.ts | 4 +-- app/front/store/stamps.ts | 4 +-- app/front/utils/getIdToken.ts | 8 ++++++ app/front/utils/socketIO.ts | 33 ++++++++++++++--------- 10 files changed, 93 insertions(+), 79 deletions(-) create mode 100644 app/front/utils/getIdToken.ts diff --git a/app/front/apiClient/index.ts b/app/front/apiClient/index.ts index 94892b28..330781c3 100644 --- a/app/front/apiClient/index.ts +++ b/app/front/apiClient/index.ts @@ -1,5 +1,6 @@ import { NuxtAxiosInstance } from "@nuxtjs/axios" import { RestApi, PathsWithMethod } from "sushi-chat-shared" +import getIdToken from "~/utils/getIdToken" type PathObject< Method extends "get" | "post" | "put", @@ -54,7 +55,8 @@ export default class Repository { * idTokenを設定する * @param idToken idToken */ - public setToken(idToken: string | null) { + public async setToken() { + const idToken = await getIdToken() if (idToken != null) { this.nuxtAxios.setToken(`Bearer ${idToken}`) } else { @@ -74,6 +76,7 @@ export default class Repository { : Path | PathObject<"get", Path>, data: RestApi<"get", Path>["request"], ) { + await this.setToken() return await this.nuxtAxios.$get["response"]>( typeof path === "string" ? path : pathBuilder(path), { data }, @@ -92,6 +95,7 @@ export default class Repository { : Path | PathObject<"post", Path>, data: RestApi<"post", Path>["request"], ) { + await this.setToken() return await this.nuxtAxios.$post["response"]>( typeof path === "string" ? path : pathBuilder(path), data, @@ -110,6 +114,7 @@ export default class Repository { : Path | PathObject<"put", Path>, data: RestApi<"put", Path>["request"], ) { + await this.setToken() return await this.nuxtAxios.$put["response"]>( typeof path === "string" ? path : pathBuilder(path), data, diff --git a/app/front/pages/index.vue b/app/front/pages/index.vue index b967fcbe..265f28c2 100644 --- a/app/front/pages/index.vue +++ b/app/front/pages/index.vue @@ -138,8 +138,9 @@ export default Vue.extend({ this.checkStatusAndAction() DeviceStore.determineOs() }, - beforeDestroy() { - this.$socket()?.disconnect() + async beforeDestroy() { + const socket = await this.$socket() + socket.disconnect() }, methods: { async checkStatusAndAction() { @@ -178,32 +179,33 @@ export default Vue.extend({ // NOTE: もしかして:archivedも返ってくる? }, // socket.ioのセットアップ。配信を受け取る - socketSetUp() { + async socketSetUp() { // socket接続 this.$initSocket(UserItemStore.userItems.isAdmin) + const socket = await this.$socket() // SocketIOのコールバックの登録 - this.$socket().on("PUB_CHAT_ITEM", (chatItem) => { + socket.on("PUB_CHAT_ITEM", (chatItem) => { console.log(chatItem) // 自分が送信したChatItemであればupdate、他のユーザーが送信したchatItemであればaddを行う ChatItemStore.addOrUpdate({ ...chatItem, status: "success" }) }) - this.$socket().on("PUB_CHANGE_TOPIC_STATE", (res) => { + socket.on("PUB_CHANGE_TOPIC_STATE", (res) => { // クリックしたTopicのStateを変える TopicStateItemStore.change({ key: res.topicId, state: res.state }) }) // スタンプ通知時の、SocketIOのコールバックの登録 - this.$socket().on("PUB_STAMP", (stamps: StampModel[]) => { + socket.on("PUB_STAMP", (stamps: StampModel[]) => { stamps.forEach((stamp) => { StampStore.addOrUpdate(stamp) }) }) // アクティブユーザー数のSocketIOのコールバックの登録 - this.$socket().on("PUB_USER_COUNT", (res: PubUserCountParam) => { + socket.on("PUB_USER_COUNT", (res: PubUserCountParam) => { this.activeUserCount = res.activeUserCount }) // ピン留めアイテムのSocketIOのコールバックの登録 - this.$socket().on("PUB_PINNED_MESSAGE", (res: PubPinnedMessageParam) => { + socket.on("PUB_PINNED_MESSAGE", (res: PubPinnedMessageParam) => { if ( PinnedChatItemsStore.pinnedChatItems.find( (id) => id === res.chatItemId, @@ -224,9 +226,10 @@ export default Vue.extend({ this.hamburgerMenu = "menu" } }, - changeTopicState(topicId: number, state: TopicState) { + async changeTopicState(topicId: number, state: TopicState) { TopicStateItemStore.change({ key: topicId, state }) - this.$socket().emit( + const socket = await this.$socket() + socket.emit( "ADMIN_CHANGE_TOPIC_STATE", { state, @@ -243,9 +246,10 @@ export default Vue.extend({ this.enterRoom(UserItemStore.userItems.myIconId) }, // ルーム入室 - enterRoom(iconId: number) { - this.socketSetUp() - this.$socket().emit( + async enterRoom(iconId: number) { + await this.socketSetUp() + const socket = await this.$socket() + socket.emit( "ENTER_ROOM", { iconId, @@ -279,9 +283,10 @@ export default Vue.extend({ this.isRoomEnter = true }, // 管理者ルーム入室 - adminEnterRoom() { - this.socketSetUp() - this.$socket().emit( + async adminEnterRoom() { + await this.socketSetUp() + const socket = await this.$socket() + socket.emit( "ADMIN_ENTER_ROOM", { roomId: this.room.id, @@ -310,8 +315,9 @@ export default Vue.extend({ UserItemStore.changeMyIcon(0) }, // ルーム終了 - finishRoom() { - this.$socket().emit("ADMIN_FINISH_ROOM", {}, (res) => { + async finishRoom() { + const socket = await this.$socket() + socket.emit("ADMIN_FINISH_ROOM", {}, (res) => { console.log(res) }) this.roomState = "finished" diff --git a/app/front/plugins/apiClient.ts b/app/front/plugins/apiClient.ts index 0be8facf..8508289a 100644 --- a/app/front/plugins/apiClient.ts +++ b/app/front/plugins/apiClient.ts @@ -5,18 +5,8 @@ const repositoryPlugin: Plugin = (context, inject) => { const axios = context.$axios.create({ timeout: 10 * 1000, }) - const apiClient = new ApiClient(axios) - // NOTE: idTokenの変更を監視して、変更があればAPIClientに反映する - context.store.watch( - (state) => state.auth._idToken, - (idToken: string | null) => { - apiClient.setToken(idToken) - }, - { - immediate: true, - }, - ) + const apiClient = new ApiClient(axios) inject("apiClient", apiClient) } diff --git a/app/front/plugins/socket.ts b/app/front/plugins/socket.ts index 2c178b39..43167aba 100644 --- a/app/front/plugins/socket.ts +++ b/app/front/plugins/socket.ts @@ -1,13 +1,16 @@ import { Plugin } from "@nuxt/types" import buildSocket, { SocketIOType } from "~/utils/socketIO" -const socketPlugin: Plugin = (context, inject) => { - let socket: SocketIOType | null = null + +const socketPlugin: Plugin = (_, inject) => { // socketを初期化する - inject("initSocket", (asAdmin: boolean) => { - socket = buildSocket(asAdmin ? context.store.state.auth._idToken : null) + const socket = new Promise((resolve) => { + inject("initSocket", async (asAdmin: boolean) => { + const socket: SocketIOType = await buildSocket(asAdmin) + resolve(socket) + }) }) // socketを取得する - inject("socket", () => socket) + inject("socket", async () => await socket) } export default socketPlugin @@ -15,18 +18,18 @@ export default socketPlugin declare module "vue/types/vue" { interface Vue { readonly $initSocket: (asAdmin?: boolean) => void - readonly $socket: () => SocketIOType + readonly $socket: () => Promise } } declare module "@nuxt/types" { interface NuxtAppOptions { readonly $initSocket: (idToken?: boolean) => void - readonly $socket: () => SocketIOType + readonly $socket: () => Promise } interface Context { readonly $initSocket: (asAdmin?: boolean) => void - readonly $socket: () => SocketIOType + readonly $socket: () => Promise } } diff --git a/app/front/store/auth.ts b/app/front/store/auth.ts index 6f332494..86593932 100644 --- a/app/front/store/auth.ts +++ b/app/front/store/auth.ts @@ -16,18 +16,13 @@ type AuthUser = { }) export default class Auth extends VuexModule { private _authUser: AuthUser | null = null - private _idToken: string | null = null public get authUser() { return this._authUser } - public get idToken() { - return this._idToken - } - public get isLoggedIn() { - return this._authUser != null && this._idToken != null + return this._authUser != null } @Mutation @@ -35,21 +30,13 @@ export default class Auth extends VuexModule { this._authUser = authUser } - @Mutation - private setIdToken(idToken: string | null) { - this._idToken = idToken - } - @Action({ rawError: true }) - public async onIdTokenChangedAction(res: { authUser?: firebase.User }) { - if (res.authUser == null) { + public onIdTokenChangedAction({ authUser }: { authUser?: firebase.User }) { + if (authUser == null) { this.setAuthUser(null) - this.setIdToken(null) return } - const { uid, email, displayName, photoURL, emailVerified } = res.authUser + const { uid, email, displayName, photoURL, emailVerified } = authUser this.setAuthUser({ uid, email, displayName, photoURL, emailVerified }) - const idToken = await res.authUser.getIdToken() - this.setIdToken(idToken) } } diff --git a/app/front/store/chatItems.ts b/app/front/store/chatItems.ts index facc29d2..cf269d18 100644 --- a/app/front/store/chatItems.ts +++ b/app/front/store/chatItems.ts @@ -1,7 +1,7 @@ import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators" import { ChatItemModel, ChatItemSenderType } from "sushi-chat-shared" import getUUID from "~/utils/getUUID" -import { AuthStore, UserItemStore } from "~/store" +import { UserItemStore } from "~/store" import buildSocket from "~/utils/socketIO" import emitAsync from "~/utils/emitAsync" @@ -86,8 +86,9 @@ export default class ChatItems extends VuexModule { target?: ChatItemModel }) { const id = getUUID() + const isAdmin = UserItemStore.userItems.isAdmin - const senderType: ChatItemSenderType = UserItemStore.userItems.isAdmin + const senderType: ChatItemSenderType = isAdmin ? "admin" : UserItemStore.userItems.speakerId === topicId ? "speaker" @@ -108,7 +109,7 @@ export default class ChatItems extends VuexModule { }) // サーバーに送信する - const socket = buildSocket(AuthStore.idToken) + const socket = await buildSocket(isAdmin) try { await emitAsync(socket, "POST_CHAT_ITEM", { id, @@ -126,6 +127,8 @@ export default class ChatItems extends VuexModule { @Action({ rawError: true }) public async postReaction({ message }: { message: ChatItemModel }) { const id = getUUID() + const isAdmin = UserItemStore.userItems.isAdmin + // ローカルに反映する this.add({ id, @@ -139,7 +142,7 @@ export default class ChatItems extends VuexModule { quote: message, }) // サーバーに反映する - const socket = buildSocket(AuthStore.idToken) + const socket = await buildSocket(isAdmin) try { await emitAsync(socket, "POST_CHAT_ITEM", { id, @@ -164,8 +167,9 @@ export default class ChatItems extends VuexModule { target?: ChatItemModel }) { const id = getUUID() + const isAdmin = UserItemStore.userItems.isAdmin - const senderType: ChatItemSenderType = UserItemStore.userItems.isAdmin + const senderType: ChatItemSenderType = isAdmin ? "admin" : UserItemStore.userItems.speakerId === topicId ? "speaker" @@ -185,7 +189,7 @@ export default class ChatItems extends VuexModule { quote: target, }) // サーバーに反映する - const socket = buildSocket(AuthStore.idToken) + const socket = await buildSocket(isAdmin) try { await emitAsync(socket, "POST_CHAT_ITEM", { id, @@ -211,6 +215,7 @@ export default class ChatItems extends VuexModule { target: ChatItemModel }) { const id = getUUID() + const isAdmin = UserItemStore.userItems.isAdmin const senderType: ChatItemSenderType = UserItemStore.userItems.isAdmin ? "admin" @@ -231,7 +236,7 @@ export default class ChatItems extends VuexModule { content: text, }) // サーバーに反映する - const socket = buildSocket(AuthStore.idToken) + const socket = await buildSocket(isAdmin) try { await emitAsync(socket, "POST_CHAT_ITEM", { id, @@ -248,7 +253,8 @@ export default class ChatItems extends VuexModule { @Action({ rawError: true }) public async retrySendChatItem({ chatItem }: { chatItem: ChatItemModel }) { - const socket = buildSocket(AuthStore.idToken) + const isAdmin = UserItemStore.userItems.isAdmin + const socket = await buildSocket(isAdmin) this.updateStatus({ id: chatItem.id, status: "loading" }) try { await emitAsync(socket, "POST_CHAT_ITEM", chatItem) diff --git a/app/front/store/pinnedChatItems.ts b/app/front/store/pinnedChatItems.ts index e6ae6519..ea97cc58 100644 --- a/app/front/store/pinnedChatItems.ts +++ b/app/front/store/pinnedChatItems.ts @@ -1,5 +1,5 @@ import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators" -import { AuthStore } from "~/store" +import { UserItemStore } from "~/store" import emitAsync from "~/utils/emitAsync" import buildSocket from "~/utils/socketIO" @@ -35,7 +35,7 @@ export default class PinnedChatItems extends VuexModule { topicId: number chatItemId: string }) { - const socket = buildSocket(AuthStore.idToken) + const socket = await buildSocket(UserItemStore.userItems.isAdmin) await emitAsync(socket, "POST_PINNED_MESSAGE", { topicId, chatItemId, diff --git a/app/front/store/stamps.ts b/app/front/store/stamps.ts index bd2604d9..9f66faac 100644 --- a/app/front/store/stamps.ts +++ b/app/front/store/stamps.ts @@ -2,7 +2,7 @@ import { Module, VuexModule, Mutation, Action } from "vuex-module-decorators" import { StampModel } from "sushi-chat-shared" import getUUID from "~/utils/getUUID" import buildSocket from "~/utils/socketIO" -import { AuthStore } from "~/utils/store-accessor" +import { UserItemStore } from "~/utils/store-accessor" import emitAsync from "~/utils/emitAsync" @Module({ @@ -38,7 +38,7 @@ export default class Stamps extends VuexModule { @Action({ rawError: true }) public async sendFavorite(topicId: number) { - const socket = buildSocket(AuthStore.idToken) + const socket = await buildSocket(UserItemStore.userItems.isAdmin) const id = getUUID() // StampStoreは配信で追加する this.add({ diff --git a/app/front/utils/getIdToken.ts b/app/front/utils/getIdToken.ts new file mode 100644 index 00000000..daf517e9 --- /dev/null +++ b/app/front/utils/getIdToken.ts @@ -0,0 +1,8 @@ +import firebase from "firebase" + +const getIdToken = async () => { + const idToken = await firebase.auth().currentUser?.getIdToken() + return idToken ?? null +} + +export default getIdToken diff --git a/app/front/utils/socketIO.ts b/app/front/utils/socketIO.ts index a3c209b5..e10fd651 100644 --- a/app/front/utils/socketIO.ts +++ b/app/front/utils/socketIO.ts @@ -1,22 +1,31 @@ import { io, Socket } from "socket.io-client" import { ServerListenEventsMap, ServerPubEventsMap } from "sushi-chat-shared" +import getIdToken from "./getIdToken" -// 型自体もexportしておく export type SocketIOType = Socket let socket: SocketIOType | null = null -const buildSocket = ( - idToken?: string | null, -): Socket => { - socket = - socket ?? - io(process.env.apiBaseUrl as string, { - auth: { - token: idToken || null, - }, - withCredentials: true, - }) +const buildSocket = async ( + asAdmin: boolean, +): Promise> => { + const idToken = !asAdmin ? null : await getIdToken() + + // NOTE: キャッシュがあれば返す + if ( + socket != null && + "token" in socket.auth && + socket.auth.token === idToken + ) { + return socket + } + + socket = io(process.env.apiBaseUrl as string, { + auth: { + token: idToken || null, + }, + withCredentials: true, + }) return socket } export default buildSocket From 59e739ff74e1489780e24fdb15abc0b73d03f6e1 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Tue, 28 Dec 2021 23:57:24 +0900 Subject: [PATCH 33/61] refactor:privateRoute --- app/front/middleware/privateRoute.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/front/middleware/privateRoute.ts b/app/front/middleware/privateRoute.ts index 11e707eb..7b9600bc 100644 --- a/app/front/middleware/privateRoute.ts +++ b/app/front/middleware/privateRoute.ts @@ -1,7 +1,8 @@ import { Middleware } from "@nuxt/types" +import { AuthStore } from "~/store" -const privateRoute: Middleware = ({ store, redirect }) => { - if (!store.getters["auth/isLoggedIn"]) { +const privateRoute: Middleware = ({ redirect }) => { + if (!AuthStore.isLoggedIn) { return redirect("/login") } } From 88fb257012ce52a42815315cc103141ad9d3e6f1 Mon Sep 17 00:00:00 2001 From: knknk98 <65712721+knknk98@users.noreply.github.com> Date: Tue, 4 Jan 2022 22:38:54 +0900 Subject: [PATCH 34/61] =?UTF-8?q?fix:=20ChatItem=E3=81=8C1=E8=A1=8C?= =?UTF-8?q?=E3=81=AE=E6=99=82padding-bottom=E3=81=8C=E5=A4=A7=E3=81=8D?= =?UTF-8?q?=E3=81=8F=E3=81=AA=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/assets/scss/index.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/front/assets/scss/index.scss b/app/front/assets/scss/index.scss index 3356afe0..b0acd354 100644 --- a/app/front/assets/scss/index.scss +++ b/app/front/assets/scss/index.scss @@ -628,7 +628,6 @@ .reaction { position: relative; width: 100%; - min-height: 5rem; flex-grow: 1; flex-shrink: 0; padding: 0em 0.8em 0.35em; From 442b46d022706ac9296a44d6c97658d7e944bdbe Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Tue, 4 Jan 2022 22:42:27 +0900 Subject: [PATCH 35/61] =?UTF-8?q?[fix]div=E3=82=BF=E3=82=B0=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/components/Message.vue | 4 ++-- app/front/components/TopicHeader.vue | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/front/components/Message.vue b/app/front/components/Message.vue index 2bf027ef..7bf34a72 100644 --- a/app/front/components/Message.vue +++ b/app/front/components/Message.vue @@ -34,7 +34,7 @@
-
diff --git a/app/front/components/TopicHeader.vue b/app/front/components/TopicHeader.vue index 0a3f2b1e..2d00bf7b 100644 --- a/app/front/components/TopicHeader.vue +++ b/app/front/components/TopicHeader.vue @@ -50,12 +50,9 @@ title="ピン留め" > - + @@ -94,6 +99,7 @@ type DataType = { selectedChatItem: ChatItemModel | null showGraph: boolean isAllCommentShowed: boolean + disableScrollTransition: boolean } export default Vue.extend({ @@ -128,6 +134,7 @@ export default Vue.extend({ selectedChatItem: null, showGraph: false, isAllCommentShowed: true, + disableScrollTransition: false, } }, computed: { @@ -197,7 +204,7 @@ export default Vue.extend({ } catch (e) { window.alert("メッセージの送信に失敗しました") } - this.clickScroll() + this.scrollToBottom() this.selectedChatItem = null }, // リアクションボタン @@ -236,8 +243,9 @@ export default Vue.extend({ } }, // いちばん下までスクロール - clickScroll() { + scrollToBottom() { const element: Element | null = (this.$refs.scrollable as Vue).$el + console.log(element?.scrollHeight) if (element) { element.scrollTo({ top: element.scrollHeight, @@ -275,6 +283,9 @@ export default Vue.extend({ }, clickShowAll() { this.isAllCommentShowed = true + Vue.nextTick(() => { + setTimeout(() => this.scrollToBottom(), 100) + }) }, clickNotShowAll() { this.isAllCommentShowed = false From 3b8983bcdd4ef56640fdb57c59a4116c63c10dff Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 18:04:16 +0900 Subject: [PATCH 41/61] fix:find topic process --- app/server/src/domain/room/Room.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/server/src/domain/room/Room.ts b/app/server/src/domain/room/Room.ts index 9f427467..156b2bc8 100644 --- a/app/server/src/domain/room/Room.ts +++ b/app/server/src/domain/room/Room.ts @@ -95,9 +95,11 @@ class RoomClass { } public calcTimestamp = (topicId: number): number | null => { - if (this.topics.find(({ id }) => id === topicId)?.state !== "ongoing") { + const topic = this.findTopicOrThrow(topicId) + if (topic.state !== "ongoing") { return null } + const openedDate = this.findOpenedDateOrThrow(topicId) const offsetTime = this._topicTimeData[topicId].offsetTime const timestamp = new Date().getTime() - openedDate - offsetTime @@ -347,6 +349,7 @@ class RoomClass { .filter( (chatItem): chatItem is Reaction => chatItem instanceof Reaction, ) + // TODO: Array::some使う .find( ({ topicId, user, quote }) => topicId === chatItem.topicId && From 327114fb3b91de155fbb09a60120f261bae481d5 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 18:07:15 +0900 Subject: [PATCH 42/61] refactor:trivial --- app/server/src/domain/room/Room.ts | 7 +++---- app/server/src/domain/user/IconId.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/server/src/domain/room/Room.ts b/app/server/src/domain/room/Room.ts index 156b2bc8..8e905e3e 100644 --- a/app/server/src/domain/room/Room.ts +++ b/app/server/src/domain/room/Room.ts @@ -345,17 +345,16 @@ class RoomClass { // NOTE: 同じユーザーが、同じchatItemに対し、複数回リアクションすることはできない if ( chatItem instanceof Reaction && - this.chatItems + !this.chatItems .filter( (chatItem): chatItem is Reaction => chatItem instanceof Reaction, ) - // TODO: Array::some使う - .find( + .some( ({ topicId, user, quote }) => topicId === chatItem.topicId && user.id === chatItem.user.id && quote.id === chatItem.quote.id, - ) != null + ) ) { throw new Error( `Reaction(topicId: ${chatItem.topicId}, user.id: ${chatItem.user.id}, quote.id: ${chatItem.quote.id}) has already exists.`, diff --git a/app/server/src/domain/user/IconId.ts b/app/server/src/domain/user/IconId.ts index b48d6e1e..92375397 100644 --- a/app/server/src/domain/user/IconId.ts +++ b/app/server/src/domain/user/IconId.ts @@ -6,7 +6,7 @@ export const NewIconId = (value: number): IconId => { if (value < 0 || value > 11) { throw new Error(`value(${value}) is invalid for IconId: out of 0 to 10.`) } - return value as any + return value as unknown as IconId } export default IconId From 1127e6777da0e356741b0bddd0ce513265783529 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 18:48:47 +0900 Subject: [PATCH 43/61] feat:add validation --- app/server/src/domain/room/Room.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/server/src/domain/room/Room.ts b/app/server/src/domain/room/Room.ts index 8e905e3e..80a2552c 100644 --- a/app/server/src/domain/room/Room.ts +++ b/app/server/src/domain/room/Room.ts @@ -96,6 +96,9 @@ class RoomClass { public calcTimestamp = (topicId: number): number | null => { const topic = this.findTopicOrThrow(topicId) + if (topic.state === "not-started") { + throw new Error(`Topic(id:${topicId}) was not started.`) + } if (topic.state !== "ongoing") { return null } @@ -292,8 +295,6 @@ class RoomClass { * @returns MessageClass 運営botメッセージ */ private finishTopic = (topic: Topic): Message => { - topic.state = "finished" - // 質問の集計 const questions = this._chatItems.filter( (c): c is Question => c instanceof Question && c.topicId === topic.id, @@ -311,7 +312,7 @@ class RoomClass { ) // トピック終了のBotメッセージ - return this.postBotMessage( + const message = this.postBotMessage( topic.id, [ "【運営Bot】\n 発表が終了しました!\n(引き続きコメントを投稿いただけます)", @@ -321,6 +322,11 @@ class RoomClass { .filter(Boolean) .join("\n"), ) + + // NOTE: stateの更新前に、botmessageのタイムスタンプを計算しておく必要がある + topic.state = "finished" + + return message } /** @@ -345,7 +351,7 @@ class RoomClass { // NOTE: 同じユーザーが、同じchatItemに対し、複数回リアクションすることはできない if ( chatItem instanceof Reaction && - !this.chatItems + this.chatItems .filter( (chatItem): chatItem is Reaction => chatItem instanceof Reaction, ) From c80ccd313f97d6efbf23d1da2de8e9f830fda807 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 18:48:53 +0900 Subject: [PATCH 44/61] fix:test --- app/server/src/__test__/chat.ts | 2 +- app/server/src/__test__/testChatItemService.ts | 2 +- app/server/src/__test__/testStampService.ts | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index 27dec394..b4b2539f 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -627,7 +627,7 @@ describe("機能テスト", () => { senderType: "speaker", iconId: 3, content: "ongoingでないトピックへの投稿", - timestamp: expect.any(Number), + timestamp: undefined, }) notOnGoingTopicMessage = res resolve() diff --git a/app/server/src/__test__/testChatItemService.ts b/app/server/src/__test__/testChatItemService.ts index a433e68f..07d2338c 100644 --- a/app/server/src/__test__/testChatItemService.ts +++ b/app/server/src/__test__/testChatItemService.ts @@ -65,7 +65,7 @@ describe("ChatItemServiceのテスト", () => { "test room", uuid(), "This is test room.", - [{ title: "test topic" }], + [{ title: "test topic", state: "ongoing" }], new Set([admin.id]), "ongoing", new Date(), diff --git a/app/server/src/__test__/testStampService.ts b/app/server/src/__test__/testStampService.ts index 327f3778..4a272010 100644 --- a/app/server/src/__test__/testStampService.ts +++ b/app/server/src/__test__/testStampService.ts @@ -58,7 +58,10 @@ describe("StampServiceのテスト", () => { "test room", uuid(), "This is test room.", - [{ title: "test topic" }], + [ + { id: 1, title: "test topic", state: "ongoing" }, + { id: 2, title: "test topic 2", state: "not-started" }, + ], new Set([admin.id]), "ongoing", new Date(), @@ -88,7 +91,7 @@ describe("StampServiceのテスト", () => { expect(deliveredStamp.userId).toBe(userId) }) - test("異常系_OPEN状態でないTopicへはスタンプを投稿できない", async () => { + test("異常系_ONGOING状態でないTopicへはスタンプを投稿できない", async () => { const stampId = uuid() await expect(() => stampService.post({ id: stampId, userId, topicId: 2 }), From b9f05f1ac1fbb6e7005811878226be2d60e84fe9 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 19:32:02 +0900 Subject: [PATCH 45/61] =?UTF-8?q?fix:=E7=8A=B6=E6=85=8B=E3=81=AE=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=82=BF=E3=82=A4=E3=83=9F=E3=83=B3=E3=82=B0=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/server/src/domain/room/Room.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/server/src/domain/room/Room.ts b/app/server/src/domain/room/Room.ts index 80a2552c..5b4e9306 100644 --- a/app/server/src/domain/room/Room.ts +++ b/app/server/src/domain/room/Room.ts @@ -282,11 +282,17 @@ class RoomClass { * @returns MessageClass 運営botメッセージ */ private pauseTopic(topic: Topic): Message { - topic.state = "paused" - this._topicTimeData[topic.id].pausedDate = new Date().getTime() - return this.postBotMessage(topic.id, "【運営Bot】\n 発表が中断されました") + const message = this.postBotMessage( + topic.id, + "【運営Bot】\n 発表が中断されました", + ) + + // NOTE: stateの更新前に、botmessageのタイムスタンプを計算しておく必要がある + topic.state = "paused" + + return message } /** From 916e1b99a180d9f4f35062e4a5d3a137d7e7168c Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 19:32:09 +0900 Subject: [PATCH 46/61] fix:trivial --- app/server/src/service/chatItem/ChatItemModelBuilder.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/server/src/service/chatItem/ChatItemModelBuilder.ts b/app/server/src/service/chatItem/ChatItemModelBuilder.ts index 6d9dee38..16408a58 100644 --- a/app/server/src/service/chatItem/ChatItemModelBuilder.ts +++ b/app/server/src/service/chatItem/ChatItemModelBuilder.ts @@ -40,7 +40,7 @@ class ChatItemModelBuilder { iconId: message.user.iconId.valueOf(), content: message.content, quote: quoteModel, - timestamp: message.timestamp, + timestamp: message.timestamp ?? undefined, } } @@ -66,7 +66,7 @@ class ChatItemModelBuilder { senderType: reaction.senderType, iconId: reaction.user.iconId.valueOf(), quote: quoteModel, - timestamp: reaction.timestamp, + timestamp: reaction.timestamp ?? undefined, } } @@ -91,7 +91,7 @@ class ChatItemModelBuilder { iconId: question.user.iconId.valueOf(), content: question.content, quote: quoteModel, - timestamp: question.timestamp, + timestamp: question.timestamp ?? undefined, } } @@ -108,7 +108,7 @@ class ChatItemModelBuilder { quote: !recursive ? undefined : this.buildQuestion(answer.quote as Question), - timestamp: answer.timestamp, + timestamp: answer.timestamp ?? undefined, } } } From 9ef660b732405bf0f842733507d68c22cf4d1ebb Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 19:32:23 +0900 Subject: [PATCH 47/61] fix:chat test --- app/server/src/__test__/chat.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/server/src/__test__/chat.ts b/app/server/src/__test__/chat.ts index b4b2539f..67e82f1e 100644 --- a/app/server/src/__test__/chat.ts +++ b/app/server/src/__test__/chat.ts @@ -476,6 +476,18 @@ describe("機能テスト", () => { // NOTE: roomData.topics[2]のトピックが進行中になっている前提 describe("ChatItemの投稿", () => { beforeAll(() => { + // await new Promise((resolve) => + // adminSocket.emit( + // "ADMIN_CHANGE_TOPIC_STATE", + // { + // topicId: roomData.topics[2].id, + // state: "ongoing", + // }, + // // eslint-disable-next-line @typescript-eslint/no-empty-function + // resolve, + // ), + // ) + clientSockets[2].emit( "ENTER_ROOM", { @@ -619,7 +631,8 @@ describe("機能テスト", () => { notOnGoingTopicMessageId = uuid() clientSockets[0].on("PUB_CHAT_ITEM", (res) => { - expect(res).toStrictEqual({ + // NOTE: `timestamp: undefined` は存在しない + expect(res).toEqual({ id: notOnGoingTopicMessageId, topicId: roomData.topics[0].id, createdAt: MATCHING.DATE, @@ -627,7 +640,6 @@ describe("機能テスト", () => { senderType: "speaker", iconId: 3, content: "ongoingでないトピックへの投稿", - timestamp: undefined, }) notOnGoingTopicMessage = res resolve() @@ -880,7 +892,7 @@ describe("機能テスト", () => { speakerTopicId: roomData.topics[0].id, }, (res) => { - expect(res).toStrictEqual({ + expect(res).toEqual({ result: "success", data: { chatItems: expect.arrayContaining(history.chatItems), From 2c32d56655393fbc2b80177a121fa6672b49f2c9 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Wed, 5 Jan 2022 20:47:35 +0900 Subject: [PATCH 48/61] empty From f735c8c02135ebed11f55b6a74b59c7623fd97e6 Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Wed, 12 Jan 2022 22:34:25 +0900 Subject: [PATCH 49/61] =?UTF-8?q?[fix]=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=82=92button=E3=82=BF=E3=82=B0=E3=81=AE=E3=81=BE=E3=81=BE?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/components/ChatRoom.vue | 6 ++++-- app/front/components/TopicHeader.vue | 32 ++++++++++++++++------------ 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/front/components/ChatRoom.vue b/app/front/components/ChatRoom.vue index e1c7526f..1c086848 100644 --- a/app/front/components/ChatRoom.vue +++ b/app/front/components/ChatRoom.vue @@ -37,8 +37,10 @@
-
diff --git a/app/front/components/TopicHeader.vue b/app/front/components/TopicHeader.vue index 0a3f2b1e..f773fcee 100644 --- a/app/front/components/TopicHeader.vue +++ b/app/front/components/TopicHeader.vue @@ -10,12 +10,14 @@ #{{ topicIndex }}
{{ title }}
-
@@ -56,14 +58,16 @@ > {{ pinnedChatItemContent }} -
From ea7e684fa635bd7f58133d226baa1079ab0fa8ff Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Wed, 12 Jan 2022 23:40:01 +0900 Subject: [PATCH 50/61] =?UTF-8?q?[fix]=E3=82=B9=E3=83=94=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=BC=E9=81=B8=E6=8A=9E=E6=AC=84=E3=81=AE=E5=B9=85=E3=82=92?= =?UTF-8?q?=E5=85=A5=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/assets/scss/sushiselect.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/front/assets/scss/sushiselect.scss b/app/front/assets/scss/sushiselect.scss index 6f3675d7..3b64dc00 100644 --- a/app/front/assets/scss/sushiselect.scss +++ b/app/front/assets/scss/sushiselect.scss @@ -90,8 +90,10 @@ } &--speaker { position: relative; - width: 400px; + width: 100%; + max-width: 400px; margin-bottom: 50px; + background: white; & > .select-speaker { -webkit-appearance: none; appearance: none; From 9f6a77157e9258a9ad04ad6ab3b69df0f887a616 Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Tue, 18 Jan 2022 22:28:23 +0900 Subject: [PATCH 51/61] =?UTF-8?q?[fix]=20=E3=82=BB=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E5=85=A5=E5=8A=9B=E6=AC=84=E3=81=AB=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=AB=E3=82=92=E5=BD=93=E3=81=A6=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/assets/scss/home/home.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/front/assets/scss/home/home.scss b/app/front/assets/scss/home/home.scss index 72850e65..04cab95e 100644 --- a/app/front/assets/scss/home/home.scss +++ b/app/front/assets/scss/home/home.scss @@ -145,12 +145,12 @@ flex: 1; margin: 0 0.5rem 0 0; padding: 0 !important; - @include white-text-box($expand: "true"); input { width: 100%; border: none; flex: 1; padding: 0; + @include white-text-box($expand: "true"); &:focus { outline: none; } From fe67d33bb6816383bc25f50f5550ad1332ad0317 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:30:10 +0900 Subject: [PATCH 52/61] feat:add border radius --- app/front/assets/scss/sushiselect.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/front/assets/scss/sushiselect.scss b/app/front/assets/scss/sushiselect.scss index 3b64dc00..91621252 100644 --- a/app/front/assets/scss/sushiselect.scss +++ b/app/front/assets/scss/sushiselect.scss @@ -93,6 +93,7 @@ width: 100%; max-width: 400px; margin-bottom: 50px; + border-radius: 4px; background: white; & > .select-speaker { -webkit-appearance: none; @@ -102,7 +103,6 @@ padding: 10px; padding-right: 2.5em; border: 1px solid transparent; - border-radius: 10px; outline: none; cursor: pointer; @include text-size("normal"); From 939c00aaa74dbe43b64ec4a1f1df06ad9e069ea9 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Tue, 18 Jan 2022 22:46:01 +0900 Subject: [PATCH 53/61] fix:border style --- app/front/assets/scss/sushiselect.scss | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/front/assets/scss/sushiselect.scss b/app/front/assets/scss/sushiselect.scss index 91621252..0cc64402 100644 --- a/app/front/assets/scss/sushiselect.scss +++ b/app/front/assets/scss/sushiselect.scss @@ -93,8 +93,6 @@ width: 100%; max-width: 400px; margin-bottom: 50px; - border-radius: 4px; - background: white; & > .select-speaker { -webkit-appearance: none; appearance: none; @@ -102,14 +100,12 @@ width: 100%; padding: 10px; padding-right: 2.5em; - border: 1px solid transparent; + border: none; + border-radius: 4px; + background: white; outline: none; cursor: pointer; @include text-size("normal"); - - &:focus-visible { - border-color: rgb(214, 214, 214); - } } & > .select-icon { From b5c73897a448aee2099dfe811be7361d49d5dcd9 Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Tue, 18 Jan 2022 23:13:29 +0900 Subject: [PATCH 54/61] Update Modal.vue --- app/front/components/Home/Modal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/front/components/Home/Modal.vue b/app/front/components/Home/Modal.vue index bf9c5010..3d29c414 100644 --- a/app/front/components/Home/Modal.vue +++ b/app/front/components/Home/Modal.vue @@ -40,7 +40,7 @@ export default Vue.extend({ computed: { width() { if (DeviceStore.device === "smartphone") { - return "100%" + return "90%" } else { return "50%" } From 75bd3c0639ca1e8b003eda727bce3eb77d8ea31f Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Tue, 18 Jan 2022 23:26:15 +0900 Subject: [PATCH 55/61] =?UTF-8?q?[fix]button=E3=82=BF=E3=82=B0=E3=81=AB@cl?= =?UTF-8?q?ick=E3=82=92=E3=81=A4=E3=81=91=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/components/ChatRoom.vue | 4 ++-- app/front/components/TopicHeader.vue | 26 ++++++++++++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/front/components/ChatRoom.vue b/app/front/components/ChatRoom.vue index 1c086848..aefbf572 100644 --- a/app/front/components/ChatRoom.vue +++ b/app/front/components/ChatRoom.vue @@ -37,8 +37,8 @@
- diff --git a/app/front/components/TopicHeader.vue b/app/front/components/TopicHeader.vue index f773fcee..5490cb62 100644 --- a/app/front/components/TopicHeader.vue +++ b/app/front/components/TopicHeader.vue @@ -10,12 +10,11 @@ #{{ topicIndex }}
{{ title }}
- @@ -58,14 +57,13 @@ > {{ pinnedChatItemContent }} - From 4f740dfdbc7960a57fe3236efc9dc76506e132b2 Mon Sep 17 00:00:00 2001 From: yuta-ike <38308823+yuta-ike@users.noreply.github.com> Date: Tue, 18 Jan 2022 23:41:22 +0900 Subject: [PATCH 56/61] remove unused variable --- app/front/assets/scss/index.scss | 3 --- app/front/components/ChatRoom.vue | 3 --- 2 files changed, 6 deletions(-) diff --git a/app/front/assets/scss/index.scss b/app/front/assets/scss/index.scss index f3848f1c..2d988560 100644 --- a/app/front/assets/scss/index.scss +++ b/app/front/assets/scss/index.scss @@ -240,9 +240,6 @@ .list-complete-item { transition: all 0.3s; transform: translateX(0); - &.disable-scroll-transition { - transition: transform 0s !important; - } } .list-complete-enter, .list-complete-leave-to { diff --git a/app/front/components/ChatRoom.vue b/app/front/components/ChatRoom.vue index 9ea01ea3..197606d7 100644 --- a/app/front/components/ChatRoom.vue +++ b/app/front/components/ChatRoom.vue @@ -20,7 +20,6 @@ v-for="message in chatItems" :key="message.id" class="list-complete-item" - :class="{ 'disable-scroll-transition': disableScrollTransition }" >
Date: Tue, 18 Jan 2022 23:57:13 +0900 Subject: [PATCH 58/61] =?UTF-8?q?[fix]=E3=83=98=E3=83=83=E3=83=80=E3=83=BC?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/assets/scss/index.scss | 12 +++--------- app/front/components/TopicHeader.vue | 11 ++++++++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/front/assets/scss/index.scss b/app/front/assets/scss/index.scss index ea80910c..0eeb6eae 100644 --- a/app/front/assets/scss/index.scss +++ b/app/front/assets/scss/index.scss @@ -150,8 +150,10 @@ & > .icon { transform: rotate(20deg); } - &--text { + &--button { flex: 1; + } + &--content { display: -webkit-box; word-break: break-all; overflow-wrap: anywhere; @@ -162,10 +164,6 @@ font-size: 100%; width: 100%; max-height: 2.5rem; - &:hover { - text-decoration: underline; - cursor: pointer; - } } &--close-icon { flex-shrink: 0; @@ -680,10 +678,6 @@ font-size: 80%; width: 100%; max-height: 2rem; - &:hover { - text-decoration: underline; - cursor: pointer; - } } } } diff --git a/app/front/components/TopicHeader.vue b/app/front/components/TopicHeader.vue index 2d00bf7b..7b2fc19c 100644 --- a/app/front/components/TopicHeader.vue +++ b/app/front/components/TopicHeader.vue @@ -50,9 +50,14 @@ title="ピン留め" >
-
- {{ pinnedChatItemContent }} -
+
-
- - -
+ + + + +
From e4c18761f0e4973432dfd64366818101337d9a1c Mon Sep 17 00:00:00 2001 From: KoukiNAGATA Date: Wed, 19 Jan 2022 01:12:17 +0900 Subject: [PATCH 61/61] =?UTF-8?q?[feat]=E7=B5=82=E4=BA=86=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92=E5=8F=96=E5=BE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/front/pages/index.vue | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/app/front/pages/index.vue b/app/front/pages/index.vue index 6be87c68..9a67d883 100644 --- a/app/front/pages/index.vue +++ b/app/front/pages/index.vue @@ -103,7 +103,7 @@ export default Vue.extend({ }, computed: { isRoomStarted(): boolean { - return this.room.state === "ongoing" + return this.room.state === "ongoing" || this.room.state === "finished" }, isAdmin(): boolean { return UserItemStore.userItems.isAdmin @@ -162,19 +162,42 @@ export default Vue.extend({ this.roomState = res.data.state TopicStore.set(res.data.topics) - // 開催中の時 if (this.room.state === "ongoing") { + // 開催中 if (this.isAdmin) { this.adminEnterRoom() } else { // ユーザーの入室 this.$modal.show("sushi-modal") } + } else if (this.room.state === "finished") { + // 終了時。すべてのトピックが閉じたルームのアーカイブが閲覧できる。 + const history = await this.$apiClient + .get({ + pathname: "/room/:id/history", + params: { id: this.room.id }, + }) + .catch((e) => { + throw new Error(e) + }) + if (history.result === "error") { + console.error(history.error) + return + } + // 入室 + ChatItemStore.setChatItems( + history.data.chatItems.map((chatItem) => ({ + ...chatItem, + status: "success", + })), + ) + history.data.pinnedChatItemIds.forEach((pinnedChatItem) => { + if (pinnedChatItem) { + PinnedChatItemsStore.add(pinnedChatItem) + } + }) + this.isRoomEnter = true } - if (this.room.state === "finished") { - // 本当はRESTでアーカイブデータを取ってきて表示する - } - // NOTE: もしかして:archivedも返ってくる? }, // socket.ioのセットアップ。配信を受け取る async socketSetUp() {