Files
kooltab/test/product_service_test.dart

324 lines
9.2 KiB
Dart

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<Exception>()),
);
});
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<InsufficientStockException>()),
);
});
});
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<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);
});
});
});
}