import 'package:flutter_test/flutter_test.dart'; import 'package:fuel_tax_tracker/services/drive_service.dart' show DriveLockFile; import 'package:fuel_tax_tracker/services/lock_coordinator.dart'; void main() { group('acquireLock', () { test('creates a lock file named {username}-{epochMillis}.lock', () async { final now = DateTime.utc(2024, 1, 1, 12, 0, 0); String? capturedName; await acquireLock( username: 'me@example.com', nowUtc: () => now, createLock: (name) async { capturedName = name; return 'id'; }, listLocks: () async => [DriveLockFile(id: 'id', username: 'me@example.com', createdAtUtc: now)], deleteLock: (_) async {}, delay: (_) async {}, ); expect(capturedName, 'me@example.com-${now.millisecondsSinceEpoch}.lock'); }); test('proceeds immediately when no other lock is older', () async { var listCallCount = 0; final delayCalls = []; final now = DateTime.utc(2024, 1, 1, 12, 0, 0); final lockId = await acquireLock( username: 'me', nowUtc: () => now, createLock: (_) async => 'my-lock-id', listLocks: () async { listCallCount++; return [DriveLockFile(id: 'my-lock-id', username: 'me', createdAtUtc: now)]; }, deleteLock: (_) async {}, delay: (d) async => delayCalls.add(d), ); expect(lockId, 'my-lock-id'); expect(listCallCount, 1); expect(delayCalls, isEmpty); }); test('a newer lock does not block acquisition', () async { final now = DateTime.utc(2024, 1, 1, 12, 0, 0); final delayCalls = []; final lockId = await acquireLock( username: 'me', nowUtc: () => now, createLock: (_) async => 'mine', listLocks: () async => [ DriveLockFile(id: 'mine', username: 'me', createdAtUtc: now), DriveLockFile( id: 'newer', username: 'other', createdAtUtc: now.add(const Duration(seconds: 5))), ], deleteLock: (_) async {}, delay: (d) async => delayCalls.add(d), ); expect(lockId, 'mine'); expect(delayCalls, isEmpty); }); test('waits for an older, non-stale lock, then proceeds once it is gone', () async { final now = DateTime.utc(2024, 1, 1, 12, 0, 0); var listCallCount = 0; final delayCalls = []; final deleteCalls = []; final lockId = await acquireLock( username: 'me', nowUtc: () => now, createLock: (_) async => 'mine', listLocks: () async { listCallCount++; if (listCallCount == 1) { return [ DriveLockFile(id: 'mine', username: 'me', createdAtUtc: now), DriveLockFile( id: 'other', username: 'other', createdAtUtc: now.subtract(const Duration(seconds: 5))), ]; } return [DriveLockFile(id: 'mine', username: 'me', createdAtUtc: now)]; }, deleteLock: (id) async => deleteCalls.add(id), delay: (d) async => delayCalls.add(d), ); expect(lockId, 'mine'); expect(listCallCount, 2); expect(delayCalls.length, 1); expect(deleteCalls, isEmpty, reason: 'a non-stale older lock should not be deleted'); }); test('deletes an older lock once it exceeds the staleness threshold', () async { final now = DateTime.utc(2024, 1, 1, 12, 0, 0); var listCallCount = 0; final deleteCalls = []; await acquireLock( username: 'me', nowUtc: () => now, staleAge: const Duration(minutes: 10), createLock: (_) async => 'mine', listLocks: () async { listCallCount++; if (listCallCount == 1) { return [ DriveLockFile(id: 'mine', username: 'me', createdAtUtc: now), DriveLockFile( id: 'stale', username: 'ghost', createdAtUtc: now.subtract(const Duration(minutes: 15))), ]; } return [DriveLockFile(id: 'mine', username: 'me', createdAtUtc: now)]; }, deleteLock: (id) async => deleteCalls.add(id), delay: (_) async {}, ); expect(deleteCalls, ['stale']); }); }); }