11package org .togetherjava .tjbot .features .moderation .scam ;
22
3+ import net .dv8tion .jda .api .entities .Message ;
34import org .junit .jupiter .api .BeforeEach ;
45import org .junit .jupiter .api .DisplayName ;
56import org .junit .jupiter .api .Test ;
910import org .togetherjava .tjbot .config .Config ;
1011import org .togetherjava .tjbot .config .ScamBlockerConfig ;
1112
13+ import java .util .ArrayList ;
14+ import java .util .Collections ;
1215import java .util .List ;
1316import java .util .Set ;
1417
1821import static org .mockito .Mockito .when ;
1922
2023final class ScamDetectorTest {
24+ private static final int SUSPICIOUS_ATTACHMENTS_THRESHOLD = 3 ;
25+ private static final String SUSPICIOUS_ATTACHMENT_NAME = "scam.png" ;
26+
2127 private ScamDetector scamDetector ;
2228
2329 @ BeforeEach
@@ -38,6 +44,10 @@ void setUp() {
3844 when (scamConfig .getSuspiciousHostKeywords ())
3945 .thenReturn (Set .of ("discord" , "nitro" , "premium" , "free" , "cheat" , "crypto" , "tele" ));
4046 when (scamConfig .getIsHostSimilarToKeywordDistanceThreshold ()).thenReturn (2 );
47+ when (scamConfig .getSuspiciousAttachmentsThreshold ())
48+ .thenReturn (SUSPICIOUS_ATTACHMENTS_THRESHOLD );
49+ when (scamConfig .getSuspiciousAttachmentNamePattern ())
50+ .thenReturn (SUSPICIOUS_ATTACHMENT_NAME );
4151
4252 scamDetector = new ScamDetector (config );
4353 }
@@ -121,6 +131,94 @@ void websitesWithTooManyDifferencesAreNotSuspicious() {
121131 assertFalse (isScamResult );
122132 }
123133
134+ @ Test
135+ @ DisplayName ("Messages containing multiple suspicious attachments are flagged as scam" )
136+ void detectsSuspiciousAttachments () {
137+ // GIVEN an empty message containing suspicious attachments
138+ String content = "" ;
139+ Message .Attachment attachment = createImageAttachmentMock (SUSPICIOUS_ATTACHMENT_NAME );
140+ List <Message .Attachment > attachments =
141+ Collections .nCopies (SUSPICIOUS_ATTACHMENTS_THRESHOLD , attachment );
142+ Message message = createMessageMock (content , attachments );
143+
144+ // WHEN analyzing it
145+ boolean isScamResult = scamDetector .isScam (message );
146+
147+ // THEN flags it as scam
148+ assertTrue (isScamResult );
149+ }
150+
151+ @ Test
152+ @ DisplayName ("Messages containing text content are not flagged for suspicious attachments" )
153+ void ignoresAttachmentsIfContentProvided () {
154+ // GIVEN a non-empty message containing suspicious attachments
155+ String content = "Hello World" ;
156+ Message .Attachment attachment = createImageAttachmentMock (SUSPICIOUS_ATTACHMENT_NAME );
157+ List <Message .Attachment > attachments =
158+ Collections .nCopies (SUSPICIOUS_ATTACHMENTS_THRESHOLD , attachment );
159+ Message message = createMessageMock (content , attachments );
160+
161+ // WHEN analyzing it
162+ boolean isScamResult = scamDetector .isScam (message );
163+
164+ // THEN flags it as harmless
165+ assertFalse (isScamResult );
166+ }
167+
168+ @ Test
169+ @ DisplayName ("Messages containing not enough suspicious attachments are not flagged" )
170+ void ignoresIfNotEnoughSuspiciousAttachments () {
171+ // GIVEN an empty message containing some, but not enough suspicious attachments
172+ String content = "" ;
173+
174+ Message .Attachment badAttachment = createImageAttachmentMock (SUSPICIOUS_ATTACHMENT_NAME );
175+ Message .Attachment goodAttachment = createImageAttachmentMock ("good.png" );
176+ int badAttachmentAmount = SUSPICIOUS_ATTACHMENTS_THRESHOLD - 1 ;
177+ List <Message .Attachment > attachments =
178+ new ArrayList <>(Collections .nCopies (badAttachmentAmount , badAttachment ));
179+ attachments .add (goodAttachment );
180+
181+ Message message = createMessageMock (content , attachments );
182+
183+ // WHEN analyzing it
184+ boolean isScamResult = scamDetector .isScam (message );
185+
186+ // THEN flags it as harmless
187+ assertFalse (isScamResult );
188+ }
189+
190+ @ Test
191+ @ DisplayName ("Messages containing harmless attachments are not flagged" )
192+ void ignoresHarmlessAttachments () {
193+ // GIVEN an empty message containing only harmless attachments
194+ String content = "" ;
195+ Message .Attachment attachment = createImageAttachmentMock ("good.png" );
196+ List <Message .Attachment > attachments =
197+ Collections .nCopies (SUSPICIOUS_ATTACHMENTS_THRESHOLD , attachment );
198+ Message message = createMessageMock (content , attachments );
199+
200+ // WHEN analyzing it
201+ boolean isScamResult = scamDetector .isScam (message );
202+
203+ // THEN flags it as harmless
204+ assertFalse (isScamResult );
205+ }
206+
207+ private static Message createMessageMock (String content , List <Message .Attachment > attachments ) {
208+ Message message = mock (Message .class );
209+ when (message .getContentRaw ()).thenReturn (content );
210+ when (message .getContentDisplay ()).thenReturn (content );
211+ when (message .getAttachments ()).thenReturn (attachments );
212+ return message ;
213+ }
214+
215+ private static Message .Attachment createImageAttachmentMock (String name ) {
216+ Message .Attachment attachment = mock (Message .Attachment .class );
217+ when (attachment .isImage ()).thenReturn (true );
218+ when (attachment .getFileName ()).thenReturn (name );
219+ return attachment ;
220+ }
221+
124222 private static List <String > provideRealScamMessages () {
125223 return List .of ("""
126224 🤩bro steam gived nitro - https://nitro-ds.online/LfgUfMzqYyx12""" ,
0 commit comments