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/product_service.dart'; void main() { late AppDatabase database; late DriftProductService service; setUp(() { database = AppDatabase(NativeDatabase.memory()); service = DriftProductService(database: database); }); tearDown(() async { await database.close(); }); group('DriftProductService', () { group('getProducts', () { test('returns empty list when no products', () async { final products = await service.getProducts(); expect(products, isEmpty); }); 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), ), ); final products = await service.getProducts(); expect(products.length, 1); expect(products.first.name, 'Active Product'); }); 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, ), ); final products = await service.getProducts(); expect(products.first.name, 'Apple'); expect(products.last.name, 'Zebra'); }); }); 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, ), ); final product = await service.getProductById('prod-123'); expect(product, isNotNull); expect(product!.name, 'Test Product'); }); test('returns null when not exists', () async { final product = await service.getProductById('nonexistent'); expect(product, isNull); }); }); group('createProduct', () { test('creates product successfully', () async { await service.createProduct( name: 'New Product', category: 'Food', stockQuantity: 20, lowStockThreshold: 5, priceInCents: 850, imagePath: null, ); final products = await service.getProducts(); expect(products.length, 1); expect(products.first.name, 'New Product'); expect(products.first.stockQuantity, 20); }); }); 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 service.updateProduct( const Product( id: 'update-test', name: 'Updated', category: 'Food', stockQuantity: 15, lowStockThreshold: 3, priceInCents: 750, active: true, ), ); final product = await service.getProductById('update-test'); expect(product!.name, 'Updated'); expect(product.stockQuantity, 15); expect(product.priceInCents, 750); }); }); 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 service.deleteProduct('delete-test'); final products = await database.select(database.products).get(); expect(products, isEmpty); }); }); 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 service.decreaseStock('stock-test', 3); final product = await service.getProductById('stock-test'); expect(product!.stockQuantity, 7); }); test('throws when product not found', () async { expect( () => service.decreaseStock('nonexistent', 1), throwsA(isA()), ); }); 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, ), ); expect( () => service.decreaseStock('low-stock', 5), throwsA(isA()), ); }); }); 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 service.increaseStock('increase-test', 5); final product = await service.getProductById('increase-test'); expect(product!.stockQuantity, 15); }); test('throws when product not found', () async { expect( () => service.increaseStock('nonexistent', 1), throwsA(isA()), ); }); 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()), ); expect( () => service.increaseStock('invalid-adjustment', -1), throwsA(isA()), ); }); 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()), ); final product = await service.getProductById('concurrent-stock'); expect(product!.stockQuantity, 0); }); }); }); }