// Author: cirakg & claude ai // Summary: Tests for schema migration: fresh store, the v0 fixture to v1, idempotency, newer and damaged metadata. import Toybox.Application; import Toybox.Lang; import Toybox.Test; (:test) function migrationCreatesFreshMetadata(logger as Test.Logger) as Boolean { var store = new MemoryBackend(); var vault = new Vault(store, new FakeTime()); var meta = store.getValue(Migration.META_KEY) as Dictionary; return vault.ids().size() == 0 && (meta["schema"] as Number) == Migration.CURRENT; } // The v0 fixture: entries written in the current record format, listed by // the old "order" key. After migration they open as before, and running the // migration again changes nothing. (:test) function migrationV0ToV1(logger as Test.Logger) as Boolean { var store = new MemoryBackend(); var vault = new Vault(store, new FakeTime()); var a = vault.add(StorageFixtures.totp("A")); var b = vault.add(StorageFixtures.code("B")); store.setValue(Migration.META_KEY, {"order" => [b, a]}); var migrated = new Vault(store, new FakeTime()); var ids = migrated.ids(); var meta = store.getValue(Migration.META_KEY) as Dictionary; var opened = migrated.open(a); opened.wipeSecrets(); var again = new Vault(store, new FakeTime()); return ids.size() == 2 && ids[0].equals(b) && ids[1].equals(a) && (meta["schema"] as Number) == 1 && !meta.hasKey("order") && again.ids().size() == 2 && again.pendingIds().size() == 0; } (:test) function migrationRefusesNewerAndDamagedMetadata(logger as Test.Logger) as Boolean { var cases = [ {"schema" => Migration.CURRENT + 1, "ids" => [], "pending" => []}, "not a dictionary", {"schema" => "1", "ids" => [], "pending" => []}, {"schema" => 1, "ids" => ["K7F2", "K7F2"], "pending" => []}, {"schema" => 1, "ids" => ["bad"], "pending" => []}, {"schema" => 1, "ids" => [], "pending" => 5} ]; var expected = [VaultError.NEWER_SCHEMA, VaultError.DAMAGED, VaultError.DAMAGED, VaultError.DAMAGED, VaultError.DAMAGED, VaultError.DAMAGED]; for (var i = 0; i < cases.size(); i++) { var store = new MemoryBackend(); store.setValue(Migration.META_KEY, cases[i] as Application.Storage.ValueType); var code = 0; try { new Vault(store, new FakeTime()); } catch (err instanceof VaultError) { code = err.code; } if (code != expected[i]) { logger.error("case " + i + ": got " + code); return false; } // Newer or damaged data is never modified. var after = store.getValue(Migration.META_KEY); if (cases[i] instanceof String ? !(after as String).equals(cases[i] as String) : MemoryBackend.sizeOf(after) != MemoryBackend.sizeOf(cases[i] as Application.Storage.ValueType)) { logger.error("case " + i + ": metadata was modified"); return false; } } return true; }