feat: add historical data

This commit is contained in:
2026-10-19 23:31:54 +02:00
parent b0074aeb3f
commit 5754ae742d
10 changed files with 1211 additions and 4 deletions
+148
View File
@@ -443,4 +443,152 @@ void main() {
},
);
});
group('tab events', () {
Future<void> queueEvent(String id, DateTime createdAt) async {
await database
.into(database.tabEvents)
.insert(
TabEventsCompanion.insert(
id: id,
payload: jsonEncode({'id': id, 'type': 'line_added'}),
createdAt: createdAt,
),
);
}
Future<List<String>> outboxIds() async {
final rows = await database.select(database.tabEvents).get();
return [for (final row in rows) row.id];
}
List<String> sentIds([int request = 0]) => [
for (final event in server.syncRequests[request]['events'] as List)
event['id'] as String,
];
test('sends at most 500 events, oldest first', () async {
final start = DateTime(2026, 10, 24, 20);
// Inserted newest first, so only the timestamps give the order
for (var i = 501; i >= 1; i--) {
await queueEvent('e$i', start.add(Duration(minutes: i)));
}
await service.sync();
expect(sentIds(), [for (var i = 1; i <= 500; i++) 'e$i']);
expect(server.syncRequests.single['events'].first, {
'id': 'e1',
'type': 'line_added',
});
});
test('deletes exactly the acknowledged and rejected events', () async {
final now = DateTime.now();
for (final id in ['e1', 'e2', 'e3']) {
await queueEvent(id, now);
}
server.respondWith({
'acknowledgedEventIds': ['e1'],
'rejectedEvents': [
{'id': 'e2', 'reason': 'unknown type'},
{'reason': 'not an object'},
],
});
await service.sync();
expect(await outboxIds(), ['e3']);
});
test('a failed sync deletes nothing', () async {
await queueEvent('e1', DateTime.now());
server.syncResponse = http.Response(
jsonEncode({
'acknowledgedEventIds': ['e1'],
}),
500,
);
await service.sync();
expect(await outboxIds(), ['e1']);
});
test('an older server without event fields keeps the events', () async {
await queueEvent('e1', DateTime.now());
await service.sync();
expect(await outboxIds(), ['e1']);
});
test('events created during the sync wait for the next one', () async {
final tab = await barTabService.createTab(customerName: 'Alice');
await barTabService.addProductToTab(tabId: tab.id, product: _beer);
server.holdSync = Completer<void>();
final running = service.sync();
await pumpEventQueue();
await barTabService.addProductToTab(tabId: tab.id, product: _beer);
server.respondWith({'acknowledgedEventIds': sentIds()});
server.holdSync!.complete();
server.holdSync = null;
await running;
expect(sentIds(), hasLength(1));
final remaining = await outboxIds();
expect(remaining, hasLength(1));
expect(remaining, isNot(contains(sentIds().single)));
});
test('a close_tab command writes one settled event', () async {
final tab = await barTabService.createTab(customerName: 'Alice');
await barTabService.addProductToTab(tabId: tab.id, product: _beer);
await barTabService.addProductToTab(tabId: tab.id, product: _beer);
final current = (await barTabService.getTabById(tab.id))!;
server.respondWith({
'commands': [
_closeCommand(
id: 'cmd-1',
tabId: tab.id,
expectedVersion: current.version,
),
],
});
// The second sync receives the same command again
await service.sync();
await service.sync();
final settled = [
for (final event in server.syncRequests.last['events'] as List)
if (event['type'] == 'settled') event,
];
expect(settled, hasLength(1));
expect(settled.single['commandId'], 'cmd-1');
expect(settled.single['paymentMethod'], 'payconiq');
expect(settled.single['amountInCents'], 800);
expect(settled.single['customerName'], 'Alice');
});
test('a conflicting close_tab command writes no settled event', () async {
final tab = await barTabService.createTab(customerName: 'Alice');
await barTabService.addProductToTab(tabId: tab.id, product: _beer);
server.respondWith({
'commands': [
_closeCommand(id: 'cmd-1', tabId: tab.id, expectedVersion: 1),
],
});
await service.sync();
await service.sync();
final types = [
for (final event in server.syncRequests.last['events'] as List)
event['type'],
];
expect(types, ['line_added']);
});
});
}