fix: Make transactions atomic / AutoLogout
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:kooltab2/app/app_lifecycle_lock.dart';
|
||||
|
||||
void main() {
|
||||
test('locks when the app is no longer active', () {
|
||||
var lockCount = 0;
|
||||
final observer = AppLifecycleLockObserver(onLock: () => lockCount++);
|
||||
|
||||
for (final state in [
|
||||
AppLifecycleState.inactive,
|
||||
AppLifecycleState.hidden,
|
||||
AppLifecycleState.paused,
|
||||
AppLifecycleState.detached,
|
||||
]) {
|
||||
observer.didChangeAppLifecycleState(state);
|
||||
}
|
||||
|
||||
expect(lockCount, 4);
|
||||
});
|
||||
|
||||
test('does not lock while the app is active', () {
|
||||
var lockCount = 0;
|
||||
final observer = AppLifecycleLockObserver(onLock: () => lockCount++);
|
||||
|
||||
observer.didChangeAppLifecycleState(AppLifecycleState.resumed);
|
||||
|
||||
expect(lockCount, 0);
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:kooltab2/models/product.dart';
|
||||
import 'package:kooltab2/models/tab_item.dart';
|
||||
import 'package:kooltab2/services/bar_tab_service.dart';
|
||||
import 'package:kooltab2/services/product_service.dart';
|
||||
@@ -27,38 +26,28 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'does not increase a tab item when its product is out of stock',
|
||||
() async {
|
||||
const product = Product(
|
||||
id: 'prod-1',
|
||||
name: 'Chips',
|
||||
category: 'Snacks',
|
||||
stockQuantity: 0,
|
||||
lowStockThreshold: 5,
|
||||
priceInCents: 150,
|
||||
);
|
||||
const item = TabItem(
|
||||
id: 'item-1',
|
||||
tabId: 'tab-1',
|
||||
productId: 'prod-1',
|
||||
productName: 'Chips',
|
||||
quantity: 20,
|
||||
unitPriceInCents: 150,
|
||||
);
|
||||
test('propagates an insufficient-stock error from the transaction', () async {
|
||||
const item = TabItem(
|
||||
id: 'item-1',
|
||||
tabId: 'tab-1',
|
||||
productId: 'prod-1',
|
||||
productName: 'Chips',
|
||||
quantity: 20,
|
||||
unitPriceInCents: 150,
|
||||
);
|
||||
|
||||
when(
|
||||
() => productService.getProducts(),
|
||||
).thenAnswer((_) async => [product]);
|
||||
await inventory.load();
|
||||
when(
|
||||
() => barTabService.adjustTabItemQuantity(tabItemId: 'item-1', delta: 1),
|
||||
).thenAnswer((_) async => throw const InsufficientStockException());
|
||||
|
||||
await expectLater(
|
||||
viewModel.changeItemQuantity(item, 21),
|
||||
throwsA(isA<InsufficientStockException>()),
|
||||
);
|
||||
await expectLater(
|
||||
viewModel.changeItemQuantity(item, 1),
|
||||
throwsA(isA<InsufficientStockException>()),
|
||||
);
|
||||
|
||||
expect(viewModel.errorMessage, isNull);
|
||||
verifyNoMoreInteractions(barTabService);
|
||||
},
|
||||
);
|
||||
expect(viewModel.errorMessage, isNull);
|
||||
verify(
|
||||
() => barTabService.adjustTabItemQuantity(tabItemId: 'item-1', delta: 1),
|
||||
).called(1);
|
||||
});
|
||||
}
|
||||
|
||||
+130
-50
@@ -4,14 +4,28 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:kooltab2/database/app_database.dart';
|
||||
import 'package:kooltab2/models/product.dart';
|
||||
import 'package:kooltab2/services/bar_tab_service.dart';
|
||||
import 'package:kooltab2/services/product_service.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late DriftBarTabService service;
|
||||
|
||||
setUp(() {
|
||||
setUp(() async {
|
||||
database = AppDatabase(NativeDatabase.memory());
|
||||
service = DriftBarTabService(database: database);
|
||||
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'prod-1',
|
||||
name: 'Test Beer',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 100,
|
||||
lowStockThreshold: 10,
|
||||
priceInCents: 400,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
@@ -59,14 +73,16 @@ void main() {
|
||||
|
||||
test('returns only open tabs', () async {
|
||||
await service.createTab(customerName: 'Open Tab');
|
||||
await database.into(database.barTabs).insert(
|
||||
BarTabsCompanion.insert(
|
||||
id: 'closed-tab',
|
||||
customerName: 'Closed Tab',
|
||||
status: const Value('closed'),
|
||||
openedAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.barTabs)
|
||||
.insert(
|
||||
BarTabsCompanion.insert(
|
||||
id: 'closed-tab',
|
||||
customerName: 'Closed Tab',
|
||||
status: const Value('closed'),
|
||||
openedAt: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
final tabs = await service.getOpenTabs();
|
||||
|
||||
@@ -76,22 +92,26 @@ void main() {
|
||||
|
||||
test('tabs sorted by openedAt descending', () async {
|
||||
final now = DateTime.now();
|
||||
await database.into(database.barTabs).insert(
|
||||
BarTabsCompanion.insert(
|
||||
id: 'tab-first',
|
||||
customerName: 'First',
|
||||
status: const Value('open'),
|
||||
openedAt: now.subtract(const Duration(hours: 1)),
|
||||
),
|
||||
);
|
||||
await database.into(database.barTabs).insert(
|
||||
BarTabsCompanion.insert(
|
||||
id: 'tab-second',
|
||||
customerName: 'Second',
|
||||
status: const Value('open'),
|
||||
openedAt: now,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.barTabs)
|
||||
.insert(
|
||||
BarTabsCompanion.insert(
|
||||
id: 'tab-first',
|
||||
customerName: 'First',
|
||||
status: const Value('open'),
|
||||
openedAt: now.subtract(const Duration(hours: 1)),
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.barTabs)
|
||||
.insert(
|
||||
BarTabsCompanion.insert(
|
||||
id: 'tab-second',
|
||||
customerName: 'Second',
|
||||
status: const Value('open'),
|
||||
openedAt: now,
|
||||
),
|
||||
);
|
||||
|
||||
final tabs = await service.getOpenTabs();
|
||||
|
||||
@@ -152,9 +172,38 @@ void main() {
|
||||
final updatedTab = await service.getTabById(tab.id);
|
||||
expect(updatedTab!.items.first.lineTotalInCents, 900);
|
||||
});
|
||||
|
||||
test('decreases stock as part of adding the item', () async {
|
||||
final tab = await service.createTab(customerName: 'John');
|
||||
final product = createTestProduct();
|
||||
|
||||
await service.addProductToTab(tabId: tab.id, product: product);
|
||||
|
||||
final storedProduct = await (database.select(
|
||||
database.products,
|
||||
)..where((row) => row.id.equals(product.id))).getSingle();
|
||||
expect(storedProduct.stockQuantity, 99);
|
||||
});
|
||||
|
||||
test('rolls back the tab insert when stock is unavailable', () async {
|
||||
final tab = await service.createTab(customerName: 'John');
|
||||
final product = createTestProduct();
|
||||
|
||||
await (database.update(database.products)
|
||||
..where((row) => row.id.equals(product.id)))
|
||||
.write(const ProductsCompanion(stockQuantity: Value(0)));
|
||||
|
||||
await expectLater(
|
||||
service.addProductToTab(tabId: tab.id, product: product),
|
||||
throwsA(isA<InsufficientStockException>()),
|
||||
);
|
||||
|
||||
final updatedTab = await service.getTabById(tab.id);
|
||||
expect(updatedTab!.items, isEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('updateTabItemQuantity', () {
|
||||
group('adjustTabItemQuantity', () {
|
||||
test('updates item quantity', () async {
|
||||
final tab = await service.createTab(customerName: 'John');
|
||||
final product = createTestProduct();
|
||||
@@ -162,40 +211,67 @@ void main() {
|
||||
|
||||
final tabItemId = (await service.getTabById(tab.id))!.items.first.id;
|
||||
|
||||
await service.updateTabItemQuantity(
|
||||
tabItemId: tabItemId,
|
||||
quantity: 5,
|
||||
);
|
||||
await service.adjustTabItemQuantity(tabItemId: tabItemId, delta: 4);
|
||||
|
||||
final updatedTab = await service.getTabById(tab.id);
|
||||
expect(updatedTab!.items.first.quantity, 5);
|
||||
});
|
||||
|
||||
test('deletes item when quantity is zero', () async {
|
||||
test('deletes item when the delta removes all quantity', () async {
|
||||
final tab = await service.createTab(customerName: 'John');
|
||||
final product = createTestProduct();
|
||||
await service.addProductToTab(tabId: tab.id, product: product);
|
||||
|
||||
final tabItemId = (await service.getTabById(tab.id))!.items.first.id;
|
||||
|
||||
await service.updateTabItemQuantity(tabItemId: tabItemId, quantity: 0);
|
||||
await service.adjustTabItemQuantity(tabItemId: tabItemId, delta: -1);
|
||||
|
||||
final updatedTab = await service.getTabById(tab.id);
|
||||
expect(updatedTab!.items, isEmpty);
|
||||
});
|
||||
|
||||
test('deletes item when quantity is negative', () async {
|
||||
test('restores stock when quantity decreases', () async {
|
||||
final tab = await service.createTab(customerName: 'John');
|
||||
final product = createTestProduct();
|
||||
await service.addProductToTab(tabId: tab.id, product: product);
|
||||
|
||||
final tabItemId = (await service.getTabById(tab.id))!.items.first.id;
|
||||
|
||||
await service.updateTabItemQuantity(tabItemId: tabItemId, quantity: -1);
|
||||
await service.adjustTabItemQuantity(tabItemId: tabItemId, delta: -1);
|
||||
|
||||
final updatedTab = await service.getTabById(tab.id);
|
||||
expect(updatedTab!.items, isEmpty);
|
||||
|
||||
final updatedProduct = await (database.select(
|
||||
database.products,
|
||||
)..where((row) => row.id.equals(product.id))).getSingle();
|
||||
expect(updatedProduct.stockQuantity, 100);
|
||||
});
|
||||
|
||||
test(
|
||||
'applies concurrent deltas without losing stock consistency',
|
||||
() async {
|
||||
final tab = await service.createTab(customerName: 'John');
|
||||
final product = createTestProduct();
|
||||
await service.addProductToTab(tabId: tab.id, product: product);
|
||||
|
||||
final tabItemId = (await service.getTabById(tab.id))!.items.first.id;
|
||||
|
||||
final deltas = await Future.wait([
|
||||
service.adjustTabItemQuantity(tabItemId: tabItemId, delta: 1),
|
||||
service.adjustTabItemQuantity(tabItemId: tabItemId, delta: 1),
|
||||
]);
|
||||
|
||||
expect(deltas, [1, 1]);
|
||||
final updatedTab = await service.getTabById(tab.id);
|
||||
expect(updatedTab!.items.first.quantity, 3);
|
||||
|
||||
final updatedProduct = await (database.select(
|
||||
database.products,
|
||||
)..where((row) => row.id.equals(product.id))).getSingle();
|
||||
expect(updatedProduct.stockQuantity, 97);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('closeTab', () {
|
||||
@@ -257,22 +333,26 @@ void main() {
|
||||
|
||||
test('returns closed tabs sorted by closedAt descending', () async {
|
||||
final now = DateTime.now();
|
||||
await database.into(database.closedTabs).insert(
|
||||
ClosedTabsCompanion.insert(
|
||||
id: 'closed-first',
|
||||
originalTabId: 'original-1',
|
||||
customerName: 'First',
|
||||
closedAt: now.subtract(const Duration(hours: 1)),
|
||||
),
|
||||
);
|
||||
await database.into(database.closedTabs).insert(
|
||||
ClosedTabsCompanion.insert(
|
||||
id: 'closed-second',
|
||||
originalTabId: 'original-2',
|
||||
customerName: 'Second',
|
||||
closedAt: now,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.closedTabs)
|
||||
.insert(
|
||||
ClosedTabsCompanion.insert(
|
||||
id: 'closed-first',
|
||||
originalTabId: 'original-1',
|
||||
customerName: 'First',
|
||||
closedAt: now.subtract(const Duration(hours: 1)),
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.closedTabs)
|
||||
.insert(
|
||||
ClosedTabsCompanion.insert(
|
||||
id: 'closed-second',
|
||||
originalTabId: 'original-2',
|
||||
customerName: 'Second',
|
||||
closedAt: now,
|
||||
),
|
||||
);
|
||||
|
||||
final closedTabs = await service.getClosedTabs();
|
||||
|
||||
|
||||
+171
-101
@@ -26,27 +26,31 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns only active products', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '1',
|
||||
name: 'Active Product',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '2',
|
||||
name: 'Inactive Product',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 5,
|
||||
lowStockThreshold: 1,
|
||||
priceInCents: 300,
|
||||
active: const Value(false),
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '1',
|
||||
name: 'Active Product',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '2',
|
||||
name: 'Inactive Product',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 5,
|
||||
lowStockThreshold: 1,
|
||||
priceInCents: 300,
|
||||
active: const Value(false),
|
||||
),
|
||||
);
|
||||
|
||||
final products = await service.getProducts();
|
||||
|
||||
@@ -55,26 +59,30 @@ void main() {
|
||||
});
|
||||
|
||||
test('returns products sorted by name', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '1',
|
||||
name: 'Zebra',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '2',
|
||||
name: 'Apple',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 5,
|
||||
lowStockThreshold: 1,
|
||||
priceInCents: 300,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '1',
|
||||
name: 'Zebra',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: '2',
|
||||
name: 'Apple',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 5,
|
||||
lowStockThreshold: 1,
|
||||
priceInCents: 300,
|
||||
),
|
||||
);
|
||||
|
||||
final products = await service.getProducts();
|
||||
|
||||
@@ -85,16 +93,18 @@ void main() {
|
||||
|
||||
group('getProductById', () {
|
||||
test('returns product when exists', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'prod-123',
|
||||
name: 'Test Product',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'prod-123',
|
||||
name: 'Test Product',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
final product = await service.getProductById('prod-123');
|
||||
|
||||
@@ -128,16 +138,18 @@ void main() {
|
||||
|
||||
group('updateProduct', () {
|
||||
test('updates product fields', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'update-test',
|
||||
name: 'Original',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'update-test',
|
||||
name: 'Original',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
await service.updateProduct(
|
||||
const Product(
|
||||
@@ -160,16 +172,18 @@ void main() {
|
||||
|
||||
group('deleteProduct', () {
|
||||
test('removes product from database', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'delete-test',
|
||||
name: 'To Delete',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'delete-test',
|
||||
name: 'To Delete',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
await service.deleteProduct('delete-test');
|
||||
|
||||
@@ -180,16 +194,18 @@ void main() {
|
||||
|
||||
group('decreaseStock', () {
|
||||
test('decreases stock quantity', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'stock-test',
|
||||
name: 'Stock Test',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'stock-test',
|
||||
name: 'Stock Test',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
await service.decreaseStock('stock-test', 3);
|
||||
|
||||
@@ -205,16 +221,18 @@ void main() {
|
||||
});
|
||||
|
||||
test('throws when not enough stock', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'low-stock',
|
||||
name: 'Low Stock',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 3,
|
||||
lowStockThreshold: 1,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'low-stock',
|
||||
name: 'Low Stock',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 3,
|
||||
lowStockThreshold: 1,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
() => service.decreaseStock('low-stock', 5),
|
||||
@@ -225,16 +243,18 @@ void main() {
|
||||
|
||||
group('increaseStock', () {
|
||||
test('increases stock quantity', () async {
|
||||
await database.into(database.products).insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'increase-test',
|
||||
name: 'Increase Test',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'increase-test',
|
||||
name: 'Increase Test',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
await service.increaseStock('increase-test', 5);
|
||||
|
||||
@@ -248,6 +268,56 @@ void main() {
|
||||
throwsA(isA<Exception>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects non-positive stock adjustments', () async {
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'invalid-adjustment',
|
||||
name: 'Invalid Adjustment',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 10,
|
||||
lowStockThreshold: 2,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
() => service.decreaseStock('invalid-adjustment', 0),
|
||||
throwsA(isA<ArgumentError>()),
|
||||
);
|
||||
expect(
|
||||
() => service.increaseStock('invalid-adjustment', -1),
|
||||
throwsA(isA<ArgumentError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('does not allow concurrent decrements below zero', () async {
|
||||
await database
|
||||
.into(database.products)
|
||||
.insert(
|
||||
ProductsCompanion.insert(
|
||||
id: 'concurrent-stock',
|
||||
name: 'Concurrent Stock',
|
||||
category: 'Drinks',
|
||||
stockQuantity: 1,
|
||||
lowStockThreshold: 0,
|
||||
priceInCents: 500,
|
||||
),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
Future.wait([
|
||||
service.decreaseStock('concurrent-stock', 1),
|
||||
service.decreaseStock('concurrent-stock', 1),
|
||||
]),
|
||||
throwsA(isA<InsufficientStockException>()),
|
||||
);
|
||||
|
||||
final product = await service.getProductById('concurrent-stock');
|
||||
expect(product!.stockQuantity, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user