test: patrol flows for fetch cancel, rotation and unchanged-key refresh

This commit is contained in:
randogoth 2026-09-29 20:35:16 +03:00
parent f7452832ea
commit fa0809f021
4 changed files with 240 additions and 34 deletions

View file

@ -4,6 +4,7 @@
import "dart:convert";
import "dart:io";
import "dart:math";
import "dart:typed_data";
import "package:flutter_test/flutter_test.dart";
@ -11,7 +12,7 @@ import "package:flutter_test/flutter_test.dart";
import "package:smol_mail/native/client.dart";
import "package:smol_mail/native/ffi.dart";
const spikeServerKey = "g5ttsssqjq6j37vso3h7otn33zrk6o6r7c6jjm7guxbbijoa5nqq";
const spikeServerKey = "wukbhdiwboarbg4pujxstbqi3caveunrruaopbwxr226ga3xv5ga";
const spikeServer = "127.0.0.1";
const spikePort = 19619;
@ -90,7 +91,8 @@ void main() async {
// username, so identities must be fresh per run.
final aliceMaster =
Uint8List.fromList(List.generate(32, (i) => i * 7 + DateTime.now().microsecondsSinceEpoch % 251));
final bobMaster = Uint8List.fromList(List.generate(32, (i) => i * 13 + 5));
final bobMaster =
Uint8List.fromList(List.generate(32, (_) => Random.secure().nextInt(256)));
final run = DateTime.now().millisecondsSinceEpoch.toRadixString(36);
final alice = FumiNative("${dir.path}/alice.db");
@ -124,4 +126,55 @@ void main() async {
await alice.close();
await bob.close();
});
// sec 7's readability invariant: mail sealed to the pre-rotation key must
// still open after a rotation, because the account derives every superseded
// key from the master.
test("old mail stays readable across a rotation",
skip: await spikeUp()
? false
: "no bunshin on 127.0.0.1:19619", () async {
final dir = await Directory.systemTemp.createTemp("native-rotate");
// Random masters: bunshin refuses a key already bound under another
// username, so identities must be fresh per run.
final rng = Random.secure();
final aliceMaster =
Uint8List.fromList(List.generate(32, (_) => rng.nextInt(256)));
final bobMaster =
Uint8List.fromList(List.generate(32, (_) => rng.nextInt(256)));
final run = DateTime.now().millisecondsSinceEpoch.toRadixString(36);
final alice = FumiNative("${dir.path}/alice.db");
await alice.open();
alice.setMaster(aliceMaster);
await alice.pinServer(spikeServer, spikeServerKey);
await alice.register("a$run@$spikeServer:$spikePort");
final bob = FumiNative("${dir.path}/bob.db");
await bob.open();
bob.setMaster(bobMaster);
// The onboarding screen zeroes its own master reference on dispose;
// whatever holds the master after that must not share that buffer.
bobMaster.fillRange(0, 32, 0);
await bob.pinServer(spikeServer, spikeServerKey);
await bob.register("b$run@$spikeServer:$spikePort");
await alice.send("b$run@$spikeServer:$spikePort",
"before the rotation",
subject: "pre-rotation");
final summary = await bob.fetch();
expect(summary["stored"], 1);
final requests = await bob.mail("requests");
final preRotationId = requests[0]["id"] as String;
final before = await bob.describe(preRotationId);
expect(before["subject"], "pre-rotation");
await bob.rotate();
final after = await bob.describe(preRotationId);
expect(after["subject"], "pre-rotation",
reason: "mail sealed to the superseded key must stay readable");
await alice.close();
await bob.close();
});
}