basic nostr actors
This commit is contained in:
parent
c14de9c3e9
commit
f1e54f2722
13 changed files with 1081 additions and 2 deletions
152
test/nostr_fetch_actor_test.dart
Normal file
152
test/nostr_fetch_actor_test.dart
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import 'dart:convert';
|
||||
import 'package:swarm/swarm.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'fake_relay_connection.dart';
|
||||
|
||||
const _filter = '{"kinds":[1],"limit":10}';
|
||||
|
||||
Map<String, dynamic> _makeEvent(String id) => {
|
||||
'id': id,
|
||||
'pubkey': 'pub',
|
||||
'created_at': 1,
|
||||
'kind': 1,
|
||||
'tags': [],
|
||||
'content': 'hello',
|
||||
'sig': 'sig',
|
||||
};
|
||||
|
||||
void main() {
|
||||
group('NostrFetchActor', () {
|
||||
test('returns events before EOSE', () async {
|
||||
final fake = FakeRelayConnection();
|
||||
addTearDown(fake.dispose);
|
||||
|
||||
final event1 = _makeEvent('id1');
|
||||
final event2 = _makeEvent('id2');
|
||||
|
||||
fake.onReq = (subId) {
|
||||
fake.push(jsonEncode(['EVENT', subId, event1]));
|
||||
fake.push(jsonEncode(['EVENT', subId, event2]));
|
||||
fake.push(jsonEncode(['EOSE', subId]));
|
||||
};
|
||||
|
||||
final actor = NostrFetchActor(
|
||||
relayUrls: ['ws://relay1'],
|
||||
connectionFactory: (_) => fake,
|
||||
);
|
||||
|
||||
final result = await actor.handle(const FetchEvents(_filter));
|
||||
|
||||
expect(result, isA<EventsFetched>());
|
||||
expect((result as EventsFetched).events, hasLength(2));
|
||||
});
|
||||
|
||||
test('deduplicates events across relays', () async {
|
||||
final fake1 = FakeRelayConnection();
|
||||
final fake2 = FakeRelayConnection();
|
||||
addTearDown(fake1.dispose);
|
||||
addTearDown(fake2.dispose);
|
||||
|
||||
final event = _makeEvent('shared-id');
|
||||
|
||||
fake1.onReq = (subId) {
|
||||
fake1.push(jsonEncode(['EVENT', subId, event]));
|
||||
fake1.push(jsonEncode(['EOSE', subId]));
|
||||
};
|
||||
fake2.onReq = (subId) {
|
||||
fake2.push(jsonEncode(['EVENT', subId, event]));
|
||||
fake2.push(jsonEncode(['EOSE', subId]));
|
||||
};
|
||||
|
||||
final fakes = {'ws://relay1': fake1, 'ws://relay2': fake2};
|
||||
|
||||
final actor = NostrFetchActor(
|
||||
relayUrls: ['ws://relay1', 'ws://relay2'],
|
||||
connectionFactory: (url) => fakes[url]!,
|
||||
);
|
||||
|
||||
final result = await actor.handle(const FetchEvents(_filter));
|
||||
|
||||
expect(result, isA<EventsFetched>());
|
||||
expect((result as EventsFetched).events, hasLength(1));
|
||||
});
|
||||
|
||||
test('returns partial results when deadline expires', () async {
|
||||
final fake = FakeRelayConnection();
|
||||
addTearDown(fake.dispose);
|
||||
|
||||
final event = _makeEvent('id1');
|
||||
|
||||
fake.onReq = (subId) {
|
||||
fake.push(jsonEncode(['EVENT', subId, event]));
|
||||
// no EOSE — deadline will expire
|
||||
};
|
||||
|
||||
final actor = NostrFetchActor(
|
||||
relayUrls: ['ws://relay1'],
|
||||
eoseDeadline: const Duration(milliseconds: 100),
|
||||
connectionFactory: (_) => fake,
|
||||
);
|
||||
|
||||
final result = await actor.handle(const FetchEvents(_filter));
|
||||
|
||||
expect(result, isA<EventsFetched>());
|
||||
expect((result as EventsFetched).events, hasLength(1));
|
||||
});
|
||||
|
||||
test('tolerates relay failure and returns events from healthy relay',
|
||||
() async {
|
||||
final fakeDown = FakeRelayConnection()..throwOnConnect = true;
|
||||
final fakeUp = FakeRelayConnection();
|
||||
addTearDown(fakeDown.dispose);
|
||||
addTearDown(fakeUp.dispose);
|
||||
|
||||
final event = _makeEvent('id1');
|
||||
fakeUp.onReq = (subId) {
|
||||
fakeUp.push(jsonEncode(['EVENT', subId, event]));
|
||||
fakeUp.push(jsonEncode(['EOSE', subId]));
|
||||
};
|
||||
|
||||
final fakes = {'ws://down': fakeDown, 'ws://up': fakeUp};
|
||||
|
||||
final actor = NostrFetchActor(
|
||||
relayUrls: ['ws://down', 'ws://up'],
|
||||
connectionFactory: (url) => fakes[url]!,
|
||||
);
|
||||
|
||||
final result = await actor.handle(const FetchEvents(_filter));
|
||||
|
||||
expect(result, isA<EventsFetched>());
|
||||
expect((result as EventsFetched).events, hasLength(1));
|
||||
});
|
||||
|
||||
test('sends CLOSE after EOSE', () async {
|
||||
final fake = FakeRelayConnection();
|
||||
addTearDown(fake.dispose);
|
||||
|
||||
final event = _makeEvent('id1');
|
||||
fake.onReq = (subId) {
|
||||
fake.push(jsonEncode(['EVENT', subId, event]));
|
||||
fake.push(jsonEncode(['EOSE', subId]));
|
||||
};
|
||||
|
||||
final actor = NostrFetchActor(
|
||||
relayUrls: ['ws://relay1'],
|
||||
connectionFactory: (_) => fake,
|
||||
);
|
||||
|
||||
await actor.handle(const FetchEvents(_filter));
|
||||
|
||||
final reqMsg = fake.sentMessages.firstWhere(
|
||||
(m) => (jsonDecode(m) as List)[0] == 'REQ',
|
||||
);
|
||||
final subId = (jsonDecode(reqMsg) as List)[1] as String;
|
||||
|
||||
final closeMsgs = fake.sentMessages
|
||||
.where((m) => (jsonDecode(m) as List)[0] == 'CLOSE')
|
||||
.toList();
|
||||
expect(closeMsgs, hasLength(1));
|
||||
expect((jsonDecode(closeMsgs[0]) as List)[1], equals(subId));
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue