import 'dart:typed_data'; import 'package:block2_spike/block2_spike.dart'; import 'package:test/test.dart'; /// Q3 — publish → discover a real offer by geohash against a running relay /// (in-process, hermetic). Proves the flow works end to end and measures it. void main() { late MiniRelay relay; setUp(() async => relay = await MiniRelay.start()); tearDown(() async => relay.stop()); NostrKey keyFor(int fill) => NostrKey.deriveFromSeed(Uint8List(32)..fillRange(0, 32, fill)); test('offer published by one identity is discovered by another via geohash', () async { final alice = keyFor(1); final bob = keyFor(2); final aliceTransport = await NostrOfferTransport.connect( relay.url, privateKeyHex: alice.privateKeyHex, pubkeyHex: alice.publicKeyHex, ); final bobTransport = await NostrOfferTransport.connect( relay.url, privateKeyHex: bob.privateKeyHex, pubkeyHex: bob.publicKeyHex, ); final offer = Offer( id: 'tomate-1', authorPubkeyHex: alice.publicKeyHex, summary: 'Tomate rosa de Barbastro', type: OfferType.gift, category: 'Solanaceae', approxGeohash: 'sp3e9xyz', // will be coarsened to sp3e9 ); final published = await aliceTransport.publish(offer); expect(published.accepted, isTrue, reason: 'relay OK: ${published.message}'); // Bob queries a coarser area and still finds it (ladder match). final found = await bobTransport.discoverUntilEose( const DiscoveryQuery(geohashPrefix: 'sp3'), ); expect(found, hasLength(1)); expect(found.single.summary, 'Tomate rosa de Barbastro'); expect(found.single.authorPubkeyHex, alice.publicKeyHex); await aliceTransport.close(); await bobTransport.close(); }); test('a query for a different area returns nothing (geohash scoping works)', () async { final alice = keyFor(1); final t = await NostrOfferTransport.connect( relay.url, privateKeyHex: alice.privateKeyHex, pubkeyHex: alice.publicKeyHex, ); await t.publish(Offer( id: 'x', authorPubkeyHex: alice.publicKeyHex, summary: 'Pimiento', type: OfferType.gift, approxGeohash: 'sp3e9', )); final elsewhere = await t.discoverUntilEose( const DiscoveryQuery(geohashPrefix: 'u09'), ); expect(elsewhere, isEmpty); await t.close(); }); test('re-publishing the same offer id replaces (addressable), not duplicates', () async { final alice = keyFor(3); final t = await NostrOfferTransport.connect( relay.url, privateKeyHex: alice.privateKeyHex, pubkeyHex: alice.publicKeyHex, ); Offer o(String summary) => Offer( id: 'lot-42', authorPubkeyHex: alice.publicKeyHex, summary: summary, type: OfferType.exchange, exchangeTerms: 'a cambio de legumbre', approxGeohash: 'sp3e9', ); await t.publish(o('v1')); await t.publish(o('v2')); final found = await t.discoverUntilEose( const DiscoveryQuery(geohashPrefix: 'sp3e9'), ); expect(found, hasLength(1), reason: 'addressable replace by (kind,pubkey,d)'); expect(found.single.summary, 'v2'); await t.close(); }); test('measures publish→discover latency (informational)', () async { final alice = keyFor(4); final t = await NostrOfferTransport.connect( relay.url, privateKeyHex: alice.privateKeyHex, pubkeyHex: alice.publicKeyHex, ); final sw = Stopwatch()..start(); await t.publish(Offer( id: 'latency', authorPubkeyHex: alice.publicKeyHex, summary: 'Calabaza', type: OfferType.gift, approxGeohash: 'sp3e9', )); final found = await t.discoverUntilEose( const DiscoveryQuery(geohashPrefix: 'sp3e9'), ); sw.stop(); expect(found, hasLength(1)); // Not an assertion on wall-time (CI is noisy); just surface the number. printOnFailure('publish+discover round-trip: ${sw.elapsedMilliseconds} ms'); // ignore: avoid_print print('[metric] publish+discover round-trip: ${sw.elapsedMilliseconds} ms'); await t.close(); }); }