diff --git a/src/modules/alerts/__tests__/alert.service.test.ts b/src/modules/alerts/__tests__/alert.service.test.ts index 8bc71a8..f9b8592 100644 --- a/src/modules/alerts/__tests__/alert.service.test.ts +++ b/src/modules/alerts/__tests__/alert.service.test.ts @@ -5,6 +5,7 @@ import { createAlert, listAlerts, deleteAlert } from '../alert.service'; import { prisma } from '../../../utils/prisma.utils'; +import { logger } from '../../../utils/logger.utils'; jest.mock('../../../utils/prisma.utils', () => ({ prisma: { @@ -17,6 +18,10 @@ jest.mock('../../../utils/prisma.utils', () => ({ }, })); +jest.mock('../../../utils/logger.utils', () => ({ + logger: { info: jest.fn() }, +})); + const mockedPrisma = prisma as jest.Mocked; const VALID_ADDRESS = 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; @@ -59,6 +64,19 @@ describe('createAlert', () => { }, }); expect(result).toEqual(DB_ALERT); + + expect(logger.info).toHaveBeenCalledWith( + expect.objectContaining({ + alert_id: DB_ALERT.id, + creator_id: DB_ALERT.creatorId, + direction: DB_ALERT.direction, + target_price: DB_ALERT.targetPrice, + registered_at: DB_ALERT.createdAt, + wallet_address: 'GAAA***AAAA', + }), + 'Price alert registered' + ); + expect((logger.info as jest.Mock).mock.calls[0][0]).not.toHaveProperty('callback_url'); }); it('creates a below-direction alert', async () => { @@ -114,6 +132,17 @@ describe('deleteAlert', () => { where: { id: 'alert-1' }, }); expect(result).toEqual({ id: 'alert-1' }); + + expect(logger.info).toHaveBeenCalledWith( + expect.objectContaining({ + alert_id: DB_ALERT.id, + creator_id: DB_ALERT.creatorId, + cancelled_at: expect.any(Date), + wallet_address: 'GAAA***AAAA', + }), + 'Price alert cancelled' + ); + expect((logger.info as jest.Mock).mock.calls[0][0]).not.toHaveProperty('callback_url'); }); it('returns null when the alert is not found', async () => { diff --git a/src/modules/alerts/alert.service.ts b/src/modules/alerts/alert.service.ts index 544df0e..1172f20 100644 --- a/src/modules/alerts/alert.service.ts +++ b/src/modules/alerts/alert.service.ts @@ -13,7 +13,7 @@ export type PriceMovement = { * Creates a new price alert for a wallet address watching a creator's key price. */ export async function createAlert(input: CreateAlertInput) { - return await prisma.priceAlert.create({ + const alert = await prisma.priceAlert.create({ data: { creatorId: input.creator_id, walletAddress: input.wallet_address, @@ -22,6 +22,20 @@ export async function createAlert(input: CreateAlertInput) { callbackUrl: input.callback_url, }, }); + + logger.info( + { + alert_id: alert.id, + creator_id: alert.creatorId, + direction: alert.direction, + target_price: toNumber(alert.targetPrice), + registered_at: alert.createdAt, + wallet_address: maskWalletAddress(alert.walletAddress), + }, + 'Price alert registered' + ); + + return alert; } /** @@ -51,6 +65,17 @@ export async function deleteAlert( } await prisma.priceAlert.delete({ where: { id } }); + + logger.info( + { + alert_id: existing.id, + creator_id: existing.creatorId, + cancelled_at: new Date(), + wallet_address: maskWalletAddress(existing.walletAddress), + }, + 'Price alert cancelled' + ); + return { id }; } @@ -58,6 +83,11 @@ function toNumber(value: number | string | { toString(): string }): number { return typeof value === 'number' ? value : Number(value.toString()); } +function maskWalletAddress(address: string): string { + if (address.length <= 8) return address; + return `${address.slice(0, 4)}***${address.slice(-4)}`; +} + function maskCallbackUrl(callbackUrl: string): string { try { const url = new URL(callbackUrl);