112 lines
3.1 KiB
Dart
112 lines
3.1 KiB
Dart
// Copyright (c) 2023, Sudipto Chandra
|
|
// All rights reserved. Check LICENSE file for details.
|
|
|
|
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:test/test.dart';
|
|
import 'package:hashlib/hashlib.dart';
|
|
|
|
void main() {
|
|
final secret = Uint8List.fromList([
|
|
0x1,
|
|
0x2,
|
|
0x3,
|
|
0x4,
|
|
0x5,
|
|
0x6,
|
|
0x7,
|
|
0x8,
|
|
]);
|
|
|
|
group('TOTP', () {
|
|
test('algorithm name', () {
|
|
expect(TOTP(secret).name, 'SHA1/HMAC');
|
|
expect(TOTP(secret, algo: md5).name, 'MD5/HMAC');
|
|
});
|
|
|
|
test('secret, label and issuer values', () {
|
|
var totp = TOTP(secret, label: 'test label', issuer: 'test issuer');
|
|
expect(totp.secret, equals(secret));
|
|
expect(totp.label, equals('test label'));
|
|
expect(totp.issuer, equals('test issuer'));
|
|
|
|
var totp2 = TOTP(secret);
|
|
expect(totp2.secret, equals(secret));
|
|
expect(totp2.label, null);
|
|
expect(totp2.issuer, null);
|
|
});
|
|
|
|
test('should generate OTP of correct length', () {
|
|
expect(TOTP(secret).valueString().length, equals(6));
|
|
});
|
|
|
|
test('number of digits must be at least 4', () {
|
|
for (int i = 0; i < 20; ++i) {
|
|
int cb() => TOTP(secret, digits: i).valueString().length;
|
|
if (i < 4 || i > 15) {
|
|
expect(cb, throwsA((e) => e is AssertionError), reason: 'digits: $i');
|
|
} else {
|
|
expect(cb(), equals(i), reason: 'digits: $i');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should generate different OTPs over time', () {
|
|
var totp = TOTP(secret);
|
|
int time = 30000 - totp.currentTime % 30000;
|
|
totp.adjustClock(time);
|
|
int otp1 = totp.value();
|
|
totp.adjustClock(time + 30000);
|
|
int otp2 = totp.value();
|
|
expect(otp1, isNot(equals(otp2)));
|
|
totp.adjustClock(time + 1);
|
|
int otp3 = totp.value();
|
|
expect(otp1, equals(otp3));
|
|
totp.adjustClock(time - 100);
|
|
int otp4 = totp.value();
|
|
expect(otp1, isNot(equals(otp4)));
|
|
});
|
|
|
|
test('should adjust the clock correctly', () {
|
|
var totp1 = TOTP(secret);
|
|
var totp2 = TOTP(secret);
|
|
totp2.adjustClock(30030);
|
|
|
|
int otp1 = totp1.value();
|
|
int otp2 = totp2.value();
|
|
|
|
expect(otp1, isNot(equals(otp2)));
|
|
});
|
|
|
|
test('stream should emit OTPs periodically', () async {
|
|
int ms = 80;
|
|
var totp = TOTP(secret, period: Duration(milliseconds: ms));
|
|
totp.adjustClock(ms - totp.currentTime % ms);
|
|
final otps = <int>{};
|
|
var subscription = totp.stream.listen((otp) {
|
|
otps.add(otp);
|
|
});
|
|
await Future.delayed(
|
|
Duration(milliseconds: 4 * ms),
|
|
subscription.cancel,
|
|
);
|
|
expect(otps.length, inInclusiveRange(2, 6));
|
|
});
|
|
|
|
test('stream should emit OTP string periodically', () async {
|
|
int ms = 80;
|
|
var totp = TOTP(secret, period: Duration(milliseconds: ms));
|
|
totp.adjustClock(ms - totp.currentTime % ms);
|
|
final otps = <String>{};
|
|
var subscription = totp.streamString.listen((otp) {
|
|
otps.add(otp);
|
|
});
|
|
await Future.delayed(
|
|
Duration(milliseconds: 4 * ms),
|
|
subscription.cancel,
|
|
);
|
|
expect(otps.length, inInclusiveRange(2, 6));
|
|
});
|
|
});
|
|
}
|