import 'package:flutter_test/flutter_test.dart';
import 'package:machweis/core/storage/local_storage.dart';
import 'package:machweis/core/sync/sync_queue_store.dart';
import 'package:shared_preferences/shared_preferences.dart';
class _RecordingTransport implements SyncTransport {
_RecordingTransport({this.fail = false});
final bool fail;
final List<SyncOperation> sent = [];
@override
Future<void> send(SyncOperation operation) async {
sent.add(operation);
if (fail) throw StateError('offline');
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async {
SharedPreferences.setMockInitialValues({});
await LocalStorage.instance.initialize(forcePreferences: true);
await SyncQueueStore.instance.initialize();
});
test('Erstellen und Bearbeiten werden zu einem Upload verdichtet', () async {
final queue = SyncQueueStore.instance;
await queue.enqueue(
entityType: 'report',
entityId: 'compact',
action: SyncAction.create,
payload: {'version': 1},
);
await queue.enqueue(
entityType: 'report',
entityId: 'compact',
action: SyncAction.update,
payload: {'version': 2},
);
expect(queue.operations, hasLength(1));
expect(queue.operations.single.action, SyncAction.create);
expect(queue.operations.single.payload['version'], 2);
await queue.enqueue(
entityType: 'report',
entityId: 'compact',
action: SyncAction.delete,
payload: const {'id': 'compact'},
);
expect(queue.operations, isEmpty);
});
test(
'Fehler werden verzögert wiederholt und Erfolg entfernt den Job',
() async {
final queue = SyncQueueStore.instance;
final now = DateTime(2026, 8, 29, 12);
await queue.enqueue(
entityType: 'report',
entityId: 'retry',
action: SyncAction.update,
payload: const {'version': 3},
);
final failing = _RecordingTransport(fail: true);
await queue.process(failing, now: now);
expect(failing.sent, hasLength(1));
expect(queue.operations.single.attempts, 1);
expect(queue.operations.single.lastError, contains('offline'));
expect(
queue.operations.single.nextAttemptAt,
now.add(const Duration(minutes: 2)),
);
final successful = _RecordingTransport();
await queue.process(successful, now: now.add(const Duration(minutes: 1)));
expect(successful.sent, isEmpty);
await queue.retryNow(queue.operations.single.id);
await queue.process(successful, now: now.add(const Duration(minutes: 1)));
expect(successful.sent, hasLength(1));
expect(queue.operations, isEmpty);
},
);
test(
'Änderung während eines Sends bleibt mit neuer Basisrevision erhalten',
() async {
final queue = SyncQueueStore.instance;
const tenant = 'tenant-concurrent';
const reportId = '40000000-0000-4000-8000-000000000004';
await queue.enqueue(
entityType: 'report',
entityId: reportId,
action: SyncAction.create,
payload: const {'version': 1},
tenantId: tenant,
);
final create = queue.dueReports(tenant, DateTime.now()).single;
queue.beginReportSend([create]);
await queue.enqueue(
entityType: 'report',
entityId: reportId,
action: SyncAction.update,
payload: const {'version': 2},
tenantId: tenant,
);
await queue.applyReportResult(create, {
'status': 'applied',
'row_version': 1,
});
queue.endReportSend([create]);
final successor = queue.dueReports(tenant, DateTime.now()).single;
expect(successor.id, isNot(create.id));
expect(successor.action, SyncAction.update);
expect(successor.baseRevision, 1);
expect(successor.payload['version'], 2);
await queue.applyReportResult(successor, {
'status': 'applied',
'row_version': 2,
});
},
);
}
