151 lines
4.8 KiB
Dart
151 lines
4.8 KiB
Dart
|
|
import 'dart:convert';
|
||
|
|
import 'package:swarm/swarm.dart';
|
||
|
|
import 'package:test/test.dart';
|
||
|
|
import 'fake_relay_connection.dart';
|
||
|
|
|
||
|
|
const _event = '{'
|
||
|
|
'"id":"abc123",'
|
||
|
|
'"pubkey":"pub",'
|
||
|
|
'"created_at":1,'
|
||
|
|
'"kind":1,'
|
||
|
|
'"tags":[],'
|
||
|
|
'"content":"hi",'
|
||
|
|
'"sig":"sig"'
|
||
|
|
'}';
|
||
|
|
|
||
|
|
void main() {
|
||
|
|
group('NostrPublishActor', () {
|
||
|
|
tearDown(() async {});
|
||
|
|
|
||
|
|
test('sends correct EVENT format', () async {
|
||
|
|
final fake = FakeRelayConnection();
|
||
|
|
addTearDown(fake.dispose);
|
||
|
|
|
||
|
|
final actor = NostrPublishActor(
|
||
|
|
relayUrls: ['ws://relay1'],
|
||
|
|
retryConfig: const RetryConfig(maxAttempts: 1),
|
||
|
|
connectionFactory: (_) => fake,
|
||
|
|
);
|
||
|
|
|
||
|
|
await actor.handle(const PublishBatch([_event]));
|
||
|
|
|
||
|
|
final eventMsgs = fake.sentMessages
|
||
|
|
.where((m) => (jsonDecode(m) as List)[0] == 'EVENT')
|
||
|
|
.toList();
|
||
|
|
expect(eventMsgs, hasLength(1));
|
||
|
|
final decoded = jsonDecode(eventMsgs[0]) as List;
|
||
|
|
expect(decoded[0], equals('EVENT'));
|
||
|
|
expect(decoded[1], isA<Map>());
|
||
|
|
expect((decoded[1] as Map)['id'], equals('abc123'));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('returns RelayConfirmed on success', () async {
|
||
|
|
final fake = FakeRelayConnection();
|
||
|
|
addTearDown(fake.dispose);
|
||
|
|
|
||
|
|
final actor = NostrPublishActor(
|
||
|
|
relayUrls: ['ws://relay1'],
|
||
|
|
retryConfig: const RetryConfig(maxAttempts: 1),
|
||
|
|
connectionFactory: (_) => fake,
|
||
|
|
);
|
||
|
|
|
||
|
|
final result = await actor.handle(const PublishBatch([_event]));
|
||
|
|
|
||
|
|
expect(result, isA<BatchPublished>());
|
||
|
|
final outcomes = (result as BatchPublished).outcomes;
|
||
|
|
expect(outcomes['ws://relay1'], isA<RelayConfirmed>());
|
||
|
|
});
|
||
|
|
|
||
|
|
test('returns RelayRejected with no retry', () async {
|
||
|
|
final fake = FakeRelayConnection()..autoRespond = false;
|
||
|
|
addTearDown(fake.dispose);
|
||
|
|
fake.queuedResponses.add(jsonEncode(['OK', 'abc123', false, 'spam']));
|
||
|
|
|
||
|
|
final actor = NostrPublishActor(
|
||
|
|
relayUrls: ['ws://relay1'],
|
||
|
|
retryConfig: const RetryConfig(maxAttempts: 1),
|
||
|
|
connectionFactory: (_) => fake,
|
||
|
|
);
|
||
|
|
|
||
|
|
final result = await actor.handle(const PublishBatch([_event]));
|
||
|
|
|
||
|
|
expect(result, isA<BatchPublished>());
|
||
|
|
final outcomes = (result as BatchPublished).outcomes;
|
||
|
|
expect(outcomes['ws://relay1'], isA<RelayRejected>());
|
||
|
|
expect((outcomes['ws://relay1'] as RelayRejected).reason, equals('spam'));
|
||
|
|
|
||
|
|
final eventMsgs = fake.sentMessages
|
||
|
|
.where((m) => (jsonDecode(m) as List)[0] == 'EVENT')
|
||
|
|
.toList();
|
||
|
|
expect(eventMsgs, hasLength(1));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('returns RelayFailed on timeout and retries', () async {
|
||
|
|
final fake = FakeRelayConnection()..autoRespond = false;
|
||
|
|
addTearDown(fake.dispose);
|
||
|
|
|
||
|
|
final actor = NostrPublishActor(
|
||
|
|
relayUrls: ['ws://relay1'],
|
||
|
|
retryConfig: const RetryConfig(
|
||
|
|
maxAttempts: 2,
|
||
|
|
baseDelay: Duration(milliseconds: 1),
|
||
|
|
jitter: false,
|
||
|
|
),
|
||
|
|
batchTimeout: const Duration(milliseconds: 50),
|
||
|
|
connectionFactory: (_) => fake,
|
||
|
|
);
|
||
|
|
|
||
|
|
final result = await actor.handle(const PublishBatch([_event]));
|
||
|
|
|
||
|
|
expect(result, isA<BatchPublished>());
|
||
|
|
final outcomes = (result as BatchPublished).outcomes;
|
||
|
|
expect(outcomes['ws://relay1'], isA<RelayFailed>());
|
||
|
|
|
||
|
|
final eventMsgs = fake.sentMessages
|
||
|
|
.where((m) => (jsonDecode(m) as List)[0] == 'EVENT')
|
||
|
|
.toList();
|
||
|
|
expect(eventMsgs, hasLength(2));
|
||
|
|
});
|
||
|
|
|
||
|
|
test('returns RelayFailed on connection error', () async {
|
||
|
|
final fake = FakeRelayConnection()..throwOnConnect = true;
|
||
|
|
addTearDown(fake.dispose);
|
||
|
|
|
||
|
|
final actor = NostrPublishActor(
|
||
|
|
relayUrls: ['ws://relay1'],
|
||
|
|
retryConfig: const RetryConfig(maxAttempts: 1),
|
||
|
|
connectionFactory: (_) => fake,
|
||
|
|
);
|
||
|
|
|
||
|
|
final result = await actor.handle(const PublishBatch([_event]));
|
||
|
|
|
||
|
|
expect(result, isA<BatchPublished>());
|
||
|
|
final outcomes = (result as BatchPublished).outcomes;
|
||
|
|
expect(outcomes['ws://relay1'], isA<RelayFailed>());
|
||
|
|
});
|
||
|
|
|
||
|
|
test('fans out to multiple relays', () async {
|
||
|
|
final fake1 = FakeRelayConnection();
|
||
|
|
final fake2 = FakeRelayConnection();
|
||
|
|
addTearDown(fake1.dispose);
|
||
|
|
addTearDown(fake2.dispose);
|
||
|
|
|
||
|
|
final fakes = {'ws://relay1': fake1, 'ws://relay2': fake2};
|
||
|
|
|
||
|
|
final actor = NostrPublishActor(
|
||
|
|
relayUrls: ['ws://relay1', 'ws://relay2'],
|
||
|
|
retryConfig: const RetryConfig(maxAttempts: 1),
|
||
|
|
connectionFactory: (url) => fakes[url]!,
|
||
|
|
);
|
||
|
|
|
||
|
|
final result = await actor.handle(const PublishBatch([_event]));
|
||
|
|
|
||
|
|
expect(result, isA<BatchPublished>());
|
||
|
|
final outcomes = (result as BatchPublished).outcomes;
|
||
|
|
expect(outcomes.keys, containsAll(['ws://relay1', 'ws://relay2']));
|
||
|
|
expect(outcomes['ws://relay1'], isA<RelayConfirmed>());
|
||
|
|
expect(outcomes['ws://relay2'], isA<RelayConfirmed>());
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|