feat: add remote products and qr code for payconic
This commit is contained in:
+175
-13
@@ -433,6 +433,159 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('product events', () {
|
||||
late DriftProductService products;
|
||||
|
||||
setUp(() {
|
||||
products = DriftProductService(
|
||||
database: database,
|
||||
eventFactory: TabEventFactory(
|
||||
now: () => DateTime.utc(2026, 10, 24, 15, 3, 40),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
Future<List<Map<String, dynamic>>> productEvents() async => [
|
||||
for (final event in await outbox())
|
||||
if ((event['type'] as String).startsWith('product_')) event,
|
||||
];
|
||||
|
||||
Future<List<PendingImageUploadRow>> uploads() =>
|
||||
database.select(database.pendingImageUploads).get();
|
||||
|
||||
test('a new product goes out with its fields, then its stock', () async {
|
||||
await products.createProduct(
|
||||
name: 'Ice Coffee',
|
||||
category: 'Koffie',
|
||||
stockQuantity: 12,
|
||||
lowStockThreshold: 3,
|
||||
priceInCents: 350,
|
||||
imagePath: null,
|
||||
);
|
||||
|
||||
final id = (await products.getProducts())
|
||||
.firstWhere((p) => p.name == 'Ice Coffee')
|
||||
.id;
|
||||
final events = await outbox();
|
||||
expect(events.map((e) => e['type']), ['product_created', 'stock_set']);
|
||||
expect(events.first, {
|
||||
'id': events.first['id'],
|
||||
'type': 'product_created',
|
||||
'productId': id,
|
||||
'product': {
|
||||
'name': 'Ice Coffee',
|
||||
'category': 'Koffie',
|
||||
'priceInCents': 350,
|
||||
'lowStockThreshold': 3,
|
||||
},
|
||||
'occurredAt': '2026-10-24T15:03:40.000Z',
|
||||
});
|
||||
expect(events.first.containsKey('tabId'), isFalse);
|
||||
});
|
||||
|
||||
test(
|
||||
'an untracked new product has no count, and no category is null',
|
||||
() async {
|
||||
await products.createProduct(
|
||||
name: 'Tap water',
|
||||
category: ' ',
|
||||
stockQuantity: 0,
|
||||
lowStockThreshold: 0,
|
||||
infiniteStock: true,
|
||||
priceInCents: 0,
|
||||
imagePath: null,
|
||||
);
|
||||
|
||||
final events = await outbox();
|
||||
expect(events.map((e) => e['type']), ['product_created']);
|
||||
expect(events.single['product'], containsPair('category', null));
|
||||
},
|
||||
);
|
||||
|
||||
test('a change sends only the fields that changed', () async {
|
||||
final cola = (await products.getProductById('prod-1'))!;
|
||||
|
||||
await products.updateProduct(
|
||||
cola.copyWith(name: 'Coca-Cola', priceInCents: 270),
|
||||
);
|
||||
|
||||
expect(
|
||||
(await productEvents()).single,
|
||||
containsPair('product', {'name': 'Coca-Cola', 'priceInCents': 270}),
|
||||
);
|
||||
});
|
||||
|
||||
test('saving without changes sends nothing', () async {
|
||||
final cola = (await products.getProductById('prod-1'))!;
|
||||
|
||||
await products.updateProduct(cola);
|
||||
|
||||
expect(await outbox(), isEmpty);
|
||||
});
|
||||
|
||||
test('deleting archives it for every tablet', () async {
|
||||
await products.deleteProduct('prod-1');
|
||||
|
||||
expect(
|
||||
(await productEvents()).single,
|
||||
containsPair('type', 'product_archived'),
|
||||
);
|
||||
expect(await products.getProducts(), isEmpty);
|
||||
});
|
||||
|
||||
test(
|
||||
'a picked picture waits for upload, a newer one replaces it',
|
||||
() async {
|
||||
final cola = (await products.getProductById('prod-1'))!;
|
||||
|
||||
await products.updateProduct(
|
||||
cola.copyWith(imagePath: '/pictures/a.jpg'),
|
||||
);
|
||||
await products.updateProduct(
|
||||
cola.copyWith(imagePath: '/pictures/b.jpg'),
|
||||
);
|
||||
// Saving again with the same picture uploads nothing new
|
||||
await products.updateProduct(
|
||||
cola.copyWith(imagePath: '/pictures/b.jpg', name: 'Coke'),
|
||||
);
|
||||
|
||||
final waiting = await uploads();
|
||||
expect(waiting.single.productId, 'prod-1');
|
||||
expect(waiting.single.localPath, '/pictures/b.jpg');
|
||||
// The picture itself is linked once uploaded, not now
|
||||
expect(
|
||||
(await productEvents()).where(
|
||||
(e) => (e['product'] as Map?)?.containsKey('imageId') ?? false,
|
||||
),
|
||||
isEmpty,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test('a new product with a picture queues it for upload', () async {
|
||||
await products.createProduct(
|
||||
name: 'Ice Coffee',
|
||||
category: 'Koffie',
|
||||
stockQuantity: 12,
|
||||
lowStockThreshold: 3,
|
||||
priceInCents: 350,
|
||||
imagePath: '/pictures/ice.jpg',
|
||||
);
|
||||
|
||||
expect((await uploads()).single.localPath, '/pictures/ice.jpg');
|
||||
});
|
||||
|
||||
test('are skipped when replaying the outbox onto server tabs', () async {
|
||||
final tab = await openTab('Jan');
|
||||
await products.deleteProduct('prod-1');
|
||||
|
||||
await service.applyServerState([]);
|
||||
|
||||
expect(await service.getTabById(tab.id), isNull);
|
||||
expect(await productEvents(), hasLength(1));
|
||||
});
|
||||
});
|
||||
|
||||
group('stock_set events', () {
|
||||
late DriftProductService products;
|
||||
|
||||
@@ -481,27 +634,36 @@ void main() {
|
||||
final water = (await products.getProducts()).firstWhere(
|
||||
(product) => product.name == 'Water',
|
||||
);
|
||||
final events = await outbox();
|
||||
expect(events, hasLength(1));
|
||||
expect(events.single, containsPair('type', 'stock_set'));
|
||||
expect(events.single, containsPair('productId', water.id));
|
||||
expect(events.single, containsPair('quantity', 24));
|
||||
final counts = [
|
||||
for (final event in await outbox())
|
||||
if (event['type'] == 'stock_set') event,
|
||||
];
|
||||
expect(counts, hasLength(1));
|
||||
expect(counts.single, containsPair('productId', water.id));
|
||||
expect(counts.single, containsPair('quantity', 24));
|
||||
});
|
||||
|
||||
test('a stock or tracking change queues a count', () async {
|
||||
final cola = (await products.getProductById('prod-1'))!;
|
||||
|
||||
await products.updateProduct(cola.copyWith(name: 'Coca-Cola'));
|
||||
expect(await outbox(), isEmpty);
|
||||
|
||||
await products.updateProduct(cola.copyWith(stockQuantity: 40));
|
||||
await products.updateProduct(
|
||||
cola.copyWith(stockQuantity: 40, infiniteStock: true),
|
||||
final renamed = cola.copyWith(name: 'Coca-Cola');
|
||||
await products.updateProduct(renamed);
|
||||
expect(
|
||||
(await outbox()).where((event) => event['type'] == 'stock_set'),
|
||||
isEmpty,
|
||||
);
|
||||
await products.updateProduct(cola.copyWith(stockQuantity: 40));
|
||||
|
||||
await products.updateProduct(renamed.copyWith(stockQuantity: 40));
|
||||
await products.updateProduct(
|
||||
renamed.copyWith(stockQuantity: 40, infiniteStock: true),
|
||||
);
|
||||
await products.updateProduct(renamed.copyWith(stockQuantity: 40));
|
||||
|
||||
expect(
|
||||
[for (final event in await outbox()) event['quantity']],
|
||||
[
|
||||
for (final event in await outbox())
|
||||
if (event['type'] == 'stock_set') event['quantity'],
|
||||
],
|
||||
[40, null, 40],
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user