From 27c1f7d5358653ab37798b2da1cec5754155a53d Mon Sep 17 00:00:00 2001 From: jahabeebs <47253537+jahabeebs@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:07:28 -0500 Subject: [PATCH] feat: better notification logic --- apps/agent/src/index.ts | 3 +- .../src/exceptions/errorHandler.ts | 8 +- .../src/interfaces/notificationService.ts | 43 ++- .../src/services/discordNotifier.ts | 130 +++++--- .../src/services/eboProcessor.ts | 138 +++++--- .../tests/mocks/eboProcessor.mocks.ts | 11 +- .../tests/services/discordNotifier.spec.ts | 313 +++++++++++------- .../tests/services/eboProcessor.spec.ts | 10 +- 8 files changed, 443 insertions(+), 213 deletions(-) diff --git a/apps/agent/src/index.ts b/apps/agent/src/index.ts index a6daa7cf..f1fcea6a 100644 --- a/apps/agent/src/index.ts +++ b/apps/agent/src/index.ts @@ -40,10 +40,11 @@ const main = async (): Promise => { logger.debug("Protocol provider initialized."); logger.debug("Initializing notifier..."); - logger.debug(`Discord webhook ${config.DISCORD_WEBHOOK}`); const notifier = new DiscordNotifier(config.DISCORD_WEBHOOK); + logger.debug("Notifier initialized..."); + const actorsManager = new EboActorsManager(); logger.debug("Initializing EBO processor..."); diff --git a/packages/automated-dispute/src/exceptions/errorHandler.ts b/packages/automated-dispute/src/exceptions/errorHandler.ts index 532f7144..dfb7169a 100644 --- a/packages/automated-dispute/src/exceptions/errorHandler.ts +++ b/packages/automated-dispute/src/exceptions/errorHandler.ts @@ -24,7 +24,13 @@ export class ErrorHandler { this.logger.error(`Error executing custom action: ${actionError}`); } finally { if (strategy.shouldNotify) { - await this.notificationService.notifyError(error, context); + const errorMessage = this.notificationService.createErrorMessage( + error, + context, + "An error occurred in the custom contract", + ); + + await this.notificationService.send(errorMessage); } if (strategy.shouldReenqueue && context.reenqueueEvent) { diff --git a/packages/automated-dispute/src/interfaces/notificationService.ts b/packages/automated-dispute/src/interfaces/notificationService.ts index dcab5610..6b2f41b7 100644 --- a/packages/automated-dispute/src/interfaces/notificationService.ts +++ b/packages/automated-dispute/src/interfaces/notificationService.ts @@ -1,13 +1,52 @@ +/** + * Represents a notification message. + */ export interface IMessage { + /** + * The main content of the message. + */ title: string; + /** + * An optional subtitle for the message. + */ subtitle?: string; + /** + * An optional description providing more details. + */ description?: string; + /** + * The username to display as the sender. + */ username?: string; + /** + * The URL of the avatar image to display. + */ avatarUrl?: string; + /** + * An optional URL associated with the message. + */ actionUrl?: string; } +/** + * Interface representing a notification service capable of sending notifications. + */ export interface NotificationService { - // send(message: IMessage): Promise; - notifyError(err: Error, context: unknown): Promise; + /** + * Sends a notification message. + * + * @param {IMessage} message - The message to send. + * @returns {Promise} A promise that resolves when the message is sent. + */ + send(message: IMessage): Promise; + + /** + * Creates an IMessage from an error. + * + * @param err - The error object of type unknown. + * @param context - Additional context for the error. + * @param defaultMessage - A default message describing the error context. + * @returns An IMessage object ready to be sent via the notifier. + */ + createErrorMessage(err: unknown, context: unknown, defaultMessage: string): IMessage; } diff --git a/packages/automated-dispute/src/services/discordNotifier.ts b/packages/automated-dispute/src/services/discordNotifier.ts index 31f2441f..9511b9e1 100644 --- a/packages/automated-dispute/src/services/discordNotifier.ts +++ b/packages/automated-dispute/src/services/discordNotifier.ts @@ -1,6 +1,6 @@ +import { isNativeError } from "util/types"; import type { APIEmbed, JSONEncodable } from "discord.js"; import { WebhookClient, WebhookMessageCreateOptions } from "discord.js"; -import { stringify } from "viem"; import { NotificationFailureException } from "../exceptions/index.js"; import { IMessage, NotificationService } from "../interfaces/index.js"; @@ -10,82 +10,108 @@ export type WebhookMessage = WebhookMessageCreateOptions & { }; /** - * TODO: Refactor me, this was a quick and dirty implementation - * - * `notifiyError` and `formatErrorMessage` should not exist. - * `send` should be the method to use across the codebase. (added as comment here and in the interface) - * If there is a new Notifier service (Ex. TelegramNotifier) it should implement the same interface (NotificationService). + * A notifier class for sending notifications to Discord via webhooks. */ export class DiscordNotifier implements NotificationService { private client: WebhookClient; + /** + * Creates an instance of DiscordNotifier. + * + * @param {string} url - The Discord webhook URL. + */ constructor(url: string) { this.client = new WebhookClient({ url }); } - async notifyError(err: Error, context: unknown): Promise { - const message = this.formatErrorMessage(err, context); - - const webhookMessage = this.buildWebhookMessage({ title: message }); + /** + * Sends a notification message to Discord. + * + * @param {IMessage} message - The message to send. + * @returns {Promise} A promise that resolves when the message is sent. + */ + async send(message: IMessage): Promise { + const webhookMessage = this.buildWebhookMessage(message); try { await this.client.send(webhookMessage); + console.error(this.client, this.client); } catch (error) { - throw new NotificationFailureException("An error occured with Discord client."); + throw new NotificationFailureException("An error occurred with the Discord client."); } } /** - * Formats the error message to be sent to Discord. - * @param {Error} error - The error object. - * @param {any} context - Additional context information. - * @returns {string} The formatted error message. - */ - private formatErrorMessage(error: Error, context: unknown): string { - return `**Error:** ${error.name} - ${error.message}\n**Context:**\n\`\`\`json\n${stringify( - context, - null, - 2, - )}\n\`\`\``; - } - - // /** - // * Sends a webhook message to Discord. - // * @param {WebhookMessage} message - The message to send. - // * @returns A promise that resolves when the message is sent. - // */ - // async send(message: IMessage) { - // const webhookMessage = this.buildWebhookMessage(message); - // try { - // await this.client.send(webhookMessage); - // } catch (error) { - // throw new NotificationFailureException("An error occured with Discord client."); - // } - // } - - /** - * Builds a Discord webhookMessage for an arbitrage opportunity. - * @param {ArbitrageOpportunity} data - The arbitrage data. + * Builds a Discord webhook message from the given IMessage. + * + * @param {IMessage} message - The message details. * @returns {WebhookMessage} The built Discord webhook message. */ - buildWebhookMessage(message: IMessage): WebhookMessage { + private buildWebhookMessage(message: IMessage): WebhookMessage { return { username: message.username || "EBO Agent", content: message.title, - avatarURL: - message.avatarUrl || "https://i.ibb.co/x3HV1Tj/The-Graph-GRT-Symbol-Color.png", // TODO: change to a better image source - // embeds: [this.buildWebhookMessageEmbed(message)], + avatarURL: message.avatarUrl || "https://cryptologos.cc/logos/the-graph-grt-logo.png", + embeds: + message.subtitle || message.description + ? [this.buildWebhookMessageEmbed(message)] + : undefined, } as WebhookMessage; } - buildWebhookMessageEmbed(message: IMessage) { - const title = message.subtitle; - const description = message.description; - + /** + * Builds a Discord embed message from the given IMessage. + * + * @param {IMessage} message - The message details. + * @returns {APIEmbed | JSONEncodable} The built Discord embed message. + */ + private buildWebhookMessageEmbed(message: IMessage): APIEmbed | JSONEncodable { return { - title, - description, + title: message.subtitle, + description: message.description, color: 2326507, url: message.actionUrl, - } as APIEmbed | JSONEncodable; + }; + } + + /** + * Creates an IMessage from an error. + * + * @param err - The error object of type unknown. + * @param context - Additional context for the error. + * @param defaultMessage - A default message describing the error context. + * @returns An IMessage object ready to be sent via the notifier. + */ + public createErrorMessage( + err: unknown, + context: Record, + defaultMessage: string, + ): IMessage { + if (isNativeError(err)) { + return { + title: `**Error:** ${err.name} - ${err.message}`, + description: `**Context:**\n\`\`\`json\n${JSON.stringify( + { + message: defaultMessage, + stack: err.stack, + ...context, + }, + null, + 2, + )}\n\`\`\``, + }; + } else { + return { + title: `**Error:** Unknown error`, + description: `**Context:**\n\`\`\`json\n${JSON.stringify( + { + message: defaultMessage, + error: String(err), + ...context, + }, + null, + 2, + )}\n\`\`\``, + }; + } } } diff --git a/packages/automated-dispute/src/services/eboProcessor.ts b/packages/automated-dispute/src/services/eboProcessor.ts index 9103ec6b..feb2fd23 100644 --- a/packages/automated-dispute/src/services/eboProcessor.ts +++ b/packages/automated-dispute/src/services/eboProcessor.ts @@ -62,9 +62,13 @@ export class EboProcessor { } catch (err) { this.logger.error(`Unhandled error during the event loop: ${err}`); - await this.notifier.notifyError(err as Error, { - message: "Unhandled error during the event loop", - }); + const errorMessage = this.notifier.createErrorMessage( + err, + { message: "Unhandled error during the event loop" }, + "Unhandled error during the event loop", + ); + + await this.notifier.send(errorMessage); clearInterval(this.eventsInterval); @@ -141,7 +145,11 @@ export class EboProcessor { await this.syncRequest(requestId, events, currentEpoch.number, lastBlock); } catch (err) { - this.onActorError(requestId, err as Error); + if (err instanceof Error) { + this.onActorError(requestId, err); + } else { + this.onActorError(requestId, new Error(String(err))); + } } }); @@ -154,15 +162,18 @@ export class EboProcessor { this.lastCheckedBlock = lastBlock.number; } catch (err) { if (isNativeError(err)) { - this.logger.error(`Sync failed: ` + `${err.message}\n\n` + `${err.stack}`); + this.logger.error(`Sync failed: ${err.message}\n\n${err.stack}`); } else { this.logger.error(`Sync failed: ${err}`); } - await this.notifier.notifyError(err as Error, { - message: "Error during synchronization", - stack: isNativeError(err) ? err.stack : undefined, - }); + const errorMessage = this.notifier.createErrorMessage( + err, + { message: "Error during synchronization" }, + "Error during synchronization", + ); + + await this.notifier.send(errorMessage); } } @@ -327,11 +338,17 @@ export class EboProcessor { } else { this.logger.warn(`Chain ${chainId} not supported by the agent. Skipping...`); - this.notifier.notifyError(new Error(`Unsupported chain`), { - message: `Chain ${chainId} not supported by the agent. Skipping...`, - chainId, - requestId, - }); + const errorMessage = this.notifier.createErrorMessage( + new Error("Unsupported chain"), + { + message: `Chain ${chainId} not supported by the agent. Skipping...`, + chainId, + requestId, + }, + `Chain ${chainId} not supported by the agent. Skipping...`, + ); + + this.notifier.send(errorMessage); return null; } @@ -366,10 +383,16 @@ export class EboProcessor { `The request ${requestId} will stop being tracked by the system.`, ); - this.notifier.notifyError(error, { - message: `Actor error for request ${requestId}`, - requestId, - }); + const errorMessage = this.notifier.createErrorMessage( + error, + { + message: `Actor error for request ${requestId}`, + requestId, + }, + `Actor error for request ${requestId}`, + ); + + this.notifier.send(errorMessage); this.terminateActor(requestId); } @@ -436,17 +459,41 @@ export class EboProcessor { } } - this.logger.error( - `Could not create a request for epoch ${epoch} and chain ${chain}.`, - ); - - this.logger.error(err as Error); - - await this.notifier.notifyError(err as Error, { - message: `Could not create a request for epoch ${epoch} and chain ${chain}.`, - epoch, - chain, - }); + if (err instanceof Error) { + this.logger.error( + `Could not create a request for epoch ${epoch} and chain ${chain}.`, + ); + + this.logger.error(err); + + const errorMessage = this.notifier.createErrorMessage( + err, + { + message: `Could not create a request for epoch ${epoch} and chain ${chain}.`, + epoch, + chain, + }, + `Could not create a request for epoch ${epoch} and chain ${chain}.`, + ); + + await this.notifier.send(errorMessage); + } else { + this.logger.error( + `Could not create a request for epoch ${epoch} and chain ${chain}: ${err}`, + ); + + const errorMessage = this.notifier.createErrorMessage( + err, + { + message: `Could not create a request for epoch ${epoch} and chain ${chain}.`, + epoch, + chain, + }, + `Could not create a request for epoch ${epoch} and chain ${chain}.`, + ); + + await this.notifier.send(errorMessage); + } } }); @@ -454,13 +501,19 @@ export class EboProcessor { this.logger.info("Missing requests created."); } catch (err) { - this.logger.error( - `Requests creation missing: ${isNativeError(err) ? err.message : err}`, + if (isNativeError(err)) { + this.logger.error(`Requests creation failed: ${err.message}`); + } else { + this.logger.error(`Requests creation failed: ${err}`); + } + + const errorMessage = this.notifier.createErrorMessage( + err, + { epoch }, + "Error creating missing requests", ); - await this.notifier.notifyError(err as Error, { - message: "Error creating missing requests", - epoch, - }); + + await this.notifier.send(errorMessage); } } @@ -478,10 +531,17 @@ export class EboProcessor { this.logger.info(`Actor handling request ${requestId} was terminated.`); } else { this.logger.warn(alreadyDeletedActorWarning(requestId)); - this.notifier.notifyError(new Error(`Actor already deleted`), { - message: `Actor handling request ${requestId} was already terminated.`, - requestId, - }); + + const errorMessage = this.notifier.createErrorMessage( + new Error("Actor already deleted"), + { + message: `Actor handling request ${requestId} was already terminated.`, + requestId, + }, + `Actor handling request ${requestId} was already terminated.`, + ); + + this.notifier.send(errorMessage); } } } diff --git a/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts b/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts index b93ceb53..e2459b49 100644 --- a/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts +++ b/packages/automated-dispute/tests/mocks/eboProcessor.mocks.ts @@ -4,8 +4,7 @@ import { vi } from "vitest"; import { NotificationService } from "../../src/index.js"; import { ProtocolProvider } from "../../src/providers/index.js"; -import { EboProcessor } from "../../src/services"; -import { EboActorsManager } from "../../src/services/index.js"; +import { EboActorsManager, EboProcessor } from "../../src/services/index.js"; import { AccountingModules } from "../../src/types/prophet.js"; import { DEFAULT_MOCKED_PROTOCOL_CONTRACTS, @@ -65,7 +64,13 @@ export function buildEboProcessor( if (!notifier) { notifier = { - notifyError: vi.fn().mockResolvedValue(undefined), + send: vi.fn(), + createErrorMessage: vi.fn((context, defaultMessage) => { + return { + title: defaultMessage, + description: JSON.stringify(context, null, 2), + }; + }), }; } diff --git a/packages/automated-dispute/tests/services/discordNotifier.spec.ts b/packages/automated-dispute/tests/services/discordNotifier.spec.ts index b5983b76..28638640 100644 --- a/packages/automated-dispute/tests/services/discordNotifier.spec.ts +++ b/packages/automated-dispute/tests/services/discordNotifier.spec.ts @@ -1,113 +1,200 @@ -// TODO: Fix tests in accordance to refactor - -// import { ILogger } from "@ebo-agent/shared"; -// import { Client, IntentsBitField } from "discord.js"; -// import { beforeEach, describe, expect, it, vi } from "vitest"; - -// import { DiscordNotifier } from "../../src/services/index.js"; -// import mocks from "../mocks/index.js"; - -// vi.mock("discord.js", async () => { -// const actualDiscord = await vi.importActual("discord.js"); - -// const MockClient = vi.fn(() => ({ -// login: vi.fn().mockResolvedValue(undefined), -// once: vi.fn((event, callback) => { -// if (event === "ready") { -// callback(); -// } -// }), -// channels: { -// fetch: vi.fn().mockResolvedValue({ -// isTextBased: () => true, -// send: vi.fn().mockResolvedValue(undefined), -// }), -// }, -// })); - -// class MockIntentsBitField { -// static Flags = actualDiscord.IntentsBitField.Flags; -// add() { -// return this; -// } -// } - -// return { -// ...actualDiscord, -// Client: MockClient, -// IntentsBitField: MockIntentsBitField, -// }; -// }); - -// describe("DiscordNotifier", () => { -// const mockConfig = { -// discordBotToken: "mock-token", -// discordChannelId: "mock-channel-id", -// }; - -// let notifier: DiscordNotifier; -// const logger: ILogger = mocks.mockLogger(); - -// beforeEach(async () => { -// vi.clearAllMocks(); -// notifier = await DiscordNotifier.create(mockConfig, logger); -// }); - -// it("initializes the Discord client and login", async () => { -// const ClientMock = Client as unknown as vi.Mock; - -// expect(ClientMock).toHaveBeenCalledWith({ -// intents: expect.any(IntentsBitField), -// }); - -// const instance = ClientMock.mock.results[0].value; -// expect(instance.login).toHaveBeenCalledWith("mock-token"); -// expect(instance.once).toHaveBeenCalledWith("ready", expect.any(Function)); -// }); - -// it("sends an error message to the Discord channel", async () => { -// const error = new Error("Test error"); -// const context = { key: "value" }; - -// await notifier.notifyError(error, context); - -// const ClientMock = Client as unknown as vi.Mock; -// const clientInstance = ClientMock.mock.results[0].value; -// const fetchMock = clientInstance.channels.fetch as vi.Mock; - -// expect(fetchMock).toHaveBeenCalledWith("mock-channel-id"); - -// const channel = await fetchMock.mock.results[0].value; -// expect(channel.isTextBased()).toBe(true); - -// const sendMock = channel.send as vi.Mock; -// expect(sendMock).toHaveBeenCalledWith(expect.stringContaining("**Error:**")); -// }); - -// it("logs an error if the channel is not found", async () => { -// const ClientMock = Client as unknown as vi.Mock; -// const clientInstance = ClientMock.mock.results[0].value; -// clientInstance.channels.fetch.mockResolvedValueOnce(null); - -// const error = new Error("Test error"); -// const context = { key: "value" }; - -// await notifier.notifyError(error, context); - -// expect(logger.error).toHaveBeenCalledWith( -// "Failed to send error notification to Discord: Error: Discord channel not found or is not text-based", -// ); -// }); - -// it("formats the error message correctly", () => { -// const error = new Error("Test error message"); -// error.name = "TestError"; -// const context = { key: "value" }; - -// const formattedMessage = (notifier as any).formatErrorMessage(error, context); - -// expect(formattedMessage).toContain("**Error:** TestError - Test error message"); -// expect(formattedMessage).toContain("**Context:**"); -// expect(formattedMessage).toContain(JSON.stringify(context, null, 2)); -// }); -// }); +import { WebhookClient } from "discord.js"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { NotificationFailureException } from "../../src/exceptions/index.js"; +import { DiscordNotifier } from "../../src/index.js"; +import { IMessage } from "../../src/interfaces/index.js"; + +// Change this to `true` to test your Discord webhook URL set below rather than mocking it +const useRealDiscordWebhook = false; +const webhookUrl = "https://discord.com/api/webhooks/KEEP-THIS-SECRET/KEEP-THIS-PART-SECRET-TOO"; + +if (!useRealDiscordWebhook) { + vi.mock("discord.js", async () => { + const actualDiscord = await vi.importActual("discord.js"); + + const mockSend = vi.fn(); + + const MockWebhookClient = vi.fn(() => ({ + send: mockSend, + })); + + return { + ...actualDiscord, + WebhookClient: MockWebhookClient, + }; + }); +} else { + vi.unmock("discord.js"); +} + +describe("DiscordNotifier", () => { + let notifier: DiscordNotifier; + + beforeEach(() => { + if (!useRealDiscordWebhook) { + vi.clearAllMocks(); + } + notifier = new DiscordNotifier(webhookUrl); + }); + + it("initializes the WebhookClient with the correct URL", () => { + if (!useRealDiscordWebhook) { + const WebhookClientMock = WebhookClient as unknown as vi.Mock; + expect(WebhookClientMock).toHaveBeenCalledWith({ url: webhookUrl }); + } + }); + + it("sends a message successfully", async () => { + const message: IMessage = { + title: "Test message", + subtitle: "Test subtitle", + description: "Test description", + username: "TestUserWithoutAvatar", + avatarUrl: "https://example.com/avatar.png", + actionUrl: "https://example.com/action", + }; + + if (useRealDiscordWebhook) { + await expect(notifier.send(message)).resolves.not.toThrow(); + } else { + await notifier.send(message); + + const WebhookClientMock = WebhookClient as unknown as vi.Mock; + const webhookClientInstance = WebhookClientMock.mock.results[0].value; + const sendMock = webhookClientInstance.send as vi.Mock; + + expect(sendMock).toHaveBeenCalledWith( + expect.objectContaining({ + username: "TestUserWithoutAvatar", + content: "Test message", + avatarURL: "https://example.com/avatar.png", + embeds: [ + { + title: "Test subtitle", + description: "Test description", + color: 2326507, + url: "https://cryptologos.cc/logos/the-graph-grt-logo.png", + }, + ], + }), + ); + } + }); + + it("throws NotificationFailureException when send fails", async () => { + if (useRealDiscordWebhook) { + return; + } + + const message: IMessage = { + title: "Test message", + }; + + const WebhookClientMock = WebhookClient as unknown as vi.Mock; + const webhookClientInstance = WebhookClientMock.mock.results[0].value; + const sendMock = webhookClientInstance.send as vi.Mock; + sendMock.mockRejectedValueOnce(new Error("Send failed")); + + await expect(notifier.send(message)).rejects.toThrow(NotificationFailureException); + }); + + it("builds the webhook message correctly without embed when subtitle and description are not provided", async () => { + const message: IMessage = { + title: "Test message", + }; + + if (useRealDiscordWebhook) { + await expect(notifier.send(message)).resolves.not.toThrow(); + } else { + await notifier.send(message); + + const WebhookClientMock = WebhookClient as unknown as vi.Mock; + const webhookClientInstance = WebhookClientMock.mock.results[0].value; + const sendMock = webhookClientInstance.send as vi.Mock; + + expect(sendMock).toHaveBeenCalledWith({ + username: "EBO Agent", + content: "Test message", + avatarURL: "https://cryptologos.cc/logos/the-graph-grt-logo.png", + }); + } + }); + + it("builds the webhook message with embed when subtitle or description are provided", async () => { + const message: IMessage = { + title: "Test message", + subtitle: "Test subtitle", + description: "Test description", + actionUrl: "https://example.com/action", + }; + + if (useRealDiscordWebhook) { + await expect(notifier.send(message)).resolves.not.toThrow(); + } else { + await notifier.send(message); + + const WebhookClientMock = WebhookClient as unknown as vi.Mock; + const webhookClientInstance = WebhookClientMock.mock.results[0].value; + const sendMock = webhookClientInstance.send as vi.Mock; + + expect(sendMock).toHaveBeenCalledWith( + expect.objectContaining({ + username: "EBO Agent", + content: "Test message", + avatarURL: "https://cryptologos.cc/logos/the-graph-grt-logo.png", + embeds: [ + { + title: "Test subtitle", + description: "Test description", + color: 2326507, + url: "https://example.com/action", + }, + ], + }), + ); + } + }); + + it("creates an error message from an Error object", () => { + const error = new Error("Test error"); + const context = { foo: "bar" }; + const defaultMessage = "Default error message"; + + const errorMessage = notifier.createErrorMessage(error, context, defaultMessage); + + expect(errorMessage).toEqual({ + title: `**Error:** ${error.name} - ${error.message}`, + description: `**Context:**\n\`\`\`json\n${JSON.stringify( + { + message: defaultMessage, + stack: error.stack, + ...context, + }, + null, + 2, + )}\n\`\`\``, + }); + }); + + it("creates an error message from a non-Error object", () => { + const error = "String error"; + const context = { foo: "bar" }; + const defaultMessage = "Default error message"; + + const errorMessage = notifier.createErrorMessage(error, context, defaultMessage); + + expect(errorMessage).toEqual({ + title: `**Error:** Unknown error`, + description: `**Context:**\n\`\`\`json\n${JSON.stringify( + { + message: defaultMessage, + error: String(error), + ...context, + }, + null, + 2, + )}\n\`\`\``, + }); + }); +}); diff --git a/packages/automated-dispute/tests/services/eboProcessor.spec.ts b/packages/automated-dispute/tests/services/eboProcessor.spec.ts index a30282ef..d4b9f3f9 100644 --- a/packages/automated-dispute/tests/services/eboProcessor.spec.ts +++ b/packages/automated-dispute/tests/services/eboProcessor.spec.ts @@ -1,4 +1,4 @@ -import { UnixTimestamp } from "@ebo-agent/shared"; +import { Caip2ChainId, UnixTimestamp } from "@ebo-agent/shared"; import { Block, Hex } from "viem"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; @@ -38,7 +38,13 @@ describe("EboProcessor", () => { beforeEach(() => { vi.useFakeTimers(); notifier = { - notifyError: vi.fn().mockResolvedValue(undefined), + send: vi.fn().mockResolvedValue(undefined), + createErrorMessage: vi.fn((context, defaultMessage) => { + return { + title: defaultMessage, + description: JSON.stringify(context, null, 2), + }; + }), }; });