fix: read back sent copies after the sec 5.6 upstream fix, pinned at 2d65d66
This commit is contained in:
parent
52947e153d
commit
d6f4c434fa
36 changed files with 4223 additions and 3118 deletions
127
test/native_binding_test.dart
Normal file
127
test/native_binding_test.dart
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
// The binding layer's proof: the Dart client drives the C ABI end to end —
|
||||
// handles, error codes, async isolates, and the backup round-trip. The
|
||||
// server-backed part runs only when the spike bunshin is up on 19619.
|
||||
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "dart:typed_data";
|
||||
|
||||
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 spikeServer = "127.0.0.1";
|
||||
const spikePort = 19619;
|
||||
|
||||
Future<bool> spikeUp() async {
|
||||
try {
|
||||
final socket = await Socket.connect(spikeServer, spikePort,
|
||||
timeout: const Duration(milliseconds: 300));
|
||||
socket.destroy();
|
||||
return true;
|
||||
} on SocketException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void main() async {
|
||||
test("handles, error codes and async isolates work end to end", () async {
|
||||
final dir = await Directory.systemTemp.createTemp("native-binding");
|
||||
final client = FumiNative("${dir.path}/a.db");
|
||||
await client.open();
|
||||
client.setMaster(Uint8List.fromList(List.filled(32, 9)));
|
||||
|
||||
// The account public key crosses as base32, driven from a worker isolate.
|
||||
expect((await client.accountPk()).length, 52);
|
||||
expect(await client.accountAddress(), isNull);
|
||||
|
||||
// A pinned but dead host fails by code, never by prose: Unreachable (7).
|
||||
await client.pinServer("127.0.0.1", spikeServerKey);
|
||||
client.setMaster(Uint8List.fromList(List.filled(32, 9)));
|
||||
try {
|
||||
await client.register("nobody@127.0.0.1:19629");
|
||||
fail("register against a dead port must fail");
|
||||
} on NativeSmolException catch (err) {
|
||||
expect(err.code, smolUnreachable);
|
||||
expect(err.details["host"], "127.0.0.1");
|
||||
}
|
||||
|
||||
// Empty folders and contact lookups render, not crash.
|
||||
expect(await client.mail("inbox"), isEmpty);
|
||||
expect((await client.contact("ghost@example.org"))["key"], "");
|
||||
|
||||
await client.close();
|
||||
});
|
||||
|
||||
test("the backup round-trip crosses the ABI in both directions", () async {
|
||||
final dir = await Directory.systemTemp.createTemp("native-backup");
|
||||
final master = Uint8List.fromList(List.generate(32, (i) => i + 3));
|
||||
|
||||
final mine = FumiNative("${dir.path}/mine.db");
|
||||
await mine.open();
|
||||
mine.setMaster(master);
|
||||
await mine.pinServer("example.org", spikeServerKey);
|
||||
await mine.importContact(
|
||||
"smol://alice@example.org/ayb6y3mwr3cfkcmcaqbn3cgfi6xsrsoqkalykw5s7oqmwqcpnmsq");
|
||||
|
||||
final file = await mine.exportBackup();
|
||||
|
||||
final restored = FumiNative("${dir.path}/restored.db");
|
||||
await restored.open();
|
||||
restored.setMaster(master);
|
||||
final summary = jsonDecode(await restored.importBackup(file))
|
||||
as Map<String, dynamic>;
|
||||
expect(summary["contactsAdded"], 1);
|
||||
expect(summary["pinsAdded"], 1);
|
||||
expect(await restored.serverPin("example.org"), spikeServerKey);
|
||||
|
||||
await mine.close();
|
||||
await restored.close();
|
||||
});
|
||||
|
||||
test("register, send, fetch and describe against the spike bunshin",
|
||||
skip: await spikeUp()
|
||||
? false
|
||||
: "no bunshin on 127.0.0.1:19619", () async {
|
||||
final dir = await Directory.systemTemp.createTemp("native-e2e");
|
||||
// Random masters: bunshin refuses a key already bound under another
|
||||
// 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 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");
|
||||
expect(await alice.accountAddress(), "a$run@$spikeServer:$spikePort");
|
||||
|
||||
final bob = FumiNative("${dir.path}/bob.db");
|
||||
await bob.open();
|
||||
bob.setMaster(bobMaster);
|
||||
await bob.pinServer(spikeServer, spikeServerKey);
|
||||
await bob.register("b$run@$spikeServer:$spikePort");
|
||||
|
||||
final sent = await alice.send("b$run@$spikeServer:$spikePort",
|
||||
"binding: hello over the ABI",
|
||||
subject: "binding");
|
||||
expect(sent["change"], "new");
|
||||
|
||||
final summary = await bob.fetch();
|
||||
expect(summary["stored"], 1);
|
||||
// First contact lands in the requests tier (sec 5.8).
|
||||
final requests = await bob.mail("requests");
|
||||
expect(requests, hasLength(1));
|
||||
final described =
|
||||
await bob.describe(requests[0]["id"] as String);
|
||||
expect(described["text"], "binding: hello over the ABI");
|
||||
expect(described["subject"], "binding");
|
||||
|
||||
await alice.close();
|
||||
await bob.close();
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue