MO-Fuel-Tax-Back/test/lock_coordinator_test.dart

133 lines
4.3 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:fuel_tax_tracker/services/cloud/cloud_storage_provider.dart' show CloudLockFile;
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 =>
[CloudLockFile(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 = <Duration>[];
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 [CloudLockFile(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 = <Duration>[];
final lockId = await acquireLock(
username: 'me',
nowUtc: () => now,
createLock: (_) async => 'mine',
listLocks: () async => [
CloudLockFile(id: 'mine', username: 'me', createdAtUtc: now),
CloudLockFile(
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 = <Duration>[];
final deleteCalls = <String>[];
final lockId = await acquireLock(
username: 'me',
nowUtc: () => now,
createLock: (_) async => 'mine',
listLocks: () async {
listCallCount++;
if (listCallCount == 1) {
return [
CloudLockFile(id: 'mine', username: 'me', createdAtUtc: now),
CloudLockFile(
id: 'other',
username: 'other',
createdAtUtc: now.subtract(const Duration(seconds: 5))),
];
}
return [CloudLockFile(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 = <String>[];
await acquireLock(
username: 'me',
nowUtc: () => now,
staleAge: const Duration(minutes: 10),
createLock: (_) async => 'mine',
listLocks: () async {
listCallCount++;
if (listCallCount == 1) {
return [
CloudLockFile(id: 'mine', username: 'me', createdAtUtc: now),
CloudLockFile(
id: 'stale',
username: 'ghost',
createdAtUtc: now.subtract(const Duration(minutes: 15))),
];
}
return [CloudLockFile(id: 'mine', username: 'me', createdAtUtc: now)];
},
deleteLock: (id) async => deleteCalls.add(id),
delay: (_) async {},
);
expect(deleteCalls, ['stale']);
});
});
}