diff options
| author | cirakg <cirakg@cirakg.xyz> | 2026-10-20 18:36:54 +0200 |
|---|---|---|
| committer | cirakg <cirakg@cirakg.xyz> | 2026-10-20 18:36:54 +0200 |
| commit | 479d7ba2e843d5ea93979f2eed68f4d38d146c25 (patch) | |
| tree | 52b0bc58a7456e8675e977698db20e55515b7100 /test/settings/CodeFormTest.mc | |
| parent | 151bfda5d4c567ff909a61dc9c90bf8eece1db37 (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.mc | 176 |
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"); +} |
