From b78eac4175ce1636dd85fd7ac60f490db27b0c9c Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Sat, 26 Oct 2024 15:50:43 +0100 Subject: [PATCH 1/6] chore: add .vscode to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6a7d6d8..2c43e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# VS Code +.vscode + # Logs logs *.log From 1c584d38c598d05f298100b295e189e3bb92fc0d Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Sat, 26 Oct 2024 15:53:38 +0100 Subject: [PATCH 2/6] feat: decouple commands from commandResolver. Closes #64 --- domain/service/commandUseCaseResolver.ts | 51 +++++++++++++----------- domain/service/commands.json | 4 ++ 2 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 domain/service/commands.json diff --git a/domain/service/commandUseCaseResolver.ts b/domain/service/commandUseCaseResolver.ts index 9de5e77..000a8fc 100644 --- a/domain/service/commandUseCaseResolver.ts +++ b/domain/service/commandUseCaseResolver.ts @@ -1,3 +1,5 @@ +import { promises as fs } from "fs"; +import path from "path"; import { Context } from "../../types"; import UseCaseNotFound from "../exception/useCaseNotFound"; import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"; @@ -8,8 +10,6 @@ import ChannelResolver from "./channelResolver"; import KataService from "./kataService/kataService"; import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; -type CallbackFunctionVariadic = (...args: unknown[]) => void; - export default class CommandUseCaseResolver { private messageRepository: MessageRepository; @@ -21,6 +21,8 @@ export default class CommandUseCaseResolver { private kataService: KataService; + private commandMessages: Record = {}; + constructor({ messageRepository, chatService, @@ -41,7 +43,13 @@ export default class CommandUseCaseResolver { this.kataService = kataService; } - resolveByCommand(command: string, context: Context): void { + private async loadCommands(): Promise { + const filePath = path.join(__dirname, "commands.json"); + const data = await fs.readFile(filePath, "utf-8"); + this.commandMessages = JSON.parse(data); + } + + async resolveByCommand(command: string, context: Context): Promise { this.loggerService.log(`Command received: "${command}"`); const deps = { @@ -52,28 +60,25 @@ export default class CommandUseCaseResolver { kataService: this.kataService, }; - const commandUseCases: Record = { - "!ja": async () => - new SendMessageToChannelUseCase(deps).execute({ - channelId: context.channelId, - message: - "Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/", - }), - "!oc": async () => - new SendMessageToChannelUseCase(deps).execute({ - channelId: context.channelId, - message: ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:", - }), - "!cwl": async () => - new SendCodewarsLeaderboardToChannelUseCase(deps).execute({ - channelId: context.channelId, - }), - }; + if (Object.keys(this.commandMessages).length === 0) { + await this.loadCommands(); + } + + if (this.commandMessages[command]) { + new SendMessageToChannelUseCase(deps).execute({ + channelId: context.channelId, + message: this.commandMessages[command], + }); + return; + } - if (!commandUseCases[command]) { - throw new UseCaseNotFound().byCommand(command); + if (command === "!cwl") { + new SendCodewarsLeaderboardToChannelUseCase(deps).execute({ + channelId: context.channelId, + }); + return; } - commandUseCases[command](); + throw new UseCaseNotFound().byCommand(command); } } diff --git a/domain/service/commands.json b/domain/service/commands.json new file mode 100644 index 0000000..38ec8a9 --- /dev/null +++ b/domain/service/commands.json @@ -0,0 +1,4 @@ +{ + "!ja": "Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/", + "!oc": ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:" +} From cfece54461b75fa9505e783404686ff271e8a8ae Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Sat, 26 Oct 2024 15:54:42 +0100 Subject: [PATCH 3/6] test: decoupled --- vitest/service/commandUseCaseResolver.spec.ts | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 vitest/service/commandUseCaseResolver.spec.ts diff --git a/vitest/service/commandUseCaseResolver.spec.ts b/vitest/service/commandUseCaseResolver.spec.ts new file mode 100644 index 0000000..3bdd54f --- /dev/null +++ b/vitest/service/commandUseCaseResolver.spec.ts @@ -0,0 +1,84 @@ +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; +import { promises as fs } from "fs"; +import CommandUseCaseResolver from "../../domain/service/commandUseCaseResolver"; +import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"; +import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; +import { Context } from "../../types"; + +vi.mock("../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"); +vi.mock("../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"); + +vi.mock("fs", () => ({ + promises: { + readFile: vi.fn(), + }, +})); + +describe("CommandUseCaseResolver", () => { + let commandUseCaseResolver: CommandUseCaseResolver; + const mockContext: Context = { + channelId: "test-channel", + }; + + beforeEach(async () => { + (fs.readFile as vi.Mock).mockResolvedValueOnce( + JSON.stringify({ + "!ja": "Olá! Test message", + "!oc": ":warning: Only Code questions!", + }) + ); + + commandUseCaseResolver = new CommandUseCaseResolver({ + messageRepository: { + getRandomIntroMessage: vi.fn(), + getRandomWelcomingMessage: vi.fn(), + }, + chatService: { + sendMessageToChannel: vi.fn(), + }, + loggerService: { log: vi.fn() }, + channelResolver: { + getBySlug: vi.fn(), + }, + kataService: { + getLeaderboard: vi.fn().mockResolvedValue([]), + }, + }); + }); + + it("should send the correct message for !ja command", async () => { + await commandUseCaseResolver.resolveByCommand("!ja", mockContext); + + expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ + channelId: "test-channel", + message: "Olá! Test message", + }); + }); + + it("should send the correct message for !oc command", async () => { + await commandUseCaseResolver.resolveByCommand("!oc", mockContext); + + expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ + channelId: "test-channel", + message: ":warning: Only Code questions!", + }); + }); + + it("should handle the !cwl command by sending the leaderboard", async () => { + await commandUseCaseResolver.resolveByCommand("!cwl", mockContext); + + expect(SendCodewarsLeaderboardToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ + channelId: "test-channel", + }); + }); + + it("should throw UseCaseNotFound error for unknown command", async () => { + await expect(commandUseCaseResolver.resolveByCommand("!unknown", mockContext)).rejects.toThrow( + 'Use case for command "!unknown" not found' + ); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); +}); From 037a8599cdf10d8ed3432be099a7a6e05d71d610 Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Sat, 2 Nov 2024 15:54:43 +0000 Subject: [PATCH 4/6] refactor: redesign commands into command pattern with previous commands implemented --- .../command/codewarsLeaderboardCommand.ts | 60 +++++++++++++++ application/command/dontAskToAskCommand.ts | 19 +++++ .../command/onlyCodeQuestionsCommand.ts | 19 +++++ domain/service/commandUseCaseResolver.ts | 74 +++++-------------- domain/service/commands.json | 4 - types.ts | 9 +++ vitest/service/commandUseCaseResolver.spec.ts | 46 ++---------- 7 files changed, 130 insertions(+), 101 deletions(-) create mode 100644 application/command/codewarsLeaderboardCommand.ts create mode 100644 application/command/dontAskToAskCommand.ts create mode 100644 application/command/onlyCodeQuestionsCommand.ts delete mode 100644 domain/service/commands.json diff --git a/application/command/codewarsLeaderboardCommand.ts b/application/command/codewarsLeaderboardCommand.ts new file mode 100644 index 0000000..91966b6 --- /dev/null +++ b/application/command/codewarsLeaderboardCommand.ts @@ -0,0 +1,60 @@ +import { SendCodewarsLeaderboardToChannelInput } from "application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelInput"; +import { Command } from "../../types"; +import KataService from "../../domain/service/kataService/kataService"; +import ChatService from "../../domain/service/chatService"; +import KataLeaderboardUser from "../../domain/service/kataService/kataLeaderboardUser"; + +export default class CodewarsLeaderboardCommand implements Command { + readonly name = "!cwl"; + + private chatService: ChatService; + + private kataService: KataService; + + constructor(chatService: ChatService, kataService: KataService) { + this.chatService = chatService; + this.kataService = kataService; + } + + private formatLeaderboard(leaderboard: KataLeaderboardUser[]): string { + let output = "```"; + let position = 1; + + const leaderboardTotalEntriesToShow = 10; + const leaderboardEntries = leaderboard.slice(0, leaderboardTotalEntriesToShow); + const leaderboardEntriesLeft = leaderboard.length - leaderboardTotalEntriesToShow; + + leaderboardEntries.forEach((user: KataLeaderboardUser) => { + const pointsCollection = user.getPoints().map((points: number) => points || 0); + + output += `${position}. ${user.getUsername()} - ${user.getScore()} - [${pointsCollection.join(",")}] points +`; + + position += 1; + }); + + output += "```"; + + if (leaderboardEntriesLeft > 1) { + output += ` +... e ${leaderboardEntriesLeft} outras participações em https://codewars.devpt.co`; + } else if (leaderboardEntriesLeft === 1) { + output += ` +... e 1 outra participação em https://codewars.devpt.co`; + } + + return output; + } + + async execute({ channelId }: SendCodewarsLeaderboardToChannelInput): Promise { + const leaderboard = await this.kataService.getLeaderboard(); + + if (leaderboard.length === 0) { + this.chatService.sendMessageToChannel("Ainda não existem participantes nesta ediçăo do desafio.", channelId); + return; + } + + const formattedLeaderboard = this.formatLeaderboard(leaderboard); + this.chatService.sendMessageToChannel(formattedLeaderboard, channelId); + } +} diff --git a/application/command/dontAskToAskCommand.ts b/application/command/dontAskToAskCommand.ts new file mode 100644 index 0000000..2a8d439 --- /dev/null +++ b/application/command/dontAskToAskCommand.ts @@ -0,0 +1,19 @@ +import { Command, Context } from "../../types"; +import ChatService from "../../domain/service/chatService"; + +export default class DontAskToAskCommand implements Command { + readonly name = "!ja"; + + private readonly message: string = + "Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/"; + + private chatService: ChatService; + + constructor(chatService: ChatService) { + this.chatService = chatService; + } + + async execute(context: Context): Promise { + await this.chatService.sendMessageToChannel(this.message, context.channelId); + } +} diff --git a/application/command/onlyCodeQuestionsCommand.ts b/application/command/onlyCodeQuestionsCommand.ts new file mode 100644 index 0000000..2aea596 --- /dev/null +++ b/application/command/onlyCodeQuestionsCommand.ts @@ -0,0 +1,19 @@ +import { Command, Context } from "../../types"; +import ChatService from "../../domain/service/chatService"; + +export default class OnlyCodeQuestionsCommand implements Command { + readonly name = "!oc"; + + private chatService: ChatService; + + private readonly message: string = + ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:"; + + constructor(chatService: ChatService) { + this.chatService = chatService; + } + + async execute(context: Context): Promise { + await this.chatService.sendMessageToChannel(this.message, context.channelId); + } +} diff --git a/domain/service/commandUseCaseResolver.ts b/domain/service/commandUseCaseResolver.ts index 000a8fc..ab1e8d1 100644 --- a/domain/service/commandUseCaseResolver.ts +++ b/domain/service/commandUseCaseResolver.ts @@ -1,84 +1,44 @@ -import { promises as fs } from "fs"; -import path from "path"; -import { Context } from "../../types"; +import CodewarsLeaderboardCommand from "../../application/command/codewarsLeaderboardCommand"; +import DontAskToAskCommand from "../../application/command/dontAskToAskCommand"; +import OnlyCodeQuestionsCommand from "../../application/command/onlyCodeQuestionsCommand"; +import { Command, Context } from "../../types"; import UseCaseNotFound from "../exception/useCaseNotFound"; -import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"; -import MessageRepository from "../repository/messageRepository"; import ChatService from "./chatService"; -import LoggerService from "./loggerService"; -import ChannelResolver from "./channelResolver"; import KataService from "./kataService/kataService"; -import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; +import LoggerService from "./loggerService"; export default class CommandUseCaseResolver { - private messageRepository: MessageRepository; - - private chatService: ChatService; + private commands: Command[] = []; private loggerService: LoggerService; - private channelResolver: ChannelResolver; - - private kataService: KataService; - - private commandMessages: Record = {}; - constructor({ - messageRepository, chatService, - loggerService, - channelResolver, kataService, + loggerService, }: { - messageRepository: MessageRepository; chatService: ChatService; - loggerService: LoggerService; - channelResolver: ChannelResolver; kataService: KataService; + loggerService: LoggerService; }) { - this.messageRepository = messageRepository; - this.chatService = chatService; this.loggerService = loggerService; - this.channelResolver = channelResolver; - this.kataService = kataService; - } - private async loadCommands(): Promise { - const filePath = path.join(__dirname, "commands.json"); - const data = await fs.readFile(filePath, "utf-8"); - this.commandMessages = JSON.parse(data); + this.commands.push( + new CodewarsLeaderboardCommand(chatService, kataService), + new DontAskToAskCommand(chatService), + new OnlyCodeQuestionsCommand(chatService) + ); } async resolveByCommand(command: string, context: Context): Promise { this.loggerService.log(`Command received: "${command}"`); - const deps = { - messageRepository: this.messageRepository, - chatService: this.chatService, - loggerService: this.loggerService, - channelResolver: this.channelResolver, - kataService: this.kataService, - }; - - if (Object.keys(this.commandMessages).length === 0) { - await this.loadCommands(); - } - - if (this.commandMessages[command]) { - new SendMessageToChannelUseCase(deps).execute({ - channelId: context.channelId, - message: this.commandMessages[command], - }); - return; - } + const commandInstance = this.commands.find((cmd) => cmd.name === command); - if (command === "!cwl") { - new SendCodewarsLeaderboardToChannelUseCase(deps).execute({ - channelId: context.channelId, - }); - return; + if (!commandInstance) { + throw new UseCaseNotFound().byCommand(command); } - throw new UseCaseNotFound().byCommand(command); + await commandInstance.execute(context); } } diff --git a/domain/service/commands.json b/domain/service/commands.json deleted file mode 100644 index 38ec8a9..0000000 --- a/domain/service/commands.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "!ja": "Olá! Experimenta fazer a pergunta diretamente e contar o que já tentaste! Sabe mais aqui :point_right: https://dontasktoask.com/pt-pt/", - "!oc": ":warning: Este servidor é APENAS para questões relacionadas com programação! :warning:" -} diff --git a/types.ts b/types.ts index e8c17f9..5e7f2bf 100644 --- a/types.ts +++ b/types.ts @@ -10,3 +10,12 @@ export enum ChannelSlug { ENTRANCE = "ENTRANCE", JOBS = "JOBS", } + +export type CommandMessages = { + [command: string]: string; +}; + +export interface Command { + name: string; + execute(context: Context): Promise; +} diff --git a/vitest/service/commandUseCaseResolver.spec.ts b/vitest/service/commandUseCaseResolver.spec.ts index 3bdd54f..913eb7c 100644 --- a/vitest/service/commandUseCaseResolver.spec.ts +++ b/vitest/service/commandUseCaseResolver.spec.ts @@ -1,19 +1,7 @@ import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; -import { promises as fs } from "fs"; import CommandUseCaseResolver from "../../domain/service/commandUseCaseResolver"; -import SendMessageToChannelUseCase from "../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"; -import SendCodewarsLeaderboardToChannelUseCase from "../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; import { Context } from "../../types"; -vi.mock("../../application/usecases/sendMessageToChannel/sendMessageToChannelUseCase"); -vi.mock("../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"); - -vi.mock("fs", () => ({ - promises: { - readFile: vi.fn(), - }, -})); - describe("CommandUseCaseResolver", () => { let commandUseCaseResolver: CommandUseCaseResolver; const mockContext: Context = { @@ -21,55 +9,33 @@ describe("CommandUseCaseResolver", () => { }; beforeEach(async () => { - (fs.readFile as vi.Mock).mockResolvedValueOnce( - JSON.stringify({ - "!ja": "Olá! Test message", - "!oc": ":warning: Only Code questions!", - }) - ); - commandUseCaseResolver = new CommandUseCaseResolver({ - messageRepository: { - getRandomIntroMessage: vi.fn(), - getRandomWelcomingMessage: vi.fn(), - }, chatService: { sendMessageToChannel: vi.fn(), }, loggerService: { log: vi.fn() }, - channelResolver: { - getBySlug: vi.fn(), - }, kataService: { getLeaderboard: vi.fn().mockResolvedValue([]), }, }); }); - it("should send the correct message for !ja command", async () => { + it("should invoke for !ja command", async () => { await commandUseCaseResolver.resolveByCommand("!ja", mockContext); - expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ - channelId: "test-channel", - message: "Olá! Test message", - }); + expect(() => commandUseCaseResolver.resolveByCommand("!ja", mockContext)).not.toThrow(); }); - it("should send the correct message for !oc command", async () => { + it("should invoke for !oc command", async () => { await commandUseCaseResolver.resolveByCommand("!oc", mockContext); - expect(SendMessageToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ - channelId: "test-channel", - message: ":warning: Only Code questions!", - }); + expect(() => commandUseCaseResolver.resolveByCommand("!oc", mockContext)).not.toThrow(); }); - it("should handle the !cwl command by sending the leaderboard", async () => { + it("should invoke for !cwl command", async () => { await commandUseCaseResolver.resolveByCommand("!cwl", mockContext); - expect(SendCodewarsLeaderboardToChannelUseCase.prototype.execute).toHaveBeenCalledWith({ - channelId: "test-channel", - }); + expect(() => commandUseCaseResolver.resolveByCommand("!cwl", mockContext)).not.toThrow(); }); it("should throw UseCaseNotFound error for unknown command", async () => { From ab15b4b0d410b9199f236601be4ff461f646dd66 Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Sat, 2 Nov 2024 16:09:44 +0000 Subject: [PATCH 5/6] refactor: cleanup codewars duplication --- ...sendCodewarsLeaderboardToChannelUseCase.ts | 57 ------------------- .../codewarsLeaderboardCommand.spec.ts} | 17 ++---- 2 files changed, 4 insertions(+), 70 deletions(-) delete mode 100644 application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase.ts rename vitest/application/{usecases/sendCodewarsLeaderboardToChannelUseCase.spec.ts => command/codewarsLeaderboardCommand.spec.ts} (87%) diff --git a/application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase.ts b/application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase.ts deleted file mode 100644 index efbdf33..0000000 --- a/application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase.ts +++ /dev/null @@ -1,57 +0,0 @@ -import KataLeaderboardUser from "../../../domain/service/kataService/kataLeaderboardUser"; -import KataService from "../../../domain/service/kataService/kataService"; -import ChatService from "../../../domain/service/chatService"; -import { SendCodewarsLeaderboardToChannelInput } from "./sendCodewarsLeaderboardToChannelInput"; - -export default class SendCodewarsLeaderboardToChannelUseCase { - private chatService: ChatService; - - private kataService: KataService; - - constructor({ chatService, kataService }: { chatService: ChatService; kataService: KataService }) { - this.chatService = chatService; - this.kataService = kataService; - } - - private formatLeaderboard(leaderboard: KataLeaderboardUser[]): string { - let output = "```"; - let position = 1; - - const leaderboardTotalEntriesToShow = 10; - const leaderboardEntries = leaderboard.slice(0, leaderboardTotalEntriesToShow); - const leaderboardEntriesLeft = leaderboard.length - leaderboardTotalEntriesToShow; - - leaderboardEntries.forEach((user: KataLeaderboardUser) => { - const pointsCollection = user.getPoints().map((points: number) => points || 0); - - output += `${position}. ${user.getUsername()} - ${user.getScore()} - [${pointsCollection.join(",")}] points -`; - - position += 1; - }); - - output += "```"; - - if (leaderboardEntriesLeft > 1) { - output += ` -... e ${leaderboardEntriesLeft} outras participações em https://codewars.devpt.co`; - } else if (leaderboardEntriesLeft === 1) { - output += ` -... e 1 outra participação em https://codewars.devpt.co`; - } - - return output; - } - - async execute({ channelId }: SendCodewarsLeaderboardToChannelInput): Promise { - const leaderboard = await this.kataService.getLeaderboard(); - - if (leaderboard.length === 0) { - this.chatService.sendMessageToChannel("Ainda não existem participantes nesta ediçăo do desafio.", channelId); - return; - } - - const formattedLeaderboard = this.formatLeaderboard(leaderboard); - this.chatService.sendMessageToChannel(formattedLeaderboard, channelId); - } -} diff --git a/vitest/application/usecases/sendCodewarsLeaderboardToChannelUseCase.spec.ts b/vitest/application/command/codewarsLeaderboardCommand.spec.ts similarity index 87% rename from vitest/application/usecases/sendCodewarsLeaderboardToChannelUseCase.spec.ts rename to vitest/application/command/codewarsLeaderboardCommand.spec.ts index 74a8355..1949126 100644 --- a/vitest/application/usecases/sendCodewarsLeaderboardToChannelUseCase.spec.ts +++ b/vitest/application/command/codewarsLeaderboardCommand.spec.ts @@ -1,6 +1,6 @@ import { vi, describe, it, expect, beforeEach } from "vitest"; import { MockProxy, mock } from "vitest-mock-extended"; -import SendCodewarsLeaderboardToChannelUseCase from "../../../application/usecases/sendCodewarsLeaderboardToChannel/sendCodewarsLeaderboardToChannelUseCase"; +import CodewarsLeaderboardCommand from "../../../application/command/codewarsLeaderboardCommand"; import ChatService from "../../../domain/service/chatService"; import KataService from "../../../domain/service/kataService/kataService"; import KataLeaderboardUser from "../../../domain/service/kataService/kataLeaderboardUser"; @@ -22,10 +22,7 @@ describe("send codewars leaderboard to channel use case", () => { const spy = vi.spyOn(mockChatService, "sendMessageToChannel"); - await new SendCodewarsLeaderboardToChannelUseCase({ - chatService: mockChatService, - kataService: mockKataService, - }).execute({ + await new CodewarsLeaderboardCommand(mockChatService, mockKataService).execute({ channelId: "855861944930402342", }); @@ -53,10 +50,7 @@ describe("send codewars leaderboard to channel use case", () => { const spy = vi.spyOn(mockChatService, "sendMessageToChannel"); - await new SendCodewarsLeaderboardToChannelUseCase({ - chatService: mockChatService, - kataService: mockKataService, - }).execute({ + await new CodewarsLeaderboardCommand(mockChatService, mockKataService).execute({ channelId: "855861944930402342", }); @@ -90,10 +84,7 @@ describe("send codewars leaderboard to channel use case", () => { const spy = vi.spyOn(mockChatService, "sendMessageToChannel"); - await new SendCodewarsLeaderboardToChannelUseCase({ - chatService: mockChatService, - kataService: mockKataService, - }).execute({ + await new CodewarsLeaderboardCommand(mockChatService, mockKataService).execute({ channelId: "855861944930402342", }); From c522a1857be30ef151f4d825b824f8a448acbfa1 Mon Sep 17 00:00:00 2001 From: diomonogatari Date: Thu, 7 Nov 2024 21:47:49 +0000 Subject: [PATCH 6/6] refactor: truly decoupled `commandUseCaseResolver` --- domain/service/commandUseCaseResolver.ts | 23 +++--------------- index.ts | 14 +++++++---- vitest/service/commandUseCaseResolver.spec.ts | 24 +++++++++++++------ 3 files changed, 30 insertions(+), 31 deletions(-) diff --git a/domain/service/commandUseCaseResolver.ts b/domain/service/commandUseCaseResolver.ts index ab1e8d1..403d454 100644 --- a/domain/service/commandUseCaseResolver.ts +++ b/domain/service/commandUseCaseResolver.ts @@ -1,33 +1,16 @@ -import CodewarsLeaderboardCommand from "../../application/command/codewarsLeaderboardCommand"; -import DontAskToAskCommand from "../../application/command/dontAskToAskCommand"; -import OnlyCodeQuestionsCommand from "../../application/command/onlyCodeQuestionsCommand"; import { Command, Context } from "../../types"; import UseCaseNotFound from "../exception/useCaseNotFound"; -import ChatService from "./chatService"; -import KataService from "./kataService/kataService"; import LoggerService from "./loggerService"; export default class CommandUseCaseResolver { - private commands: Command[] = []; + private commands: Command[]; private loggerService: LoggerService; - constructor({ - chatService, - kataService, - loggerService, - }: { - chatService: ChatService; - kataService: KataService; - loggerService: LoggerService; - }) { + constructor({ commands, loggerService }: { commands: Command[]; loggerService: LoggerService }) { this.loggerService = loggerService; - this.commands.push( - new CodewarsLeaderboardCommand(chatService, kataService), - new DontAskToAskCommand(chatService), - new OnlyCodeQuestionsCommand(chatService) - ); + this.commands = commands; } async resolveByCommand(command: string, context: Context): Promise { diff --git a/index.ts b/index.ts index ba97632..0c0f878 100644 --- a/index.ts +++ b/index.ts @@ -14,6 +14,10 @@ import KataService from "./domain/service/kataService/kataService"; import CodewarsKataService from "./infrastructure/service/codewarsKataService"; import ContentAggregatorService from "./domain/service/contentAggregatorService/contentAggregatorService"; import LemmyContentAggregatorService from "./infrastructure/service/lemmyContentAggregatorService"; +import CodewarsLeaderboardCommand from "./application/command/codewarsLeaderboardCommand"; +import DontAskToAskCommand from "./application/command/dontAskToAskCommand"; +import OnlyCodeQuestionsCommand from "./application/command/onlyCodeQuestionsCommand"; +import { Command } from "./types"; dotenv.config(); @@ -29,12 +33,14 @@ const loggerService: LoggerService = new ConsoleLoggerService(); const channelResolver: ChannelResolver = new ChannelResolver(); const kataService: KataService = new CodewarsKataService(); const lemmyContentAggregatorService: ContentAggregatorService = new LemmyContentAggregatorService(); +const commands: Command[] = [ + new CodewarsLeaderboardCommand(chatService, kataService), + new DontAskToAskCommand(chatService), + new OnlyCodeQuestionsCommand(chatService), +]; const useCaseResolver = new CommandUseCaseResolver({ - messageRepository, - chatService, + commands, loggerService, - channelResolver, - kataService, }); const checkForNewPosts = async () => { diff --git a/vitest/service/commandUseCaseResolver.spec.ts b/vitest/service/commandUseCaseResolver.spec.ts index 913eb7c..c0197c1 100644 --- a/vitest/service/commandUseCaseResolver.spec.ts +++ b/vitest/service/commandUseCaseResolver.spec.ts @@ -1,6 +1,6 @@ import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; import CommandUseCaseResolver from "../../domain/service/commandUseCaseResolver"; -import { Context } from "../../types"; +import { Command, Context } from "../../types"; describe("CommandUseCaseResolver", () => { let commandUseCaseResolver: CommandUseCaseResolver; @@ -8,15 +8,25 @@ describe("CommandUseCaseResolver", () => { channelId: "test-channel", }; + const mockCommandJa: Command = { + name: "!ja", + execute: vi.fn(), + }; + + const mockCommandOc: Command = { + name: "!oc", + execute: vi.fn(), + }; + + const mockCommandCwl: Command = { + name: "!cwl", + execute: vi.fn(), + }; + beforeEach(async () => { commandUseCaseResolver = new CommandUseCaseResolver({ - chatService: { - sendMessageToChannel: vi.fn(), - }, + commands: [mockCommandJa, mockCommandCwl, mockCommandOc], loggerService: { log: vi.fn() }, - kataService: { - getLeaderboard: vi.fn().mockResolvedValue([]), - }, }); });