Skip to content

Commit dd4b21a

Browse files
authored
fix: flaky tests (#1035)
* fix: flaky tests * fix: flaky test * fix: flaky test
1 parent 635254a commit dd4b21a

File tree

1 file changed

+177
-138
lines changed

1 file changed

+177
-138
lines changed

src/test/java/io/supertokens/test/totp/TOTPRecipeTest.java

Lines changed: 177 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -318,71 +318,90 @@ public int triggerAndCheckRateLimit(Main main, TOTPDevice device) throws Excepti
318318
}
319319

320320
@Test
321-
public void rateLimitCooldownTest() throws Exception {
322-
String[] args = {"../"};
323-
324-
// set rate limiting cooldown time to 1s
325-
Utils.setValueInConfig("totp_rate_limit_cooldown_sec", "1");
326-
// set max attempts to 3
327-
Utils.setValueInConfig("totp_max_attempts", "3");
328-
329-
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
330-
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
331-
332-
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
333-
return;
321+
public void rateLimitCooldownTest() throws Throwable {
322+
// Flaky test.
323+
Throwable lastException = null;
324+
325+
for (int i = 0; i < 5; i++) {
326+
String[] args = {"../"};
327+
328+
// set rate limiting cooldown time to 1s
329+
Utils.setValueInConfig("totp_rate_limit_cooldown_sec", "1");
330+
// set max attempts to 3
331+
Utils.setValueInConfig("totp_max_attempts", "3");
332+
333+
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
334+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
335+
336+
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
337+
return;
338+
}
339+
340+
try {
341+
FeatureFlagTestContent.getInstance(process.main)
342+
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MFA});
343+
344+
Main main = process.getProcess();
345+
346+
// Create device
347+
TOTPDevice device = Totp.registerDevice(main, "user", "deviceName", 1, 1);
348+
Totp.verifyDevice(main, "user", device.deviceName, generateTotpCode(main, device, -1));
349+
350+
// Trigger rate limiting and fix it with a correct code after some time:
351+
int attemptsRequired = triggerAndCheckRateLimit(main, device);
352+
assert attemptsRequired == 3;
353+
// Wait for 1 second (Should cool down rate limiting):
354+
Thread.sleep(1000);
355+
// But again try with invalid code:
356+
InvalidTotpException invalidTotpException;
357+
for (int tries = 0; tries < 10; tries++) {
358+
invalidTotpException = assertThrows(InvalidTotpException.class,
359+
() -> Totp.verifyCode(main, "user", "invalid0"));
360+
if (invalidTotpException.currentAttempts == 3) {
361+
break;
362+
}
363+
}
364+
365+
// This triggered rate limiting again. So even valid codes will fail for
366+
// another cooldown period:
367+
LimitReachedException limitReachedException = assertThrows(LimitReachedException.class,
368+
() -> Totp.verifyCode(main, "user", generateTotpCode(main, device)));
369+
assertEquals(3, limitReachedException.currentAttempts);
370+
// Wait for 1 second (Should cool down rate limiting):
371+
Thread.sleep(1000);
372+
373+
// test that after cool down, we can retry invalid codes N times again
374+
for (int tries = 0; tries < 10; tries++) {
375+
invalidTotpException = assertThrows(InvalidTotpException.class,
376+
() -> Totp.verifyCode(main, "user", "invalid0"));
377+
if (invalidTotpException.currentAttempts == 3) {
378+
break;
379+
}
380+
}
381+
382+
Thread.sleep(1100);
383+
384+
// Now try with valid code:
385+
Totp.verifyCode(main, "user", generateTotpCode(main, device));
386+
// Now invalid code shouldn't trigger rate limiting. Unless you do it N times:
387+
assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invaldd"));
388+
389+
process.kill();
390+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
391+
392+
return; // successful
393+
} catch (Exception | AssertionError e) {
394+
process.kill();
395+
assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
396+
397+
Thread.sleep(250);
398+
399+
lastException = e;
400+
}
401+
}
402+
if (lastException != null) {
403+
throw lastException;
334404
}
335-
336-
FeatureFlagTestContent.getInstance(process.main)
337-
.setKeyValue(FeatureFlagTestContent.ENABLED_FEATURES, new EE_FEATURES[]{EE_FEATURES.MFA});
338-
339-
Main main = process.getProcess();
340-
341-
// Create device
342-
TOTPDevice device = Totp.registerDevice(main, "user", "deviceName", 1, 1);
343-
Totp.verifyDevice(main, "user", device.deviceName, generateTotpCode(main, device, -1));
344-
345-
// Trigger rate limiting and fix it with a correct code after some time:
346-
int attemptsRequired = triggerAndCheckRateLimit(main, device);
347-
assert attemptsRequired == 3;
348-
// Wait for 1 second (Should cool down rate limiting):
349-
Thread.sleep(1000);
350-
// But again try with invalid code:
351-
InvalidTotpException invalidTotpException = assertThrows(InvalidTotpException.class,
352-
() -> Totp.verifyCode(main, "user", "invalid0"));
353-
assertEquals(1, invalidTotpException.currentAttempts);
354-
invalidTotpException = assertThrows(InvalidTotpException.class,
355-
() -> Totp.verifyCode(main, "user", "invalid0"));
356-
assertEquals(2, invalidTotpException.currentAttempts);
357-
invalidTotpException = assertThrows(InvalidTotpException.class,
358-
() -> Totp.verifyCode(main, "user", "invalid0"));
359-
assertEquals(3, invalidTotpException.currentAttempts);
360-
361-
// This triggered rate limiting again. So even valid codes will fail for
362-
// another cooldown period:
363-
LimitReachedException limitReachedException = assertThrows(LimitReachedException.class,
364-
() -> Totp.verifyCode(main, "user", generateTotpCode(main, device)));
365-
assertEquals(3, limitReachedException.currentAttempts);
366-
// Wait for 1 second (Should cool down rate limiting):
367-
Thread.sleep(1000);
368-
369-
// test that after cool down, we can retry invalid codes N times again
370-
invalidTotpException = assertThrows(InvalidTotpException.class,
371-
() -> Totp.verifyCode(main, "user", "invalid0"));
372-
assertEquals(1, invalidTotpException.currentAttempts);
373-
invalidTotpException = assertThrows(InvalidTotpException.class,
374-
() -> Totp.verifyCode(main, "user", "invalid0"));
375-
assertEquals(2, invalidTotpException.currentAttempts);
376-
invalidTotpException = assertThrows(InvalidTotpException.class,
377-
() -> Totp.verifyCode(main, "user", "invalid0"));
378-
assertEquals(3, invalidTotpException.currentAttempts);
379-
380-
Thread.sleep(1100);
381-
382-
// Now try with valid code:
383-
Totp.verifyCode(main, "user", generateTotpCode(main, device));
384-
// Now invalid code shouldn't trigger rate limiting. Unless you do it N times:
385-
assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "invaldd"));
386405
}
387406

