diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-20 01:51:05 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-20 01:51:05 +0200 |
| commit | 42f9331e693f5058fcfcd7aef5a92cabc04c20ee (patch) | |
| tree | f3dab2657eb592ee468e6cebc9b47d9da7e08683 /test/import/OtpAuthTest.mc | |
| parent | d537f4e154a40075405b0ab2fed14fbe2eeaac81 (diff) | |
test(import): cover parsers, importer and a seeded fuzz corpus; import 100 entries in the UI flow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'test/import/OtpAuthTest.mc')
| -rw-r--r-- | test/import/OtpAuthTest.mc | 99 |
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; +} |
