// Cross-client interop: kirakira's gsmol export imports into fumi, and // fumi's export imports back into kirakira — the interchange the export // patch upstream exists for. The kirakira side is driven through the real // store API now: pins and contacts (with rotation history) land in the // native store, and the sealed mail round-trip is covered by fumi-core's // own export tests, so this proves the container compatibility both ways. // Runs the fumi CLI as a subprocess, so the test skips when no built fumi // sits in the sibling checkout. import "dart:io"; import "dart:typed_data"; import "package:flutter_test/flutter_test.dart"; import "package:hive_flutter/hive_flutter.dart"; import "package:smol_mail/smol/address.dart"; import "package:smol_mail/smol/store.dart"; const fumiBinary = "../fumi/target/release/fumi"; void main() { final fumiBuilt = File(fumiBinary).existsSync(); setUpAll(() async { TestWidgetsFlutterBinding.ensureInitialized(); final dir = await Directory.systemTemp.createTemp("smol-interop"); Hive.init(dir.path); }); test("exports cross-import in both directions", skip: fumiBuilt ? false : "no built fumi in ../fumi", () async { final dir = await Directory.systemTemp.createTemp("fumi-interop"); final master = Uint8List.fromList(List.generate(32, (i) => i + 3)); // Real, distinct, valid key forms: the system server's and fumi's. const pinKey = "lm2gqd7e5q67xq3isc5hx6jfj2q7a7xvq4l7trctxipudujovmgq"; final mine = await SmolStore.open( dbPath: "${dir.path}/kirakira.db", stateBox: "interop-state", readBox: "interop-read"); mine.restoreMaster(master, 0); mine.pinServer("example.org", pinKey, defaultPort); // One contact, bound to the current key; rotation history is written by // the trust engine on a validated rotation, not by a plain re-save. await mine.saveContact("alice@example.org", pinKey, verified: true); final file = File("${dir.path}/kirakira.json"); await file.writeAsString(await mine.exportData()); // fumi imports it. final keyFile = File("${dir.path}/identity.key"); await keyFile.writeAsBytes(master); Future fumi(List args) => Process.run( fumiBinary, ["--key", keyFile.path, "--db", "${dir.path}/fumi.db", ...args]); final into = await fumi(["import-backup", file.path]); expect(into.exitCode, 0, reason: into.stderr.toString()); expect(into.stdout.toString(), contains("1 contacts")); // fumi exports its store, and kirakira imports that back. final fumiFile = File("${dir.path}/fumi.json"); final out = await fumi(["export", fumiFile.path]); expect(out.exitCode, 0, reason: out.stderr.toString()); final restored = await SmolStore.open( dbPath: "${dir.path}/restored.db", stateBox: "interop2-state", readBox: "interop2-read"); restored.restoreMaster(master, 0); final summary = await restored.importData(await fumiFile.readAsString()); expect(summary.contactsAdded, 1); expect(summary.pinsAdded, 1); expect(summary.malformed, 0); // The binding survived both directions of the round trip. final contact = restored.contact("alice@example.org")!; expect(contact.key, pinKey); expect(restored.serverPin("example.org", defaultPort), pinKey); }); }