fix: initialise the catering queue at startup

`KuchniaQueueService` had no `init()` call in `main()`, so on a real device
the whole feature would have done nothing: every entry point checks
`isInitialized` first and returns quietly, which reads as "no delivery today"
rather than as an error. All 694 tests passed throughout, because widget tests
initialise the service directly.

Caught by grepping main.dart before the phone deploy, not by the suite. So it
is now a repo invariant: `repo_invariants_test.dart` asserts main() names both
catering `init()` calls, and the assertion was checked against a main.dart
with the line deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQpCiPaSbyRpwsLkH3dgsX
This commit is contained in:
Krzysztof Rudnicki 2026-08-23 09:08:54 +02:00
parent af35eb20c8
commit 8836453f8c
4 changed files with 44 additions and 2 deletions

View File

@ -10,6 +10,7 @@ import 'package:diet_guard_app/services/budget_history_service.dart';
import 'package:diet_guard_app/services/foodbank_service.dart';
import 'package:diet_guard_app/services/frame_stats.dart';
import 'package:diet_guard_app/services/kuchnia_credential_service.dart';
import 'package:diet_guard_app/services/kuchnia_queue.dart';
import 'package:diet_guard_app/services/log_storage_service.dart';
import 'package:diet_guard_app/services/meal_schedule_service.dart';
import 'package:diet_guard_app/services/notification_service.dart';
@ -28,6 +29,7 @@ Future<void> main() async {
await MealScheduleService.init();
await FoodBankService.init();
await KuchniaCredentialService.init();
await KuchniaQueueService.init();
final notifications = await NotificationService.init();
// Deliberately *not* awaited before the first frame: the browser's
// `Notification.requestPermission()` does not complete until the user

View File

@ -23,6 +23,7 @@ library;
import 'package:diet_guard_app/models/kuchnia_dish.dart';
import 'package:diet_guard_app/services/document_store.dart';
import 'package:diet_guard_app/services/document_store_factory.dart';
import 'package:diet_guard_app/services/kuchnia_import.dart';
import 'package:diet_guard_app/services/kuchnia_orders.dart' show isoDay;
import 'package:flutter/foundation.dart';
@ -60,7 +61,24 @@ class KuchniaQueueService {
/// How many dishes are still waiting to be logged.
static int get remaining => _instance?._pending.length ?? 0;
/// Initialises from [store], for tests and for [init].
/// Initialises the singleton against the platform document store.
///
/// Without this the queue silently no-ops on a real device: every entry
/// point checks [isInitialized] first, so a missing call reads as "no
/// delivery" rather than as an error.
static Future<KuchniaQueueService> init() async {
if (_instance != null) return _instance!;
// Resolving the platform store is a plugin call, not reachable from
// `flutter test`.
// coverage:ignore-start
final service = KuchniaQueueService._(await openDocumentStore());
// coverage:ignore-end
await service._load();
_instance = service;
return service;
}
/// Initialises from [store], for tests.
@visibleForTesting
static Future<KuchniaQueueService> initForTesting(DocumentStore store) async {
final service = KuchniaQueueService._(store);

View File

@ -2,7 +2,7 @@ name: diet_guard_app
description: "Companion phone app for diet_guard: log meals on the go."
publish_to: 'none'
version: 1.0.64+1
version: 1.0.65+1
environment:
sdk: ^3.12.2

View File

@ -125,6 +125,28 @@ void main() {
});
});
group('singletons that silently no-op are initialised at startup', () {
// Every catering entry point checks `isInitialized` first and returns
// quietly when it is false, so a missing `init()` does not throw -- it
// reads as "no delivery" and "no credential" forever. That is invisible in
// widget tests, which initialise these directly, and was caught only by
// grepping main.dart before a phone deploy.
test('main.dart initialises the catering services', () {
final source = File('${_appDir.path}/lib/main.dart').readAsStringSync();
for (final call in [
'KuchniaCredentialService.init()',
'KuchniaQueueService.init()',
]) {
expect(
source.contains(call),
isTrue,
reason: '$call is missing from main(). The feature will silently do '
'nothing on a real device while every test still passes.',
);
}
});
});
group('the desktop wrapper origin is fixed', () {
// IndexedDB is keyed by origin and lives in the Chrome profile. Changing
// either the port or the --user-data-dir hides the entire local food log