// Store-level behavior against the real native store: master lifecycle, read // marks, pins and the settings the app owns in Hive. import "dart:io"; import "dart:math"; import "dart:typed_data"; import "package:flutter_test/flutter_test.dart"; import "package:hive_flutter/hive_flutter.dart"; import "package:smol_mail/smol/store.dart"; Uint8List randomBytes(int n) => Uint8List.fromList(List.generate(n, (_) => Random.secure().nextInt(256))); Future freshStore(String tag) => SmolStore.open( dbPath: "${Directory.systemTemp.createTempSync("smol-store-$tag").path}/store.db", stateBox: "$tag-state", readBox: "$tag-read"); void main() { setUpAll(() async { TestWidgetsFlutterBinding.ensureInitialized(); final dir = await Directory.systemTemp.createTemp("smol-store-test"); Hive.init(dir.path); }); test("master set, restore and rotations", () async { final store = await freshStore("master"); expect(store.master(), isNull); expect(store.identity(), isNull); final master = randomBytes(32); store.setMaster(master); expect(store.master(), master); // A fresh identity's public key is the native-derived one. expect(store.identity()!.publicKey.length, 52); expect(store.rotations(), 0); final other = randomBytes(32); store.restoreMaster(other, 3); expect(store.master(), other); // The account handle was rebuilt at the restored rotation index. expect(store.identity()!.publicKey.length, 52); }); test("leave-on-server is a setting, not protocol state", () async { final store = await freshStore("leave"); expect(store.leaveOnServer(), isFalse); await store.setLeaveOnServer(true); expect(store.leaveOnServer(), isTrue); await store.setLeaveOnServer(false); expect(store.leaveOnServer(), isFalse); }); test("read marks drive the unread counts", () async { final store = await freshStore("read"); expect(store.unreadCount(), 0); // Nothing is stored yet, so marking an id is harmless bookkeeping. store.markRead("ab" * 32); expect(store.isRead("ab" * 32), isTrue); }); test("pins save, list and unpin", () async { final store = await freshStore("pins"); expect(store.allPins(), isEmpty); // A syntactically valid key: the system server's, a real 52-char form. const key = "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq"; store.pinServer("example.org", key); expect(store.serverPin("example.org"), key); expect(store.allPins().length, 1); await store.unpinServer("example.org"); expect(store.serverPin("example.org"), isNull); }); test("wipe clears every secret and mark, overwriting the master", () async { final store = await freshStore("wipe"); final master = randomBytes(32); store.setMaster(master); // The store's own copy, read back before the wipe. final stored = store.master()!; store.pinServer("example.org", "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq"); store.markRead("cd" * 32); await store.wipe(); // The bytes the store owned are overwritten, not just dereferenced — // the reference read back through the store sees the zeros. The // caller's own buffer is the caller's to zeroize: the store holds a // copy precisely so nobody else's zeroization can reach it, and vice // versa a wipe never reaches a buffer the store handed out. expect(stored.every((b) => b == 0), isTrue); expect(master.every((b) => b == 0), isFalse); expect(store.master(), isNull); expect(store.identity(), isNull); expect(store.serverPin("example.org"), isNull); expect(store.isRead("cd" * 32), isFalse); }); }