import 'package:angel3_event_bus/res/app_event.dart'; import 'package:test/test.dart'; import 'package:angel3_container/angel3_container.dart'; import 'package:angel3_mq/mq.dart'; import 'package:angel3_events/dispatcher.dart'; // Replace with the actual import path void main() { late Dispatcher dispatcher; late MockMQClient mockMQClient; setUp(() { var container = Container(EmptyReflector()); dispatcher = Dispatcher(container); mockMQClient = MockMQClient(); dispatcher.mqClient = mockMQClient; // Use the setter // Clear the queue before each test mockMQClient.queuedMessages.clear(); }); group('Dispatcher', () { test('listen and dispatch', () async { var callCount = 0; dispatcher.listen('test_event', (dynamic event, dynamic payload) { expect(event, equals('test_event')); expect(payload, equals(['test_payload'])); callCount++; }); await dispatcher.dispatch('test_event', ['test_payload']); expect(callCount, equals(1)); }); test('wildcard listener', () async { var callCount = 0; dispatcher.listen('test.*', (dynamic event, dynamic payload) { expect(event, matches(RegExp(r'^test\.'))); callCount++; }); await dispatcher.dispatch('test.one', ['payload1']); await dispatcher.dispatch('test.two', ['payload2']); expect(callCount, equals(2)); }); test('hasListeners', () { dispatcher.listen('test_event', (dynamic event, dynamic payload) {}); expect(dispatcher.hasListeners('test_event'), isTrue); expect(dispatcher.hasListeners('non_existent_event'), isFalse); }); test('until', () async { // Test without pushing the event immediately var futureResult = dispatcher.until('test_event'); // Use a small delay to ensure the until listener is set up await Future.delayed(Duration(milliseconds: 10)); await dispatcher.dispatch('test_event', ['test_payload']); var result = await futureResult; expect(result, equals(['test_payload'])); // Test with pushing the event immediately result = await dispatcher.until('another_test_event', ['another_payload']); expect(result, equals(['another_payload'])); }, timeout: Timeout(Duration(seconds: 5))); // Add a reasonable timeout test('forget', () async { var callCount = 0; dispatcher.listen('test_event', (dynamic event, dynamic payload) { callCount++; }); await dispatcher.dispatch('test_event'); expect(callCount, equals(1)); dispatcher.forget('test_event'); await dispatcher.dispatch('test_event'); expect(callCount, equals(1)); // Should not increase }); test('push and flush', () async { print('Starting push and flush test'); // Push 4 messages for (var i = 0; i < 4; i++) { dispatcher.push('delayed_event', ['delayed_payload_$i']); } // Verify that 4 messages were queued expect(mockMQClient.queuedMessages['delayed_events_queue']?.length, equals(4), reason: 'Should have queued exactly 4 messages'); print( 'Queued messages: ${mockMQClient.queuedMessages['delayed_events_queue']?.length}'); var callCount = 0; var processedPayloads = []; // Remove any existing listeners dispatcher.forget('delayed_event'); dispatcher.listen('delayed_event', (dynamic event, dynamic payload) { print('Listener called with payload: $payload'); expect(event, equals('delayed_event')); expect(payload[0], startsWith('delayed_payload_')); processedPayloads.add(payload[0]); callCount++; }); await dispatcher.flush('delayed_event'); print('After flush - Call count: $callCount'); print('Processed payloads: $processedPayloads'); expect(callCount, equals(4), reason: 'Should process exactly 4 messages'); expect(processedPayloads.toSet().length, equals(4), reason: 'All payloads should be unique'); // Verify that all messages were removed from the queue expect(mockMQClient.queuedMessages['delayed_events_queue']?.length, equals(0), reason: 'Queue should be empty after flush'); // Flush again to ensure no more messages are processed await dispatcher.flush('delayed_event'); expect(callCount, equals(4), reason: 'Should still be 4 after second flush'); }); test('shouldBroadcast', () async { var broadcastEvent = BroadcastTestEvent(); var callCount = 0; dispatcher.listen('BroadcastTestEvent', (dynamic event, dynamic payload) { callCount++; }); await dispatcher.dispatch(broadcastEvent); expect(callCount, equals(1)); }); test('shouldQueue', () async { var queueEvent = QueueTestEvent(); await dispatcher.dispatch(queueEvent); expect(mockMQClient.queuedMessages['events_queue'], isNotEmpty); expect(mockMQClient.queuedMessages['events_queue']!.first.payload, containsPair('event', 'QueueTestEvent')); }); test('forgetPushed removes only pushed events', () { dispatcher.listen('event_pushed', (_, __) {}); dispatcher.listen('normal_event', (_, __) {}); dispatcher.forgetPushed(); expect(dispatcher.hasListeners('event_pushed'), isFalse); expect(dispatcher.hasListeners('normal_event'), isTrue); }); test('setQueueResolver and setTransactionManagerResolver', () { var queueResolverCalled = false; var transactionManagerResolverCalled = false; dispatcher.setQueueResolver(() { queueResolverCalled = true; }); dispatcher.setTransactionManagerResolver(() { transactionManagerResolverCalled = true; }); // Trigger the resolvers dispatcher.triggerQueueResolver(); dispatcher.triggerTransactionManagerResolver(); expect(queueResolverCalled, isTrue); expect(transactionManagerResolverCalled, isTrue); }); test('getRawListeners returns unmodifiable map', () { dispatcher.listen('test_event', (_, __) {}); var rawListeners = dispatcher.getRawListeners(); expect(rawListeners, isA>>()); expect(() => rawListeners['new_event'] = [], throwsUnsupportedError); }); test('multiple listeners for same event', () async { var callCount1 = 0; var callCount2 = 0; dispatcher.listen('multi_event', (_, __) => callCount1++); dispatcher.listen('multi_event', (_, __) => callCount2++); await dispatcher.dispatch('multi_event'); expect(callCount1, equals(1)); expect(callCount2, equals(1)); }); }); } abstract class MQClientWrapper { Stream fetchQueue(String queueId); void sendMessage({ required Message message, String? exchangeName, String? routingKey, }); String declareQueue(String queueId); void declareExchange({ required String exchangeName, required ExchangeType exchangeType, }); void bindQueue({ required String queueId, required String exchangeName, String? bindingKey, }); void close(); } class RealMQClientWrapper implements MQClientWrapper { final MQClient _client; RealMQClientWrapper(this._client); @override Stream fetchQueue(String queueId) => _client.fetchQueue(queueId); @override void sendMessage({ required Message message, String? exchangeName, String? routingKey, }) => _client.sendMessage( message: message, exchangeName: exchangeName, routingKey: routingKey, ); @override String declareQueue(String queueId) => _client.declareQueue(queueId); @override void declareExchange({ required String exchangeName, required ExchangeType exchangeType, }) => _client.declareExchange( exchangeName: exchangeName, exchangeType: exchangeType, ); @override void bindQueue({ required String queueId, required String exchangeName, String? bindingKey, }) => _client.bindQueue( queueId: queueId, exchangeName: exchangeName, bindingKey: bindingKey, ); @override void close() => _client.close(); } class MockMQClient implements MQClient { Map> queuedMessages = {}; int _messageIdCounter = 0; void queueMessage(String queueName, Message message) { queuedMessages.putIfAbsent(queueName, () => []).add(message); print( 'Queued message. Queue $queueName now has ${queuedMessages[queueName]?.length} messages'); } @override String declareQueue(String queueId) { queuedMessages[queueId] = []; return queueId; } @override void deleteQueue(String queueId) { queuedMessages.remove(queueId); } @override Stream fetchQueue(String queueId) { print('Fetching queue: $queueId'); return Stream.fromIterable(queuedMessages[queueId] ?? []); } @override void sendMessage({ required Message message, String? exchangeName, String? routingKey, }) { print('Sending message to queue: $routingKey'); final newMessage = Message( payload: message.payload, headers: message.headers, timestamp: message.timestamp, id: 'msg_${_messageIdCounter++}', ); queueMessage(routingKey ?? '', newMessage); } @override Message? getLatestMessage(String queueId) { final messages = queuedMessages[queueId]; return messages?.isNotEmpty == true ? messages!.last : null; } @override void bindQueue({ required String queueId, required String exchangeName, String? bindingKey, }) { // Implement if needed for your tests } @override void unbindQueue({ required String queueId, required String exchangeName, String? bindingKey, }) { // Implement if needed for your tests } @override void declareExchange({ required String exchangeName, required ExchangeType exchangeType, }) { // Implement if needed for your tests } @override void deleteExchange(String exchangeName) { // Implement if needed for your tests } @override List listQueues() { return queuedMessages.keys.toList(); } @override void close() { queuedMessages.clear(); } @override void deleteMessage(String queueId, Message message) { print('Deleting message from queue: $queueId'); queuedMessages[queueId]?.removeWhere((m) => m.id == message.id); print( 'After deletion, queue $queueId has ${queuedMessages[queueId]?.length} messages'); } } class BroadcastTestEvent implements AppEvent, ShouldBroadcast { @override List get props => []; @override bool? get stringify => true; @override DateTime get timestamp => DateTime.now(); } class QueueTestEvent implements AppEvent, ShouldQueue { @override List get props => []; @override bool? get stringify => true; @override DateTime get timestamp => DateTime.now(); } // This is a simple implementation of Reflector that does nothing class EmptyReflector implements Reflector { const EmptyReflector(); @override ReflectedType reflectType(Type type) { throw UnimplementedError(); } @override ReflectedInstance reflectInstance(Object object) { throw UnimplementedError(); } @override ReflectedType reflectFutureOf(Type type) { throw UnimplementedError(); } @override String? getName(Symbol symbol) { // TODO: implement getName throw UnimplementedError(); } @override ReflectedClass? reflectClass(Type clazz) { // TODO: implement reflectClass throw UnimplementedError(); } @override ReflectedFunction? reflectFunction(Function function) { // TODO: implement reflectFunction throw UnimplementedError(); } }