refactor: merge profile cache into unified cache repository

This commit is contained in:
qzl
2026-03-20 15:29:06 +08:00
parent 1cea877bf1
commit a99973fb96
6 changed files with 202 additions and 85 deletions
@@ -1,70 +1,69 @@
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:social_app/core/cache/cache_policy.dart';
import 'package:social_app/core/cache/hybrid_cache_store.dart';
import 'package:social_app/core/cache/memory_cache_store.dart';
import 'package:social_app/core/cache/persistent_cache_store.dart';
import 'package:social_app/features/settings/data/services/settings_user_cache.dart';
import 'package:social_app/features/settings/data/services/user_profile_cache_repository.dart';
import 'package:social_app/features/users/data/models/user_response.dart';
void main() {
test('getOrLoad calls loader only once when cache exists', () async {
final cache = SettingsUserCache();
test('getProfile caches latest user in memory field', () async {
var loadCalls = 0;
final repository = UserProfileCacheRepository(
store: HybridCacheStore(
memory: MemoryCacheStore(),
persistent: PersistentCacheStore(),
),
policy: const CachePolicy(
softTtl: Duration(minutes: 2),
hardTtl: Duration(minutes: 30),
minRefreshInterval: Duration(minutes: 1),
),
remoteLoader: () async {
loadCalls += 1;
return const UserResponse(id: 'u1', username: 'first');
},
);
final cache = SettingsUserCache(repository);
Future<UserResponse> loader() async {
loadCalls += 1;
return const UserResponse(id: 'u1', username: 'first');
}
final first = await cache.getOrLoad(loader);
final second = await cache.getOrLoad(loader);
final first = await cache.getProfile();
final second = await cache.getProfile();
expect(first.username, 'first');
expect(second.username, 'first');
expect(cache.cachedUser?.id, 'u1');
expect(loadCalls, 1);
});
test('invalidate forces next load', () async {
final cache = SettingsUserCache();
var loadCalls = 0;
test('invalidate clears memory cache', () {
final repository = UserProfileCacheRepository(
store: HybridCacheStore(
memory: MemoryCacheStore(),
persistent: PersistentCacheStore(),
),
remoteLoader: () async => const UserResponse(id: 'u1', username: 'first'),
);
final cache = SettingsUserCache(repository);
Future<UserResponse> loader() async {
loadCalls += 1;
return UserResponse(id: 'u$loadCalls', username: 'user$loadCalls');
}
final first = await cache.getOrLoad(loader);
cache.set(const UserResponse(id: 'u1', username: 'first'));
cache.invalidate();
final second = await cache.getOrLoad(loader);
expect(first.id, 'u1');
expect(second.id, 'u2');
expect(loadCalls, 2);
expect(cache.cachedUser, isNull);
});
test(
'invalidate blocks stale inflight response from repopulating cache',
() async {
final cache = SettingsUserCache();
final completer = Completer<UserResponse>();
var loadCalls = 0;
test('set should update cached user immediately', () {
final repository = UserProfileCacheRepository(
store: HybridCacheStore(
memory: MemoryCacheStore(),
persistent: PersistentCacheStore(),
),
remoteLoader: () async => const UserResponse(id: 'u1', username: 'first'),
);
final cache = SettingsUserCache(repository);
Future<UserResponse> slowLoader() {
loadCalls += 1;
return completer.future;
}
cache.set(const UserResponse(id: 'u2', username: 'next'));
final pending = cache.getOrLoad(slowLoader);
cache.invalidate();
completer.complete(const UserResponse(id: 'u1', username: 'stale'));
await pending;
final fresh = await cache.getOrLoad(() async {
loadCalls += 1;
return const UserResponse(id: 'u2', username: 'fresh');
});
expect(fresh.id, 'u2');
expect(cache.cachedUser?.id, 'u2');
expect(loadCalls, 2);
},
);
expect(cache.cachedUser?.id, 'u2');
});
}
@@ -0,0 +1,47 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:social_app/core/cache/cache_entry.dart';
import 'package:social_app/core/cache/cache_policy.dart';
import 'package:social_app/core/cache/hybrid_cache_store.dart';
import 'package:social_app/core/cache/memory_cache_store.dart';
import 'package:social_app/core/cache/persistent_cache_store.dart';
import 'package:social_app/features/settings/data/services/user_profile_cache_repository.dart';
import 'package:social_app/features/users/data/models/user_response.dart';
void main() {
test(
'repository should return persistent cache first then refresh in background',
() async {
final store = HybridCacheStore(
memory: MemoryCacheStore(),
persistent: PersistentCacheStore(),
);
const key = UserProfileCacheRepository.cacheKey;
final stale = CacheEntry<UserResponse>(
value: const UserResponse(id: 'u1', username: 'cached'),
fetchedAt: DateTime(2026, 3, 20, 11, 0),
);
await store.persistent.write<CacheEntry<UserResponse>>(key, stale);
var refreshCalls = 0;
final repository = UserProfileCacheRepository(
store: store,
now: () => DateTime(2026, 3, 20, 11, 5),
policy: const CachePolicy(
softTtl: Duration(minutes: 2),
hardTtl: Duration(minutes: 30),
minRefreshInterval: Duration(minutes: 1),
),
remoteLoader: () async {
refreshCalls += 1;
return const UserResponse(id: 'u1', username: 'remote');
},
);
final result = await repository.getProfile();
await Future<void>.delayed(const Duration(milliseconds: 10));
expect(result.username, 'cached');
expect(refreshCalls, 1);
},
);
}