@@ -36,6 +36,7 @@ public class VirusScanValidationTest {
3636
3737 private static final String BUCKET_NAME = System .getenv ("VALIDATION_BUCKET" );
3838 private static final String INFECTED_KEY = "eicar.txt" ;
39+ private static final String CLEAN_KEY = "NotInfectedFile.pdf" ;
3940 private static final String OVERSIZED_KEY = "large-test-file.zip" ;
4041
4142 private static final S3Client s3 = S3Client .create ();
@@ -48,6 +49,7 @@ static void checkSetup() {
4849
4950 // Ensure all tags are cleared before testing starts
5051 clearTags (INFECTED_KEY );
52+ clearTags (CLEAN_KEY );
5153 clearTags (OVERSIZED_KEY );
5254
5355 //throw new AssertionError("Testing rollback");
@@ -83,20 +85,35 @@ public void validateScanOfKnownInfectedFile() throws InterruptedException {
8385 retriggerScan (INFECTED_KEY );
8486 }
8587 // Need to wait for scan to complete, which sometimes can take over a minute
86- waitForTagValue (INFECTED_KEY , ScanStatus .INFECTED , Duration .ofMinutes (2 ));
88+ waitForTerminalTagValue (INFECTED_KEY , ScanStatus .INFECTED , Duration .ofMinutes (2 ));
8789 }
8890
8991 /**
90- * When file to scan is over MAX_BYTES, then ensure it is not scanned and immediately tagged FILE_SIZE_EXCEEDED.
92+ * Validate a known clean file tests to be CLEAN. This catches false positives where ClamAV
93+ * fails to start but the pipeline still reports files as infected.
9194 *
9295 * @throws InterruptedException
9396 */
9497 @ Test
9598 @ Order (3 )
99+ public void validateScanOfKnownCleanFile () throws InterruptedException {
100+ assumeTrue (!ONLY_TAG_INFECTED , "Skipping because clean validation requires ONLY_TAG_INFECTED=false" );
101+
102+ retriggerScan (CLEAN_KEY );
103+ waitForTerminalTagValue (CLEAN_KEY , ScanStatus .CLEAN , Duration .ofMinutes (2 ));
104+ }
105+
106+ /**
107+ * When file to scan is over MAX_BYTES, then ensure it is not scanned and immediately tagged FILE_SIZE_EXCEEDED.
108+ *
109+ * @throws InterruptedException
110+ */
111+ @ Test
112+ @ Order (4 )
96113 public void validateScanOfOversizedFile () throws InterruptedException {
97114 retriggerScan (OVERSIZED_KEY );
98115 // Should be detected before scanning on the S3 Head operation
99- waitForTagValue (OVERSIZED_KEY , ScanStatus .FILE_SIZE_EXCEEED , Duration .ofSeconds (30 ));
116+ waitForTerminalTagValue (OVERSIZED_KEY , ScanStatus .FILE_SIZE_EXCEEED , Duration .ofSeconds (30 ));
100117 }
101118
102119 /**
@@ -141,6 +158,37 @@ private static void waitForTagValue(String key, ScanStatus expectedValue, Durati
141158 throw new AssertionError ("Timed out waiting for scan-status tag: " + expectedValue + " on key: " + key );
142159 }
143160
161+ private static void waitForTerminalTagValue (String key , ScanStatus expectedValue , Duration timeout ) throws InterruptedException {
162+ long timeoutMillis = timeout .toMillis ();
163+ long sleepMillis = 5000 ;
164+ long start = System .currentTimeMillis ();
165+
166+ while (System .currentTimeMillis () - start <= timeoutMillis ) {
167+ List <Tag > tags = getTags (key );
168+ String actual = tags .stream ()
169+ .filter (tag -> SCAN_TAG_NAME .equals (tag .key ()))
170+ .map (Tag ::value )
171+ .findFirst ()
172+ .orElse (null );
173+
174+ if (expectedValue .name ().equals (actual )) {
175+ assertThat (actual ).isEqualTo (expectedValue .name ());
176+ return ;
177+ }
178+
179+ if (actual != null ) {
180+ ScanStatus actualStatus = ScanStatus .valueOf (actual );
181+ if (actualStatus != ScanStatus .SCANNING ) {
182+ throw new AssertionError ("Expected scan-status " + expectedValue + " but found " + actualStatus + " on key: " + key );
183+ }
184+ }
185+
186+ Thread .sleep (sleepMillis );
187+ }
188+
189+ throw new AssertionError ("Timed out waiting for scan-status tag: " + expectedValue + " on key: " + key );
190+ }
191+
144192 /**
145193 * Get all tags on S3 Object.
146194 *
0 commit comments