1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
// Author: cirakg & claude ai
// Summary: Tests for the UI's logic without a screen: the code cache, code grouping, text helpers and option defaults.
import Toybox.Lang;
import Toybox.Test;
(:test)
function codeCacheSplitsCodesForReading(logger as Test.Logger) as Boolean {
var cases = [["123456", "123|456"], ["1234567", "123|4567"], ["12345678", "1234|5678"], ["R7VXK", "R7VXK"]];
for (var i = 0; i < cases.size(); i++) {
var c = cases[i] as Array<String>;
var parts = CodeCache.split(c[0]);
var joined = parts.size() == 2 ? parts[0] + "|" + parts[1] : parts[0];
if (!TestSupport.sameString(logger, "split " + c[0], joined, c[1])) {
return false;
}
}
return true;
}
// The cache gives the same codes as the OTP module, keeps them until the
// period ends, and learns the period and cost from the encrypted part.
(:test)
function codeCacheComputesAndExpiresCodes(logger as Test.Logger) as Boolean {
var vault = new Vault(new MemoryBackend(), new FakeTime());
var e = StorageFixtures.totp("Cache");
var secret = Bytes.copy(e.secret as ByteArray);
var id = vault.add(e);
var cache = new CodeCache(vault);
var now = 1800000026l; // 34 s left in the fixture's 60 s period
var expected = Otp.totp(Hashes.SHA256, secret, now, 60, 8);
var next = Otp.totp(Hashes.SHA256, secret, now + 60, 60, 8);
var unknownCost = cache.costOf(e);
var code = cache.code(id, now, 0);
var stillCached = cache.cached(id, now + 33) != null;
var expired = cache.cached(id, now + 34) == null;
return TestSupport.sameString(logger, "current", code, expected)
&& TestSupport.sameString(logger, "next", cache.code(id, now, 1), next)
&& stillCached && expired && cache.periodOf(id) == 60
&& unknownCost == CodeCache.COST_SHA512 && cache.costOf(e) == CodeCache.COST_NATIVE;
}
(:test)
function codeCacheHotpAndInvalidate(logger as Test.Logger) as Boolean {
var vault = new Vault(new MemoryBackend(), new FakeTime());
var id = vault.add(StorageFixtures.hotp("Counter", 5l));
var cache = new CodeCache(vault);
var first = cache.code(id, 0l, 0);
vault.setCounter(id, 6l);
var stale = cache.code(id, 0l, 0);
cache.invalidate(id);
var fresh = cache.code(id, 0l, 0);
// RFC 4226 Appendix D: counters 5 and 6.
return first.equals("254676") && stale.equals("254676") && fresh.equals("287922");
}
(:test)
function codeCacheRefusesLockedEntriesWhileLocked(logger as Test.Logger) as Boolean {
var vault = new Vault(new MemoryBackend(), new FakeTime());
StorageFixtures.setPin(vault, StorageFixtures.PIN);
var e = StorageFixtures.totp("Locked");
e.locked = true;
var id = vault.add(e);
var cache = new CodeCache(vault);
var unlockedCode = cache.code(id, 1800000026l, 0);
vault.lock();
cache.clear();
var code = 0;
try {
cache.code(id, 1800000026l, 0);
} catch (err instanceof VaultError) {
code = err.code;
}
return unlockedCode.length() == 8 && code == VaultError.LOCKED && cache.cached(id, 1800000026l) == null;
}
(:test)
function uiTextHelpers(logger as Test.Logger) as Boolean {
var parts = Ui.split("a b c", " ");
return Ui.isDigits("123 456") && !Ui.isDigits("12a") && !Ui.isDigits("") && !Ui.isDigits("------")
&& parts.size() == 4 && parts[0].equals("a") && parts[2].equals("") && parts[3].equals("c")
&& Ui.minutesSeconds(90).equals("1:30") && Ui.minutesSeconds(3600).equals("60:00") && Ui.minutesSeconds(5).equals("0:05");
}
// resources/settings/properties.xml defaults give README 10.2's defaults.
(:test)
function optionsDefaults(logger as Test.Logger) as Boolean {
var o = new Options();
o.load();
return o.autoLockMs == 60000 && !o.eraseAfterFailures && o.timeOffset == 0 && o.codeSize == Options.CODE_LARGE
&& o.theme == Options.THEME_DARK && o.showGroups && !o.encryptNames && !o.glanceCode && o.fullBrightness;
}
|