feat: adopt smolmail protocol 1.1, adaptive nav shell, and server mail retention
This commit is contained in:
parent
eaaa3f2ede
commit
c693e6fcb9
27 changed files with 1190 additions and 592 deletions
|
|
@ -63,11 +63,11 @@ void main() {
|
|||
expect(saved.key, identity.publicKey);
|
||||
});
|
||||
|
||||
test("export never contains the seed and round-trips through import",
|
||||
test("export never contains the master secret and round-trips through import",
|
||||
() async {
|
||||
final a = await freshStore("export");
|
||||
final b = await freshStore("round-trip");
|
||||
a.setIdentity(randomBytes(32));
|
||||
a.setMaster(randomBytes(32));
|
||||
a.pinServer("example.org", randomBytes(32));
|
||||
a.saveContact("alice@example.org", randomBytes(32), true);
|
||||
a.saveContact("alice@example.org", randomBytes(32), false); // history grows
|
||||
|
|
@ -77,12 +77,12 @@ void main() {
|
|||
MailRecord("bb", randomBytes(64), recipient: "bob@example.org", sentAt: 6));
|
||||
|
||||
final data = a.exportData();
|
||||
expect(data["gsmolExport"], 2); // sealed to the identity's seed, like gsmol
|
||||
expect(jsonEncode(data).contains(hex(a.seed()!)), isFalse);
|
||||
expect(data["gsmolExport"], 2); // sealed to the identity's master, like gsmol
|
||||
expect(jsonEncode(data).contains(hex(a.master()!)), isFalse);
|
||||
|
||||
// v2 is sealed to the exporting identity's seed — a restore-on-new-device
|
||||
// v2 is sealed to the exporting identity's master — a restore-on-new-device
|
||||
// scenario, not a transfer to someone else's identity (see the test below).
|
||||
b.setIdentity(a.seed()!);
|
||||
b.setMaster(a.master()!);
|
||||
final summary = await b.importData(data);
|
||||
expect(summary.mailAdded, 2);
|
||||
expect(summary.pinsAdded, 1);
|
||||
|
|
@ -96,14 +96,58 @@ void main() {
|
|||
test("v2 import refuses a different identity's export", () async {
|
||||
final a = await freshStore("export-wrong-identity");
|
||||
final c = await freshStore("round-trip-wrong-identity");
|
||||
a.setIdentity(randomBytes(32));
|
||||
a.setMaster(randomBytes(32));
|
||||
a.pinServer("example.org", randomBytes(32));
|
||||
final data = a.exportData();
|
||||
|
||||
c.setIdentity(randomBytes(32)); // a different seed than a's
|
||||
c.setMaster(randomBytes(32)); // a different master than a's
|
||||
expect(() => c.importData(data), throwsA(isA<SmolError>()));
|
||||
});
|
||||
|
||||
test("accept tokens: accept/block gate the sync set, tiers split the inbox view",
|
||||
() async {
|
||||
final store = await freshStore("accept-tokens");
|
||||
final master = randomBytes(32);
|
||||
store.setMaster(master);
|
||||
final alice = randomBytes(32);
|
||||
store.saveContact("alice@example.org", alice, true);
|
||||
|
||||
// No one accepted yet: an empty set is already complete, so it may sync.
|
||||
var (sync, tokens) = store.tokenSet(master);
|
||||
expect(sync, 1);
|
||||
expect(tokens, isEmpty);
|
||||
|
||||
store.accept("alice@example.org", alice);
|
||||
(sync, tokens) = store.tokenSet(master);
|
||||
expect(sync, 1);
|
||||
expect(tokens, [tokenFor(master, alice)]);
|
||||
expect(store.accepted("alice@example.org")!.active, isTrue);
|
||||
|
||||
store.block("alice@example.org");
|
||||
expect(store.accepted("alice@example.org")!.active, isFalse);
|
||||
expect(store.tokenSet(master).$2, isEmpty);
|
||||
// Re-accepting keeps the identity frozen at the original acceptance,
|
||||
// so the token a correspondent already holds keeps working.
|
||||
store.accept("alice@example.org", randomBytes(32));
|
||||
expect(store.accepted("alice@example.org")!.identity, alice);
|
||||
|
||||
expect(() => store.block("bob@example.org"), throwsA(isA<SmolError>()));
|
||||
|
||||
// A restored master must not silently replace the server's set.
|
||||
store.setSyncOk(false);
|
||||
(sync, tokens) = store.tokenSet(master);
|
||||
expect(sync, 0);
|
||||
expect(tokens, isEmpty);
|
||||
|
||||
await store.storeIfNew(
|
||||
"inbox", MailRecord("aa", randomBytes(64), receivedAt: 1, tier: tierMain));
|
||||
await store.storeIfNew("inbox",
|
||||
MailRecord("bb", randomBytes(64), receivedAt: 2, tier: tierRequests));
|
||||
expect(store.listMessages("inbox").map((r) => r.id), ["aa"]);
|
||||
expect(store.listMessages("requests").map((r) => r.id), ["bb"]);
|
||||
expect(store.getMessage("requests", "bb"), isNotNull);
|
||||
});
|
||||
|
||||
test("v1 legacy export still imports without an identity", () async {
|
||||
final store = await freshStore("import-legacy-v1");
|
||||
final summary = await store.importData({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue