import 'package:flutter_test/flutter_test.dart'; import 'package:layer_wizard/history/history_db.dart'; import 'package:layer_wizard/history/history_service.dart'; import 'package:layer_wizard/ostree_service.dart'; DeploymentInfo _deployment({ required String checksum, bool booted = false, bool staged = false, String version = 'v1', List requestedPackages = const [], }) { return DeploymentInfo( version: version, checksum: checksum, containerImageRef: 'ostree-image-signed:docker://ghcr.io/ublue-os/aurora:stable', requestedPackages: requestedPackages, booted: booted, staged: staged, ); } void main() { final history = HistoryService.instance; setUp(() { HistoryDb.resetForTesting(); }); test('first reconcile bootstraps a root node and both pointers', () async { final a = _deployment(checksum: 'aaa', booted: true); final current = await history.reconcile(deployments: [a]); expect(current.checksum, 'aaa'); expect(current.parentId, isNull); expect(current.source, 'detected'); final safe = await history.getLastKnownSafe(); expect(safe?.checksum, 'aaa'); }); test('reconcile is idempotent for an already-known checksum', () async { final a = _deployment(checksum: 'aaa', booted: true); await history.reconcile(deployments: [a]); await history.reconcile(deployments: [a]); await history.reconcile(deployments: [a]); final all = await history.getAllNodes(); expect(all, hasLength(1)); }); test('recordInstall adds a child of current without moving current', () async { final a = _deployment(checksum: 'aaa', booted: true); final root = await history.reconcile(deployments: [a]); final staged = _deployment(checksum: 'bbb', staged: true, requestedPackages: ['vim']); final node = await history.recordInstall(packageName: 'vim', staged: staged); expect(node.parentId, root.id); expect(node.action, 'install'); expect(node.source, 'layer_wizard'); expect(node.packageName, 'vim'); final current = await history.getCurrent(); expect(current?.id, root.id, reason: 'current must not move until the staged deployment is booted'); }); test('rebooting into a staged install moves current without a duplicate row', () async { final a = _deployment(checksum: 'aaa', booted: true); await history.reconcile(deployments: [a]); final staged = _deployment(checksum: 'bbb', staged: true, requestedPackages: ['vim']); final installed = await history.recordInstall(packageName: 'vim', staged: staged); final bootedAfterReboot = _deployment(checksum: 'bbb', booted: true, requestedPackages: ['vim']); final current = await history.reconcile(deployments: [bootedAfterReboot]); expect(current.id, installed.id); final all = await history.getAllNodes(); expect(all, hasLength(2), reason: 'no new row — same checksum as the staged install'); }); test('a manual change (unknown checksum) is recorded as detected, parented on current', () async { final a = _deployment(checksum: 'aaa', booted: true); final root = await history.reconcile(deployments: [a]); final manual = _deployment(checksum: 'ccc', booted: true, requestedPackages: ['htop']); final current = await history.reconcile(deployments: [manual]); expect(current.checksum, 'ccc'); expect(current.source, 'detected'); expect(current.parentId, root.id); final all = await history.getAllNodes(); expect(all, hasLength(2)); }); test('markCurrentAsSafe moves last_known_safe to the current node', () async { final a = _deployment(checksum: 'aaa', booted: true); await history.reconcile(deployments: [a]); final b = _deployment(checksum: 'bbb', booted: true); await history.reconcile(deployments: [b]); expect((await history.getLastKnownSafe())?.checksum, 'aaa'); await history.markCurrentAsSafe(); expect((await history.getLastKnownSafe())?.checksum, 'bbb'); }); test('getChainToCurrent walks root to current in order', () async { final a = _deployment(checksum: 'aaa', booted: true); await history.reconcile(deployments: [a]); final staged = _deployment(checksum: 'bbb', staged: true, requestedPackages: ['vim']); await history.recordInstall(packageName: 'vim', staged: staged); final bootedAfterReboot = _deployment(checksum: 'bbb', booted: true, requestedPackages: ['vim']); await history.reconcile(deployments: [bootedAfterReboot]); final chain = await history.getChainToCurrent(); expect(chain.map((n) => n.checksum), ['aaa', 'bbb']); }); }