aboutsummaryrefslogtreecommitdiff
path: root/test/import/AegisTest.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 01:51:05 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 01:51:05 +0200
commit42f9331e693f5058fcfcd7aef5a92cabc04c20ee (patch)
treef3dab2657eb592ee468e6cebc9b47d9da7e08683 /test/import/AegisTest.mc
parentd537f4e154a40075405b0ab2fed14fbe2eeaac81 (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/AegisTest.mc')
-rw-r--r--test/import/AegisTest.mc128
1 files changed, 128 insertions, 0 deletions
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;
+}