refactor: Projektstruktur in app/ und backend/ aufgeteilt
This commit is contained in:
312
app/test/services/ack_tracker_test.dart
Normal file
312
app/test/services/ack_tracker_test.dart
Normal file
@@ -0,0 +1,312 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:votianlt_app/services/ack_tracker.dart';
|
||||
|
||||
void main() {
|
||||
group('AckTracker', () {
|
||||
late AckTracker tracker;
|
||||
late List<Map<String, String>> retryCalls;
|
||||
late List<Map<String, String>> timeoutCalls;
|
||||
|
||||
setUp(() {
|
||||
retryCalls = [];
|
||||
timeoutCalls = [];
|
||||
tracker = AckTracker(
|
||||
maxRetries: 4,
|
||||
onRetry: (topic, payload) async {
|
||||
retryCalls.add({'topic': topic, 'payload': payload});
|
||||
return true;
|
||||
},
|
||||
onTimeout: (messageId, topic) {
|
||||
timeoutCalls.add({'messageId': messageId, 'topic': topic});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('track', () {
|
||||
test('adds message to pending', () {
|
||||
tracker.track('msg-1', '/server/user/message', '{"data": "test"}');
|
||||
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
expect(tracker.pendingCount, 1);
|
||||
});
|
||||
|
||||
test('can track multiple messages', () {
|
||||
tracker.track('msg-1', '/topic1', 'payload1');
|
||||
tracker.track('msg-2', '/topic2', 'payload2');
|
||||
tracker.track('msg-3', '/topic3', 'payload3');
|
||||
|
||||
expect(tracker.pendingCount, 3);
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
expect(tracker.isPending('msg-2'), true);
|
||||
expect(tracker.isPending('msg-3'), true);
|
||||
});
|
||||
|
||||
test('stores correct topic and payload', () {
|
||||
tracker.track('msg-1', '/server/user/message', '{"key": "value"}');
|
||||
|
||||
final pending = tracker.getPendingMessage('msg-1');
|
||||
expect(pending, isNotNull);
|
||||
expect(pending!.topic, '/server/user/message');
|
||||
expect(pending.jsonPayload, '{"key": "value"}');
|
||||
expect(pending.retryCount, 0);
|
||||
});
|
||||
|
||||
test('overwrites existing message with same ID', () {
|
||||
tracker.track('msg-1', '/old/topic', 'old payload');
|
||||
tracker.track('msg-1', '/new/topic', 'new payload');
|
||||
|
||||
final pending = tracker.getPendingMessage('msg-1');
|
||||
expect(pending!.topic, '/new/topic');
|
||||
expect(pending.jsonPayload, 'new payload');
|
||||
expect(tracker.pendingCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('acknowledge', () {
|
||||
test('removes message from pending', () {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
|
||||
tracker.acknowledge('msg-1');
|
||||
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
expect(tracker.pendingCount, 0);
|
||||
});
|
||||
|
||||
test('does nothing for unknown message ID', () {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
tracker.acknowledge('unknown-msg');
|
||||
|
||||
expect(tracker.pendingCount, 1);
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
});
|
||||
|
||||
test('only removes specified message', () {
|
||||
tracker.track('msg-1', '/topic1', 'payload1');
|
||||
tracker.track('msg-2', '/topic2', 'payload2');
|
||||
|
||||
tracker.acknowledge('msg-1');
|
||||
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
expect(tracker.isPending('msg-2'), true);
|
||||
expect(tracker.pendingCount, 1);
|
||||
});
|
||||
});
|
||||
|
||||
group('isPending', () {
|
||||
test('returns true for tracked message', () {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
});
|
||||
|
||||
test('returns false for untracked message', () {
|
||||
expect(tracker.isPending('unknown'), false);
|
||||
});
|
||||
|
||||
test('returns false after acknowledge', () {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
tracker.acknowledge('msg-1');
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
});
|
||||
});
|
||||
|
||||
group('pendingMessageIds', () {
|
||||
test('returns empty list when no messages pending', () {
|
||||
expect(tracker.pendingMessageIds, isEmpty);
|
||||
});
|
||||
|
||||
test('returns all pending message IDs', () {
|
||||
tracker.track('msg-1', '/topic1', 'payload1');
|
||||
tracker.track('msg-2', '/topic2', 'payload2');
|
||||
|
||||
final ids = tracker.pendingMessageIds;
|
||||
|
||||
expect(ids, containsAll(['msg-1', 'msg-2']));
|
||||
expect(ids.length, 2);
|
||||
});
|
||||
|
||||
test('returns unmodifiable list', () {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
final ids = tracker.pendingMessageIds;
|
||||
|
||||
expect(() => ids.add('new-id'), throwsUnsupportedError);
|
||||
});
|
||||
});
|
||||
|
||||
group('processRetries', () {
|
||||
test('increments retryCount on each call', () async {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
await tracker.processRetries();
|
||||
expect(tracker.getPendingMessage('msg-1')!.retryCount, 1);
|
||||
|
||||
await tracker.processRetries();
|
||||
expect(tracker.getPendingMessage('msg-1')!.retryCount, 2);
|
||||
|
||||
await tracker.processRetries();
|
||||
expect(tracker.getPendingMessage('msg-1')!.retryCount, 3);
|
||||
});
|
||||
|
||||
test('calls onRetry callback with correct parameters', () async {
|
||||
tracker.track('msg-1', '/server/user/message', '{"data": "test"}');
|
||||
|
||||
await tracker.processRetries();
|
||||
|
||||
expect(retryCalls.length, 1);
|
||||
expect(retryCalls[0]['topic'], '/server/user/message');
|
||||
expect(retryCalls[0]['payload'], '{"data": "test"}');
|
||||
});
|
||||
|
||||
test('calls onRetry for each pending message', () async {
|
||||
tracker.track('msg-1', '/topic1', 'payload1');
|
||||
tracker.track('msg-2', '/topic2', 'payload2');
|
||||
|
||||
await tracker.processRetries();
|
||||
|
||||
expect(retryCalls.length, 2);
|
||||
});
|
||||
|
||||
test('calls onTimeout after maxRetries exceeded', () async {
|
||||
tracker.track('msg-1', '/timeout/topic', 'payload');
|
||||
|
||||
// Process until maxRetries reached
|
||||
for (var i = 0; i < 4; i++) {
|
||||
await tracker.processRetries();
|
||||
}
|
||||
|
||||
expect(timeoutCalls, isEmpty);
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
|
||||
// One more retry should trigger timeout
|
||||
await tracker.processRetries();
|
||||
|
||||
expect(timeoutCalls.length, 1);
|
||||
expect(timeoutCalls[0]['messageId'], 'msg-1');
|
||||
expect(timeoutCalls[0]['topic'], '/timeout/topic');
|
||||
});
|
||||
|
||||
test('removes message after timeout', () async {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
// Process until timeout
|
||||
for (var i = 0; i <= 4; i++) {
|
||||
await tracker.processRetries();
|
||||
}
|
||||
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
expect(tracker.pendingCount, 0);
|
||||
});
|
||||
|
||||
test('does not retry when isConnected is false', () async {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
await tracker.processRetries(isConnected: false);
|
||||
|
||||
expect(retryCalls, isEmpty);
|
||||
expect(tracker.getPendingMessage('msg-1')!.retryCount, 0);
|
||||
});
|
||||
|
||||
test('still times out when disconnected after max retries', () async {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
// Manually set retry count to max (simulating previous retries)
|
||||
final pending = tracker.getPendingMessage('msg-1')!;
|
||||
pending.retryCount = 4;
|
||||
|
||||
await tracker.processRetries(isConnected: false);
|
||||
|
||||
expect(timeoutCalls.length, 1);
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
});
|
||||
|
||||
test('does nothing when no messages pending', () async {
|
||||
await tracker.processRetries();
|
||||
|
||||
expect(retryCalls, isEmpty);
|
||||
expect(timeoutCalls, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('clearAll', () {
|
||||
test('removes all pending messages', () {
|
||||
tracker.track('msg-1', '/topic1', 'payload1');
|
||||
tracker.track('msg-2', '/topic2', 'payload2');
|
||||
|
||||
tracker.clearAll();
|
||||
|
||||
expect(tracker.pendingCount, 0);
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
expect(tracker.isPending('msg-2'), false);
|
||||
});
|
||||
});
|
||||
|
||||
group('clearForTopic', () {
|
||||
test('removes only messages for matching topic', () {
|
||||
tracker.track('msg-1', '/server/login', 'login1');
|
||||
tracker.track('msg-2', '/server/login', 'login2');
|
||||
tracker.track('msg-3', '/server/user/message', 'message');
|
||||
|
||||
tracker.clearForTopic('/server/login');
|
||||
|
||||
expect(tracker.isPending('msg-1'), false);
|
||||
expect(tracker.isPending('msg-2'), false);
|
||||
expect(tracker.isPending('msg-3'), true);
|
||||
expect(tracker.pendingCount, 1);
|
||||
});
|
||||
|
||||
test('does nothing when no matching topic', () {
|
||||
tracker.track('msg-1', '/server/user/message', 'payload');
|
||||
|
||||
tracker.clearForTopic('/server/login');
|
||||
|
||||
expect(tracker.pendingCount, 1);
|
||||
expect(tracker.isPending('msg-1'), true);
|
||||
});
|
||||
});
|
||||
|
||||
group('without callbacks', () {
|
||||
test('processRetries works without onRetry callback', () async {
|
||||
final noCallbackTracker = AckTracker(maxRetries: 2);
|
||||
noCallbackTracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
// Should not throw
|
||||
await noCallbackTracker.processRetries();
|
||||
|
||||
expect(noCallbackTracker.getPendingMessage('msg-1')!.retryCount, 1);
|
||||
});
|
||||
|
||||
test('processRetries works without onTimeout callback', () async {
|
||||
final noCallbackTracker = AckTracker(maxRetries: 1);
|
||||
noCallbackTracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
// Process until timeout
|
||||
await noCallbackTracker.processRetries();
|
||||
await noCallbackTracker.processRetries();
|
||||
|
||||
// Should be removed even without callback
|
||||
expect(noCallbackTracker.isPending('msg-1'), false);
|
||||
});
|
||||
});
|
||||
|
||||
group('PendingMessage', () {
|
||||
test('stores sentAt timestamp', () {
|
||||
final before = DateTime.now();
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
final after = DateTime.now();
|
||||
|
||||
final pending = tracker.getPendingMessage('msg-1')!;
|
||||
|
||||
expect(pending.sentAt.isAfter(before) || pending.sentAt == before, true);
|
||||
expect(pending.sentAt.isBefore(after) || pending.sentAt == after, true);
|
||||
});
|
||||
|
||||
test('initializes retryCount to 0', () {
|
||||
tracker.track('msg-1', '/topic', 'payload');
|
||||
|
||||
expect(tracker.getPendingMessage('msg-1')!.retryCount, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user