aboutsummaryrefslogtreecommitdiff
path: root/test/settings/CodeFormTest.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 18:36:54 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 18:36:54 +0200
commit479d7ba2e843d5ea93979f2eed68f4d38d146c25 (patch)
tree52b0bc58a7456e8675e977698db20e55515b7100 /test/settings/CodeFormTest.mc
parent151bfda5d4c567ff909a61dc9c90bf8eece1db37 (diff)
test(settings): cover every form, symbology and Manage action; send the forms end to end in the UI flow
34 Monkey C tests (154 per device): validation messages, idempotency, the queue, the About scan, "Encrypt names" and the properties contract. The UI-test build fills in Add OTP, Add code and Manage like the phone; the flow checks the new TOTP (309 848 at the frozen time), decodes the Code 128 card and confirms the queued change to the locked entry with the PIN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'test/settings/CodeFormTest.mc')
-rw-r--r--test/settings/CodeFormTest.mc176
1 files changed, 176 insertions, 0 deletions
diff --git a/test/settings/CodeFormTest.mc b/test/settings/CodeFormTest.mc
new file mode 100644
index 0000000..9d51797
--- /dev/null
+++ b/test/settings/CodeFormTest.mc
@@ -0,0 +1,176 @@
+// Author: cirakg & claude ai
+// Summary: Tests for the Add code form: a valid and invalid payload for every symbology, options, notes, size warnings and the cache.
+
+import Toybox.Lang;
+import Toybox.Test;
+
+(:test)
+module CodeFormCases {
+
+ // [symbology, payload, part of the status] for contents every
+ // symbology accepts (all fake).
+ const VALID = [
+ [Codes.QR, "https://example.com/card/0042", ""],
+ [Codes.CODE128, "Member-0042", ""],
+ [Codes.CODE39, "CARD 0042", ""],
+ [Codes.EAN13, " 590123412345 ", "check digit 7 added"],
+ [Codes.EAN8, "96385074", ""],
+ [Codes.UPCA, "03600029145", "check digit 2 added"],
+ [Codes.UPCE, "0123456", ""],
+ [Codes.ITF, "123456789", ""], // with its check digit: 10 digits
+ [Codes.CODABAR, "40156", "start and stop characters A...A added"],
+ [Codes.DATAMATRIX, "PARCEL-FAKE-0042", ""],
+ [Codes.PDF417, "Boarding pass FAKE 0042", ""],
+ [Codes.AZTEC, "RAIL-FAKE-0042", ""]
+ ];
+
+ // [symbology, payload, check-digit mode, expected status part].
+ function invalid() as Array<Array> {
+ return [
+ [Codes.EAN13, "5901234123450", Codes.CHECK_ADD_VERIFY,
+ "Not added: EAN-13: check digit should be 7, got 0. Choose 'Fix if wrong' to correct it"],
+ [Codes.EAN13, "59012341234", Codes.CHECK_ADD_VERIFY, "EAN-13 needs 12 or 13 digits, got 11"],
+ [Codes.EAN13, "590123412345", Codes.CHECK_OFF, "EAN-13 with the check digit off needs all 13 digits"],
+ [Codes.EAN8, "12A4567", Codes.CHECK_ADD_VERIFY, "EAN-8 takes digits only"],
+ [Codes.UPCA, "123", Codes.CHECK_ADD_VERIFY, "UPC-A needs 11 or 12 digits, got 3"],
+ [Codes.UPCE, "2123456", Codes.CHECK_ADD_VERIFY, "UPC-E: the number system must be 0 or 1, got 2"],
+ [Codes.CODE128, "Café", Codes.CHECK_ADD_VERIFY, "Code 128: 'é' (position 4) is not a printable ASCII character"],
+ [Codes.CODE39, "card", Codes.CHECK_ADD_VERIFY, "Code 39: 'c' (position 1)"],
+ [Codes.ITF, "123", Codes.CHECK_OFF, "ITF needs an even number of digits: add a leading 0"],
+ [Codes.CODABAR, "A12X3B", Codes.CHECK_ADD_VERIFY, "Codabar: 'X' isn't allowed there"],
+ [Codes.QR, SettingsFixtures.repeat("a", 2400), Codes.CHECK_ADD_VERIFY, "QR: too long for a QR code"],
+ [Codes.DATAMATRIX, SettingsFixtures.repeat("a", 1600), Codes.CHECK_ADD_VERIFY, "too long for Data Matrix"],
+ [Codes.PDF417, SettingsFixtures.repeat("a1", 1500), Codes.CHECK_ADD_VERIFY, "PDF417: too"],
+ [Codes.AZTEC, SettingsFixtures.repeat("é", 2000), Codes.CHECK_ADD_VERIFY, "Aztec: too long"]
+ ] as Array<Array>;
+ }
+}
+
+// Every symbology accepts a typical payload; 2D codes are cached encoded.
+(:test)
+function settingsAddsEverySymbology(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ var s = SettingsFixtures.store();
+ var ok = true;
+ for (var i = 0; i < CodeFormCases.VALID.size(); i++) {
+ var c = CodeFormCases.VALID[i] as Array;
+ var symbology = c[0] as Number;
+ SettingsFixtures.code(s, symbology, "Card " + i, c[1] as String);
+ SettingsFixtures.run(vault, s);
+ var status = SettingsFixtures.text(s, CodeForm.STATUS);
+ var ids = vault.ids();
+ if (ids.size() != i + 1 || status.find("Added " + ids[i] + " Card " + i) != 0 || status.find(c[2] as String) == null) {
+ logger.error(Codes.name(symbology) + ": " + status);
+ ok = false;
+ continue;
+ }
+ var cached = vault.loadCodeCache(ids[i]);
+ if (Codes.isMatrix(symbology) != (cached != null && Codes.Matrix.fromBytes(cached) != null)) {
+ logger.error(Codes.name(symbology) + ": cache " + (cached != null));
+ ok = false;
+ }
+ if (!SettingsFixtures.text(s, CodeForm.PAYLOAD).equals("") || Forms.flag(s, CodeForm.SWITCH)) {
+ logger.error(Codes.name(symbology) + ": form not cleared");
+ ok = false;
+ }
+ }
+ // Linear contents are stored trimmed.
+ return ok && SettingsFixtures.secretOf(vault, vault.ids()[3]).equals("590123412345");
+}
+
+// Every symbology's typical mistakes give the encoder's message; nothing
+// is added and only the contents are cleared.
+(:test)
+function settingsRejectsInvalidCodes(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ var cases = CodeFormCases.invalid();
+ var ok = true;
+ for (var i = 0; i < cases.size(); i++) {
+ var c = cases[i];
+ var s = SettingsFixtures.store();
+ SettingsFixtures.code(s, c[0] as Number, "Card", c[1] as String);
+ s.set("codeCheckDigit", c[2] as Number);
+ SettingsFixtures.run(vault, s);
+ var status = SettingsFixtures.text(s, CodeForm.STATUS);
+ if (status.find(c[3] as String) == null || vault.ids().size() != 0 || !SettingsFixtures.text(s, "codeName").equals("Card")
+ || !SettingsFixtures.text(s, CodeForm.PAYLOAD).equals("")) {
+ logger.error("case " + i + " (" + Codes.name(c[0] as Number) + "): " + status);
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+(:test)
+function settingsChecksCodeFields(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ var s = SettingsFixtures.store();
+ SettingsFixtures.code(s, Codes.QR, " ", "x");
+ SettingsFixtures.run(vault, s);
+ var ok = SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Name: enter a name for the card");
+ SettingsFixtures.code(s, Codes.QR, "Card", " ");
+ SettingsFixtures.run(vault, s);
+ ok = ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Contents: missing");
+ SettingsFixtures.code(s, 12, "Card", "x");
+ SettingsFixtures.run(vault, s);
+ ok = ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Type: choose one of the 12 barcode types");
+ SettingsFixtures.code(s, Codes.QR, "Card", SettingsFixtures.repeat("é", 2049));
+ SettingsFixtures.run(vault, s);
+ return ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Contents: too long (4098 bytes, at most 4096)")
+ && vault.ids().size() == 0;
+}
+
+// The form's options end up in Entry.options; "Fix if wrong" corrects the
+// check digit and says so.
+(:test)
+function settingsStoresCodeOptions(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ var s = SettingsFixtures.store();
+ SettingsFixtures.code(s, Codes.EAN13, "Gym", "5901234123450");
+ s.set("codeCheckDigit", Codes.CHECK_FIX);
+ s.set("codeShowText", false);
+ s.set("codeEcLevel", 3);
+ s.set("codeGroup", "Sport");
+ s.set("codeLocked", false);
+ SettingsFixtures.run(vault, s);
+ if (vault.ids().size() != 1) {
+ logger.error(SettingsFixtures.text(s, CodeForm.STATUS));
+ return false;
+ }
+ var e = vault.open(vault.ids()[0]);
+ var ok = e.options == (3 | (Codes.CHECK_FIX << Codes.OPT_CHECK_SHIFT)) && e.group.equals("Sport") && e.type == Codes.EAN13;
+ e.wipeSecrets();
+ return ok && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "check digit fixed: 7 (was 0)");
+}
+
+// Codes that encode but can't be shown at the minimum module size on this
+// screen are added with the README 12.2 warning.
+(:test)
+function settingsWarnsWhenACodeIsTooLarge(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ var s = SettingsFixtures.store();
+ // About version 39 at level L: over 360 px at 2 px per module.
+ SettingsFixtures.code(s, Codes.QR, "Big", SettingsFixtures.repeat("Ab", 1400));
+ s.set("codeEcLevel", 0);
+ SettingsFixtures.run(vault, s);
+ var qr = SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Too large for this screen: needs 2 px per module");
+ SettingsFixtures.code(s, Codes.CODE128, "Wide", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn");
+ SettingsFixtures.run(vault, s);
+ return qr && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "Too large for this screen: needs") && vault.ids().size() == 2;
+}
+
+// A locked card sent while locked waits as pending; no cache is written
+// for it (its key isn't available).
+(:test)
+function settingsLockedCodeWaitsForThePin(logger as Test.Logger) as Boolean {
+ var vault = SettingsFixtures.vault();
+ StorageFixtures.setPin(vault, StorageFixtures.PIN);
+ vault.lock();
+ var s = SettingsFixtures.store();
+ SettingsFixtures.code(s, Codes.DATAMATRIX, "Parcel", "PARCEL-FAKE-0042");
+ s.set("codeLocked", true);
+ SettingsFixtures.run(vault, s);
+ var id = vault.pendingIds().size() == 1 ? vault.pendingIds()[0] : "";
+ return id.length() > 0 && vault.storage().getValue("x." + id) == null
+ && SettingsFixtures.contains(logger, s, CodeForm.STATUS, "It waits for your PIN on the watch");
+}