aboutsummaryrefslogtreecommitdiff
path: root/test/import/OtpAuthTest.mc
diff options
context:
space:
mode:
Diffstat (limited to 'test/import/OtpAuthTest.mc')
-rw-r--r--test/import/OtpAuthTest.mc99
1 files changed, 99 insertions, 0 deletions
diff --git a/test/import/OtpAuthTest.mc b/test/import/OtpAuthTest.mc
new file mode 100644
index 0000000..67f43f5
--- /dev/null
+++ b/test/import/OtpAuthTest.mc
@@ -0,0 +1,99 @@
+// Author: cirakg & claude ai
+// Summary: Tests for otpauth:// parsing: splitting, labels, parameters, Steam, separators and every validation error.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module OtpAuthFixtures {
+
+ // RFC 4226 test key "12345678901234567890" in Base32.
+ const SECRET = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; // lint: allow-literal (RFC test key)
+
+ function parse(uri as String) as ImportItem {
+ return OtpAuth.parse(uri, 1);
+ }
+
+ function entry(logger as Test.Logger, uri as String) as Entry? {
+ var item = parse(uri);
+ if (item.entry == null) {
+ logger.error("no entry for " + uri + ": " + item.problem + " / " + item.skipped);
+ }
+ return item.entry;
+ }
+}
+
+(:test)
+function otpauthSplitsRunTogetherUris(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var uris = OtpAuth.splitUris("junk otpauth://totp/A?secret=" + s + " \n OTPAUTH://totp/B?secret=" + s
+ + "otpauth://hotp/C?secret=" + s + "&counter=1\n");
+ return uris.size() == 3 && (uris[0] as String).find("totp/A") != null && (uris[1] as String).find("totp/B") != null
+ && (uris[2] as String).find("counter=1") != null && (uris[2] as String).find("\n") == null;
+}
+
+(:test)
+function otpauthLabelsAndDefaults(logger as Test.Logger) as Boolean {
+ var e = OtpAuthFixtures.entry(logger, "otpauth://totp/ACME%20Co:john%40example.com?secret=" + OtpAuthFixtures.SECRET);
+ var bare = OtpAuthFixtures.entry(logger, "otpauth://totp/alice?secret=" + OtpAuthFixtures.SECRET.toLower());
+ if (e == null || bare == null) {
+ return false;
+ }
+ return e.name.equals("ACME Co") && e.account.equals("john@example.com") && e.algorithm == Hashes.SHA1
+ && e.digits == 6 && e.period == 30 && e.type == Entry.OTP_TOTP && e.source == Entry.SOURCE_IMPORT
+ && Bytes.equal(e.secret as ByteArray, Bytes.fromUtf8("12345678901234567890"))
+ && bare.name.equals("") && bare.account.equals("alice");
+}
+
+(:test)
+function otpauthParametersAndSeparators(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var e = OtpAuthFixtures.entry(logger,
+ "otpauth://totp/Label:bob?SECRET=" + s + ";Algorithm=sha512;digits=8&period=60&image=https%3A%2F%2Fx&issuer=Big+Bank");
+ var item = OtpAuthFixtures.parse("otpauth://totp/Old:bob?secret=" + s + "&issuer=New");
+ if (e == null) {
+ return false;
+ }
+ return e.name.equals("Big Bank") && e.algorithm == Hashes.SHA512 && e.digits == 8 && e.period == 60
+ && item.entry != null && (item.entry as Entry).name.equals("New") && item.note != null
+ && e.account.equals("bob");
+}
+
+(:test)
+function otpauthHotpAndSteam(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var h = OtpAuthFixtures.entry(logger, "otpauth://hotp/VPN:dave?secret=" + s + "&counter=42");
+ var noCounter = OtpAuthFixtures.parse("otpauth://hotp/VPN:dave?secret=" + s);
+ var steam = OtpAuthFixtures.entry(logger, "otpauth://steam/Steam:erin?secret=" + s);
+ var encoder = OtpAuthFixtures.entry(logger, "otpauth://totp/Steam:erin?secret=" + s + "&encoder=steam&digits=5");
+ return h != null && h.type == Entry.OTP_HOTP && h.counter == 42l
+ && noCounter.problem != null && (noCounter.problem as String).find("counter") != null
+ && steam != null && steam.type == Entry.OTP_STEAM && encoder != null && encoder.type == Entry.OTP_STEAM;
+}
+
+(:test)
+function otpauthReportsEveryProblem(logger as Test.Logger) as Boolean {
+ var s = OtpAuthFixtures.SECRET;
+ var cases = [
+ ["otpauth://totp/A:b", "secret missing"],
+ ["otpauth://totp/A:b?secret=" + s + "&digits=9", "9 digits"],
+ ["otpauth://totp/A:b?secret=" + s + "&digits=x", "not a whole number"],
+ ["otpauth://totp/A:b?secret=" + s + "&period=0", "period 0"],
+ ["otpauth://totp/A:b?secret=" + s + "&algorithm=MD5", "algorithm"],
+ ["otpauth://totp/A:b?secret=ABC1DEFG", "position 4"],
+ ["otpauth://totp/A%2:b?secret=" + s, "% escape"],
+ ["otpauth://totp/?secret=" + s, "issuer or an account"],
+ ["otpauth://totp", "no '/'"]
+ ];
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i] as Array<String>;
+ var item = OtpAuthFixtures.parse(c[0]);
+ var problem = item.problem;
+ if (item.entry != null || problem == null || problem.find(c[1]) == null) {
+ logger.error("case " + i + ": problem " + problem);
+ return false;
+ }
+ }
+ var motp = OtpAuthFixtures.parse("otpauth://motp/A:b?secret=" + s);
+ return motp.skipped != null && motp.entry == null;
+}