From d16c8b093aa3d5f1be169b620a031973d6b83141 Mon Sep 17 00:00:00 2001 From: Steve Freeman Date: Thu, 7 May 2026 15:53:54 -0400 Subject: [PATCH] Fixed unexpected issue with suppressions when no text is provided --- .../services/suppressor.service.spec.ts | 91 +++++++++++++++++-- .../src/shared/services/suppressor.service.ts | 12 ++- 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/packages/backend/src/shared/services/suppressor.service.spec.ts b/packages/backend/src/shared/services/suppressor.service.spec.ts index d3cc2dc8..b9ebfd11 100644 --- a/packages/backend/src/shared/services/suppressor.service.spec.ts +++ b/packages/backend/src/shared/services/suppressor.service.spec.ts @@ -135,9 +135,11 @@ describe('SuppressorService', () => { ); }); - it('sendSuppressedMessage uses corpo mode for libworkchat', async () => { + it('sendSuppressedMessage falls back when translation fails', async () => { + (suppressorService.translationService.translate as Mock).mockRejectedValue(new Error('translate fail')); + await suppressorService.sendSuppressedMessage( - '#libworkchat', + 'C123', 'U1', 'hello world', '123', @@ -145,14 +147,88 @@ describe('SuppressorService', () => { suppressorService.muzzlePersistenceService as never, ); + expect(suppressorService.webService.sendMessage).toHaveBeenCalled(); + }); + + it('sendSuppressedMessage returns early when text is undefined', async () => { + await suppressorService.sendSuppressedMessage( + 'C123', + 'U1', + undefined, + '123', + 1, + suppressorService.muzzlePersistenceService as never, + ); + + expect(suppressorService.translationService.translate).not.toHaveBeenCalled(); + expect(suppressorService.webService.sendMessage).not.toHaveBeenCalled(); + }); + + it('sendSuppressedMessage uses corpo mode for C023B688SLT with more than 10 words', async () => { + const longEnoughText = new Array(11).fill('word').join(' '); + await suppressorService.sendSuppressedMessage( + 'C023B688SLT', + 'U1', + longEnoughText, + '123', + 1, + suppressorService.muzzlePersistenceService as never, + ); + expect(suppressorService.aiService.generateCorpoSpeak).toHaveBeenCalled(); + expect(suppressorService.translationService.translate).not.toHaveBeenCalled(); }); - it('sendSuppressedMessage falls back when translation fails', async () => { - (suppressorService.translationService.translate as Mock).mockRejectedValue(new Error('translate fail')); + it('sendSuppressedMessage does not use corpo for C023B688SLT with 10 or fewer words', async () => { + const shortText = new Array(10).fill('word').join(' '); + await suppressorService.sendSuppressedMessage( + 'C023B688SLT', + 'U1', + shortText, + '123', + 1, + suppressorService.muzzlePersistenceService as never, + ); + expect(suppressorService.translationService.translate).toHaveBeenCalled(); + expect(suppressorService.aiService.generateCorpoSpeak).not.toHaveBeenCalled(); + }); + + it('sendSuppressedMessage uses corpo mode for #libworkchat with more than 10 words', async () => { + const longEnoughText = new Array(11).fill('word').join(' '); await suppressorService.sendSuppressedMessage( - 'C123', + '#libworkchat', + 'U1', + longEnoughText, + '123', + 1, + suppressorService.muzzlePersistenceService as never, + ); + + expect(suppressorService.aiService.generateCorpoSpeak).toHaveBeenCalled(); + expect(suppressorService.translationService.translate).not.toHaveBeenCalled(); + }); + + it('sendSuppressedMessage does not use corpo for #libworkchat with 10 or fewer words', async () => { + const shortText = new Array(10).fill('word').join(' '); + await suppressorService.sendSuppressedMessage( + '#libworkchat', + 'U1', + shortText, + '123', + 1, + suppressorService.muzzlePersistenceService as never, + ); + + expect(suppressorService.translationService.translate).toHaveBeenCalled(); + expect(suppressorService.aiService.generateCorpoSpeak).not.toHaveBeenCalled(); + }); + + it('sendSuppressedMessage falls back when corpo speak fails', async () => { + (suppressorService.aiService.generateCorpoSpeak as Mock).mockRejectedValue(new Error('ai fail')); + + await suppressorService.sendSuppressedMessage( + '#libworkchat', 'U1', 'hello world', '123', @@ -160,7 +236,10 @@ describe('SuppressorService', () => { suppressorService.muzzlePersistenceService as never, ); - expect(suppressorService.webService.sendMessage).toHaveBeenCalled(); + expect(suppressorService.webService.sendMessage).toHaveBeenCalledWith( + '#libworkchat', + expect.stringContaining('<@U1> says'), + ); }); it('sendSuppressedMessage skips when too many words', async () => { diff --git a/packages/backend/src/shared/services/suppressor.service.ts b/packages/backend/src/shared/services/suppressor.service.ts index c757a95e..49e64fb1 100644 --- a/packages/backend/src/shared/services/suppressor.service.ts +++ b/packages/backend/src/shared/services/suppressor.service.ts @@ -206,16 +206,20 @@ export class SuppressorService { public async sendSuppressedMessage( channel: string, userId: string, - text: string, + text: string | undefined, timestamp: string, dbId: number, persistenceService: MuzzlePersistenceService | BackFirePersistenceService | CounterPersistenceService, ): Promise { await this.webService.deleteMessage(channel, timestamp, userId); - const words = text.split(' '); + if (!text) { + return; + } + + const words: string[] | undefined = text.split(' '); - const shouldMuzzle = words.length > 0 && words.length <= 250; + const shouldMuzzle = words.length <= 250; if (shouldMuzzle) { const textWithFallbackReplacments = words @@ -224,7 +228,7 @@ export class SuppressorService { ) .join(' '); - const shouldCorpo = channel === '#libworkchat' || (channel === 'C023B688SLT' && words.length > 10); + const shouldCorpo = (channel === '#libworkchat' || channel === 'C023B688SLT') && words.length > 10; if (shouldCorpo) { await this.aiService