import 'package:drift/drift.dart' hide isNull, isNotNull; import 'package:drift/native.dart'; 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(() 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 { await database.close(); }); Product createTestProduct({ String id = 'prod-1', String name = 'Test Beer', int priceInCents = 400, }) { return Product( id: id, name: name, category: 'Drinks', stockQuantity: 100, lowStockThreshold: 10, priceInCents: priceInCents, ); } group('DriftBarTabService', () { group('createTab', () { test('creates tab with open status', () async { final tab = await service.createTab(customerName: 'John'); expect(tab.customerName, 'John'); expect(tab.status, 'open'); expect(tab.items, isEmpty); }); test('creates tab with unique id', () async { final tab1 = await service.createTab(customerName: 'John'); final tab2 = await service.createTab(customerName: 'Jane'); expect(tab1.id, isNot(equals(tab2.id))); }); }); group('getOpenTabs', () { test('returns empty list when no tabs', () async { final tabs = await service.getOpenTabs(); expect(tabs, isEmpty); }); 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(), ), ); final tabs = await service.getOpenTabs(); expect(tabs.length, 1); expect(tabs.first.customerName, 'Open Tab'); }); 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, ), ); final tabs = await service.getOpenTabs(); expect(tabs.first.customerName, 'Second'); expect(tabs.last.customerName, 'First'); }); }); group('getTabById', () { test('returns tab when exists', () async { final created = await service.createTab(customerName: 'Test'); final tab = await service.getTabById(created.id); expect(tab, isNotNull); expect(tab!.customerName, 'Test'); }); test('returns null when not exists', () async { final tab = await service.getTabById('nonexistent'); expect(tab, isNull); }); }); group('addProductToTab', () { test('adds new product to tab', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(); await service.addProductToTab(tabId: tab.id, product: product); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.items.length, 1); expect(updatedTab.items.first.productName, 'Test Beer'); expect(updatedTab.items.first.quantity, 1); }); test('increments quantity when product already in tab', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(); await service.addProductToTab(tabId: tab.id, product: product); await service.addProductToTab(tabId: tab.id, product: product); await service.addProductToTab(tabId: tab.id, product: product); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.items.length, 1); expect(updatedTab.items.first.quantity, 3); }); test('adding product calculates correct line total', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(priceInCents: 450); await service.addProductToTab(tabId: tab.id, product: product); await service.addProductToTab(tabId: tab.id, product: product); 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()), ); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.items, isEmpty); }); }); group('adjustTabItemQuantity', () { test('updates item 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.adjustTabItemQuantity(tabItemId: tabItemId, delta: 4); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.items.first.quantity, 5); }); 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.adjustTabItemQuantity(tabItemId: tabItemId, delta: -1); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.items, isEmpty); }); 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.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', () { test('archives tab items to closed tabs', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(); await service.addProductToTab(tabId: tab.id, product: product); await service.addProductToTab(tabId: tab.id, product: product); await service.closeTab(tab.id); final closedTabs = await service.getClosedTabs(); expect(closedTabs.length, 1); expect(closedTabs.first.customerName, 'John'); expect(closedTabs.first.items.length, 1); expect(closedTabs.first.items.first.quantity, 2); }); test('clears tab items after closing', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(); await service.addProductToTab(tabId: tab.id, product: product); await service.closeTab(tab.id); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.items, isEmpty); }); test('tab remains open but items cleared', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(); await service.addProductToTab(tabId: tab.id, product: product); await service.closeTab(tab.id); final updatedTab = await service.getTabById(tab.id); expect(updatedTab!.isOpen, true); expect(updatedTab.items, isEmpty); }); test('closed tab preserves item prices', () async { final tab = await service.createTab(customerName: 'John'); final product = createTestProduct(priceInCents: 550); await service.addProductToTab(tabId: tab.id, product: product); await service.closeTab(tab.id); final closedTabs = await service.getClosedTabs(); expect(closedTabs.first.items.first.unitPriceInCents, 550); }); }); group('getClosedTabs', () { test('returns empty when no closed tabs', () async { final tabs = await service.getClosedTabs(); expect(tabs, isEmpty); }); 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, ), ); final closedTabs = await service.getClosedTabs(); expect(closedTabs.first.customerName, 'Second'); expect(closedTabs.last.customerName, 'First'); }); }); }); }