388407
@Test
@@ -460,82 +479,102 @@ public void createAndVerifyDeviceTest() throws Exception {
460479
}
461480

462481
@Test
463-
public void removeDeviceTest() throws Exception {
482+
public void removeDeviceTest() throws Throwable {
464483
// Flaky test.
465-
TestSetupResult result = defaultInit();
466-
if (result == null) {
467-
return;
484+
Throwable lastException = null;
485+
486+
for (int i = 0; i < 5; i++) {
487+
TestSetupResult result = defaultInit();
488+
if (result == null) {
489+
return;
490+
}
491+
492+
try {
493+
Main main = result.process.getProcess();
494+
TOTPStorage storage = result.storage;
495+
496+
// Create devices
497+
TOTPDevice device1 = Totp.registerDevice(main, "user", "device1", 1, 30);
498+
TOTPDevice device2 = Totp.registerDevice(main, "user", "device2", 1, 30);
499+
500+
Thread.sleep(1);
501+
Totp.verifyDevice(main, "user", "device1", generateTotpCode(main, device1, -1));
502+
Thread.sleep(1);
503+
Totp.verifyDevice(main, "user", "device2", generateTotpCode(main, device2, -1));
504+
505+
TOTPDevice[] devices = Totp.getDevices(main, "user");
506+
assert (devices.length == 2);
507+
508+
// Try to delete device for non-existent user:
509+
assertThrows(UnknownDeviceException.class, () -> Totp.removeDevice(main, "non-existent-user", "device1"));
510+
511+
// Try to delete non-existent device:
512+
assertThrows(UnknownDeviceException.class, () -> Totp.removeDevice(main, "user", "non-existent-device"));
513+
514+
// Delete one of the devices
515+
{
516+
assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "ic0"));
517+
518+
Thread.sleep(1000 - System.currentTimeMillis() % 1000 + 10);
519+
520+
Thread.sleep(1);
521+
Totp.verifyCode(main, "user", generateTotpCode(main, device1));
522+
Thread.sleep(1);
523+
Totp.verifyCode(main, "user", generateTotpCode(main, device2));
524+
525+
// Delete device1
526+
Totp.removeDevice(main, "user", "device1");
527+
528+
// 1 device still remain so all codes should still be there:
529+
TOTPUsedCode[] usedCodes = getAllUsedCodesUtil(storage, "user");
530+
assert (usedCodes.length == 5); // 2 for device verification and 3 for code verification
531+
532+
devices = Totp.getDevices(main, "user");
533+
assert (devices.length == 1);
534+
}
535+
536+
// Deleting the last device of a user should delete all related codes:
537+
// Delete the 2nd (and the last) device
538+
{
539+
540+
// Create another user to test that other users aren't affected:
541+
TOTPDevice otherUserDevice = Totp.registerDevice(main, "other-user", "device", 1, 30);
542+
Totp.verifyDevice(main, "other-user", "device", generateTotpCode(main, otherUserDevice, -1));
543+
Thread.sleep(1);
544+
Totp.verifyCode(main, "other-user", generateTotpCode(main, otherUserDevice));
545+
assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "other-user", "ic1"));
546+
547+
// Delete device2
548+
Totp.removeDevice(main, "user", "device2");
549+
550+
// No more devices are left for the user:
551+
assert (Totp.getDevices(main, "user").length == 0);
552+
553+
// No device left so all codes of the user should be deleted:
554+
TOTPUsedCode[] usedCodes = getAllUsedCodesUtil(storage, "user");
555+
assert (usedCodes.length == 0);
556+
557+
usedCodes = getAllUsedCodesUtil(storage, "other-user");
558+
System.out.println("Point2 " + usedCodes.length);
559+
assert (usedCodes.length == 3); // 1 for device verification and 2 for code verification
560+
561+
// But for other users things should still be there:
562+
TOTPDevice[] otherUserDevices = Totp.getDevices(main, "other-user");
563+
assert (otherUserDevices.length == 1);
564+
}
565+
566+
result.process.kill();
567+
assertNotNull(result.process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
568+
569+
return; // successful
570+
} catch (Exception | AssertionError e) {
571+
lastException = e;
572+
result.process.kill();
573+
assertNotNull(result.process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STOPPED));
574+
}
468575
}
469-
Main main = result.process.getProcess();
470-
TOTPStorage storage = result.storage;
471-
472-
// Create devices
473-
TOTPDevice device1 = Totp.registerDevice(main, "user", "device1", 1, 30);
474-
TOTPDevice device2 = Totp.registerDevice(main, "user", "device2", 1, 30);
475-
476-
Thread.sleep(1);
477-
Totp.verifyDevice(main, "user", "device1", generateTotpCode(main, device1, -1));
478-
Thread.sleep(1);
479-
Totp.verifyDevice(main, "user", "device2", generateTotpCode(main, device2, -1));
480-
481-
TOTPDevice[] devices = Totp.getDevices(main, "user");
482-
assert (devices.length == 2);
483-
484-
// Try to delete device for non-existent user:
485-
assertThrows(UnknownDeviceException.class, () -> Totp.removeDevice(main, "non-existent-user", "device1"));
486-
487-
// Try to delete non-existent device:
488-
assertThrows(UnknownDeviceException.class, () -> Totp.removeDevice(main, "user", "non-existent-device"));
489-
490-
// Delete one of the devices
491-
{
492-
assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "user", "ic0"));
493-
494-
Thread.sleep(1000 - System.currentTimeMillis() % 1000 + 10);
495-
496-
Thread.sleep(1);
497-
Totp.verifyCode(main, "user", generateTotpCode(main, device1));
498-
Thread.sleep(1);
499-
Totp.verifyCode(main, "user", generateTotpCode(main, device2));
500-
501-
// Delete device1
502-
Totp.removeDevice(main, "user", "device1");
503-
504-
devices = Totp.getDevices(main, "user");
505-
assert (devices.length == 1);
506-
507-
// 1 device still remain so all codes should still be still there:
508-
TOTPUsedCode[] usedCodes = getAllUsedCodesUtil(storage, "user");
509-
assert (usedCodes.length == 5); // 2 for device verification and 3 for code verification
510-
}
511-
512-
// Deleting the last device of a user should delete all related codes:
513-
// Delete the 2nd (and the last) device
514-
{
515-
516-
// Create another user to test that other users aren't affected:
517-
TOTPDevice otherUserDevice = Totp.registerDevice(main, "other-user", "device", 1, 30);
518-
Totp.verifyDevice(main, "other-user", "device", generateTotpCode(main, otherUserDevice, -1));
519-
Thread.sleep(1);
520-
Totp.verifyCode(main, "other-user", generateTotpCode(main, otherUserDevice));
521-
assertThrows(InvalidTotpException.class, () -> Totp.verifyCode(main, "other-user", "ic1"));
522-
523-
// Delete device2
524-
Totp.removeDevice(main, "user", "device2");
525-
526-
// No more devices are left for the user:
527-
assert (Totp.getDevices(main, "user").length == 0);
528-
529-
// No device left so all codes of the user should be deleted:
530-
TOTPUsedCode[] usedCodes = getAllUsedCodesUtil(storage, "user");
531-
assert (usedCodes.length == 0);
532-
533-
// But for other users things should still be there:
534-
TOTPDevice[] otherUserDevices = Totp.getDevices(main, "other-user");
535-
assert (otherUserDevices.length == 1);
536-
537-
usedCodes = getAllUsedCodesUtil(storage, "other-user");
538-
assert (usedCodes.length == 3); // 1 for device verification and 2 for code verification
576+
if (lastException != null) {
577+
throw lastException;
539578
}
540579
}
541580

0 commit comments

Comments
 (0)