refactor: Projektstruktur in app/ und backend/ aufgeteilt

This commit is contained in:
2026-03-24 15:06:44 +01:00
parent 5f5d5995c5
commit 2673ef658d
449 changed files with 28551 additions and 167 deletions

View 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);
});
});
});
}