From 42f9331e693f5058fcfcd7aef5a92cabc04c20ee Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 01:51:05 +0200 Subject: test(import): cover parsers, importer and a seeded fuzz corpus; import 100 entries in the UI flow Co-Authored-By: Claude Opus 5.5 --- test/import/AegisTest.mc | 128 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 test/import/AegisTest.mc (limited to 'test/import/AegisTest.mc') diff --git a/test/import/AegisTest.mc b/test/import/AegisTest.mc new file mode 100644 index 0000000..eaad039 --- /dev/null +++ b/test/import/AegisTest.mc @@ -0,0 +1,128 @@ +// Author: cirakg & claude ai +// Summary: Tests for the Aegis reader: database versions 1-3, types, parameters, groups, icons, encryption and bad input. + +import Toybox.Lang; +import Toybox.Test; + +// Database version 3: types, algorithms, digits, periods, groups listed +// after the entries, favourites, icons and Unicode. +(:test) +function aegisReadsDatabaseVersion3(logger as Test.Logger) as Boolean { + var icon = ""; + for (var i = 0; i < 200; i++) { + icon += "iVBORw0KGgoAAAANSUhEUgAAA"; + } + var entries = [ + AegisFixtures.entry("totp", "GitHub", "alice", AegisFixtures.SECRET_1, "SHA1", 6, 30, + "\"icon\": \"" + icon + "\", \"icon_mime\": \"image/png\", \"groups\": [\"g-work\", \"g-other\"], "), + AegisFixtures.entry("totp", "Bänk €", "bob", AegisFixtures.SECRET_2, "SHA256", 8, 60, "\"favorite\": true, "), + "{\"type\": \"hotp\", \"name\": \"carol\", \"issuer\": \"VPN\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_3 + + "\", \"algo\": \"SHA512\", \"digits\": 7, \"period\": 30, \"counter\": 12}}", + AegisFixtures.entry("steam", "Steam", "dave", AegisFixtures.SECRET_1, "SHA1", 5, 30, ""), + AegisFixtures.entry("motp", "Old", "erin", AegisFixtures.SECRET_1, "MD5", 6, 10, ""), + AegisFixtures.entry("yandex", "Ya", "frank", AegisFixtures.SECRET_1, "SHA256", 8, 30, ""), + AegisFixtures.entry("totp", "Odd", "gina", AegisFixtures.SECRET_1, "SHA1", 6, 20, "\"groups\": [\"g-missing\"], "), + AegisFixtures.entry("totp", "Slow", "hal", AegisFixtures.SECRET_1, "SHA1", 6, 50, "") + ]; + var groups = "[{\"uuid\": \"g-work\", \"name\": \"Work\"}, {\"uuid\": \"g-other\", \"name\": \"Other\"}]"; + var reader = AegisFixtures.read(AegisFixtures.export(3, entries, groups)); + if (reader.error != null || reader.items.size() != 8) { + logger.error("error " + reader.error + ", items " + reader.items.size()); + return false; + } + var it = reader.items; + var github = it[0].entry; + var bank = it[1].entry; + var vpn = it[2].entry; + var steam = it[3].entry; + var odd = it[6].entry; + var slow = it[7].entry; + if (github == null || bank == null || vpn == null || steam == null || odd == null || slow == null) { + for (var i = 0; i < it.size(); i++) { + logger.error("item " + i + ": " + it[i].problem + " / " + it[i].skipped); + } + return false; + } + var checks = [ + github.name.equals("GitHub") && github.account.equals("alice"), + github.group.equals("Work"), + bank.name.equals("Bänk €") && bank.algorithm == Hashes.SHA256 && bank.digits == 8 && bank.period == 60, + bank.favorite, + vpn.type == Entry.OTP_HOTP && vpn.counter == 12l && vpn.digits == 7 && vpn.algorithm == Hashes.SHA512, + steam.type == Entry.OTP_STEAM, + it[4].skipped != null && it[5].skipped != null, + odd.group.equals("") && odd.period == 20 && slow.period == 50, + Bytes.equal(github.secret as ByteArray, Bytes.fromUtf8("12345678901234567890")) + ]; + for (var i = 0; i < checks.size(); i++) { + if (!(checks[i] as Boolean)) { + logger.error("check " + i + " failed"); + return false; + } + } + return true; +} + +// Database versions 1 and 2 name the group directly. +(:test) +function aegisReadsOlderDatabaseVersions(logger as Test.Logger) as Boolean { + for (var v = 1; v <= 2; v++) { + var reader = AegisFixtures.read(AegisFixtures.export(v, + [AegisFixtures.entry("totp", "Mail", "ann", AegisFixtures.SECRET_1, "SHA1", 6, 30, "\"group\": \"Private\", ")], "")); + var e = reader.items.size() == 1 ? reader.items[0].entry : null; + if (e == null || !e.group.equals("Private")) { + logger.error("database version " + v + " failed: " + reader.error); + return false; + } + } + return true; +} + +(:test) +function aegisRefusesEncryptedExports(logger as Test.Logger) as Boolean { + var slots = "{\"version\": 1, \"header\": {\"slots\": [{\"type\": 1}], \"params\": {}}, \"db\": \"c2VjcmV0\"}"; + var dbString = "{\"version\": 1, \"header\": {\"slots\": null}, \"db\": \"c2VjcmV0\"}"; + var a = AegisFixtures.read(slots); + var b = AegisFixtures.read(dbString); + return a.error != null && (a.error as String).find("encrypted") != null + && b.error != null && (b.error as String).find("encrypted") != null; +} + +(:test) +function aegisReportsBadEntriesAndDocuments(logger as Test.Logger) as Boolean { + var entries = [ + AegisFixtures.entry("totp", "Nine", "x", AegisFixtures.SECRET_1, "SHA1", 9, 30, ""), + "{\"type\": \"totp\", \"name\": \"dup\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_1 + "\", \"secret\": \"" + + AegisFixtures.SECRET_2 + "\", \"algo\": \"SHA1\", \"digits\": 6, \"period\": 30}}", + AegisFixtures.entry("totp", "Bad", "y", "NOT-BASE32!", "SHA1", 6, 30, ""), + "{\"type\": \"totp\", \"name\": \"z\", \"info\": {\"secret\": \"" + AegisFixtures.SECRET_1 + "\", \"digits\": 6.5}}" + ]; + var reader = AegisFixtures.read(AegisFixtures.export(3, entries, "")); + var it = reader.items; + if (reader.error != null || it.size() != 4) { + logger.error("error " + reader.error); + return false; + } + var problems = [it[0].problem, it[1].problem, it[2].problem, it[3].problem]; + var needles = ["9 digits", "twice", "Base32", "whole number"]; + for (var i = 0; i < 4; i++) { + var p = problems[i]; + if (p == null || (p as String).find(needles[i]) == null) { + logger.error("entry " + i + ": " + p); + return false; + } + } + var notJson = AegisFixtures.read("{\"version\": 1, \"db\": [1,]}"); + var notAegis = AegisFixtures.read("{\"hello\": \"world\"}"); + var newer = AegisFixtures.read("{\"version\": 2, \"db\": {\"version\": 3, \"entries\": []}}"); + var deep = "{\"version\": 1, \"x\": " + "[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]" + "}"; + return notJson.error != null && notAegis.error != null && newer.error != null + && (newer.error as String).find("version 2") != null && AegisFixtures.read(deep).error != null; +} + +(:test) +function aegisSaysWhenTheTextIsIncomplete(logger as Test.Logger) as Boolean { + var full = AegisFixtures.export(3, [AegisFixtures.totp("A", "a", AegisFixtures.SECRET_1), AegisFixtures.totp("B", "b", AegisFixtures.SECRET_2)], ""); + return AegisFixtures.status(full.substring(0, full.length() / 2) as String) == AegisReader.INCOMPLETE + && AegisFixtures.status(full) == AegisReader.DONE; +} -- cgit v1.2.3