feat: make server owner of tabs

This commit is contained in:
2026-10-20 16:15:14 +02:00
parent 5754ae742d
commit 93acfe15a9
26 changed files with 3654 additions and 389 deletions
+34 -68
View File
@@ -249,27 +249,24 @@ void main() {
final tabId = tabIds[random.nextInt(tabIds.length)];
final product = random.nextBool() ? _beer : _wine;
try {
switch (random.nextInt(10)) {
case < 6:
await tabs.addProductToTab(tabId: tabId, product: product);
case < 8:
final tab = (await tabs.getTabById(tabId))!;
if (tab.items.isEmpty) continue;
final item = tab.items[random.nextInt(tab.items.length)];
await tabs.adjustTabItemQuantity(
tabItemId: item.id,
delta: random.nextInt(5) - 2,
);
case 8:
await tabs.closeTab(tabId);
default:
await tabs.deleteTab(tabId);
tabIds.remove(tabId);
tabIds.add((await tabs.createTab(customerName: 'New')).id);
}
} on InsufficientStockException {
// Running out is part of the scenario.
// Running out is part of the scenario: the stock goes negative
switch (random.nextInt(10)) {
case < 6:
await tabs.addProductToTab(tabId: tabId, product: product);
case < 8:
final tab = (await tabs.getTabById(tabId))!;
if (tab.items.isEmpty) continue;
final item = tab.items[random.nextInt(tab.items.length)];
await tabs.adjustTabItemQuantity(
tabItemId: item.id,
delta: random.nextInt(5) - 2,
);
case 8:
await tabs.closeTab(tabId);
default:
await tabs.deleteTab(tabId);
tabIds.remove(tabId);
tabIds.add((await tabs.createTab(customerName: 'New')).id);
}
for (final product in [_beer, _wine]) {
@@ -278,65 +275,48 @@ void main() {
await quantityOnOpenTabs(product.id) +
await soldInHistory(product.id);
expect(accounted, product.stockQuantity, reason: 'step $step');
expect(await stockOf(product.id), greaterThanOrEqualTo(0));
}
}
});
test('can never sell more than is in stock', () async {
test('keeps selling when the stock runs out', () async {
final tab = await tabs.createTab(customerName: 'John');
var sold = 0;
for (var i = 0; i < _wine.stockQuantity + 5; i++) {
try {
await tabs.addProductToTab(tabId: tab.id, product: _wine);
sold++;
} on InsufficientStockException {
// Expected once the wine runs out.
}
await tabs.addProductToTab(tabId: tab.id, product: _wine);
}
expect(sold, _wine.stockQuantity);
expect(await stockOf('wine'), 0);
expect((await tabs.getTabById(tab.id))!.totalInCents, 20 * 450);
expect(await stockOf('wine'), -5);
expect((await tabs.getTabById(tab.id))!.totalInCents, 25 * 450);
});
test('simultaneous orders for the last bottle sell it once', () async {
test('simultaneous orders for the last bottle both go through', () async {
await products.updateProduct(_wine.copyWith(stockQuantity: 1));
final first = await tabs.createTab(customerName: 'A');
final second = await tabs.createTab(customerName: 'B');
final results = await Future.wait([
await Future.wait([
for (final tab in [first, second])
tabs
.addProductToTab(tabId: tab.id, product: _wine)
.then((_) => true)
.catchError(
(_) => false,
test: (e) => e is InsufficientStockException,
),
tabs.addProductToTab(tabId: tab.id, product: _wine),
]);
expect(results.where((ok) => ok), hasLength(1));
expect(await stockOf('wine'), 0);
expect(await quantityOnOpenTabs('wine'), 1);
expect(await stockOf('wine'), -1);
expect(await quantityOnOpenTabs('wine'), 2);
});
test('raising a quantity past the stock adds nothing', () async {
test('raising a quantity past the stock goes below zero', () async {
await products.updateProduct(_wine.copyWith(stockQuantity: 2));
final tab = await tabs.createTab(customerName: 'John');
await tabs.addProductToTab(tabId: tab.id, product: _wine);
await expectLater(
tabs.adjustTabItemQuantity(
tabItemId: await itemId(tab.id, 'wine'),
delta: 5,
),
throwsA(isA<InsufficientStockException>()),
final applied = await tabs.adjustTabItemQuantity(
tabItemId: await itemId(tab.id, 'wine'),
delta: 5,
);
expect(await stockOf('wine'), 1);
expect((await tabs.getTabById(tab.id))!.items.single.quantity, 1);
expect(applied, 5);
expect(await stockOf('wine'), -4);
expect((await tabs.getTabById(tab.id))!.items.single.quantity, 6);
});
test('lowering past zero only returns what was on the tab', () async {
@@ -372,19 +352,5 @@ void main() {
expect(await stockOf('water'), 0);
});
test('a failed add leaves no item behind', () async {
await products.updateProduct(_wine.copyWith(stockQuantity: 0));
final tab = await tabs.createTab(customerName: 'John');
await expectLater(
tabs.addProductToTab(tabId: tab.id, product: _wine),
throwsA(isA<InsufficientStockException>()),
);
final open = (await tabs.getTabById(tab.id))!;
expect(open.items, isEmpty);
expect(open.totalInCents, 0);
});
});
}