add http_get_cache

This commit is contained in:
2026-01-20 23:18:39 -08:00
parent 572c0789b7
commit 811e884481
168 changed files with 37335 additions and 0 deletions
@@ -0,0 +1,11 @@
import 'package:http_get_cache/src/date_parser.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('date-parse', () {
const str = 'Wed, 17 Jul 2024 23:19:37 GMT';
const parser = DateParser();
final date = parser(str);
expect(date != null, true);
});
}
@@ -0,0 +1,204 @@
import 'dart:convert';
import 'dart:io';
import 'package:http_get_cache/http_get_cache.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
late HttpGetCacheDatabase db;
late SqliteHttpCacheStore store;
setUp(() async {
db = await initHttpGetCache(
cachePath: 'temp',
databasePath: 'temp'
);
store = SqliteHttpCacheStore(db);
});
tearDown(() async {
await db.close();
});
tearDownAll(() async {
await Directory(db.settings.cacheDir).delete(recursive: true);
// await Directory(db.settings.databaseDir).delete(recursive: true);
});
group('HttpGetCache', () {
test('check for no cache headers', () async {
final inner = TestHttpClient();
final client = HttpGetCache(inner, store);
var res = await client.get(Uri.http('example.com'));
expect(res.statusCode, 200);
expect(inner.calls, 1);
res = await client.get(Uri.http('example.com'));
expect(res.statusCode, 200);
expect(inner.calls, 2);
});
test('check if cache-control header is respected', () async {
final inner = TestHttpClient();
const target = 'http://example.com/cache-control';
inner.handle = (req) async {
return Response('', 200, headers: {
'Cache-Control': 'public, immutable',
});
};
final client = HttpGetCache(inner, store);
var res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'], 'public, immutable');
expect(inner.calls, 1);
res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'], 'public, immutable');
expect(inner.calls, 2);
});
test('check if max-age is respected', () async {
final inner = TestHttpClient();
const target = 'http://example.com/max-age';
const ma = 5;
inner.handle = (req) async {
return Response.bytes(utf8.encode('Success!'), 200, headers: {
'Cache-Control': 'max-age=$ma',
});
};
final client = HttpGetCache(inner, store);
var res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'], 'max-age=$ma');
expect(inner.calls, 1);
res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'], 'max-age=$ma');
expect(inner.calls, 1);
await Future.delayed(const Duration(seconds: ma + 1));
res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'], 'max-age=$ma');
expect(inner.calls, 2);
});
test('check if max-age + stale-while-revalidate is respected', () async {
final inner = TestHttpClient();
const target = 'http://example.com/max-age/stale-while-revalidate';
const ma = 3;
const swr = 5;
inner.handle = (req) async {
return Response.bytes(utf8.encode('Success!'), 200, headers: {
'Cache-Control': 'max-age=$ma, stale-while-revalidate=$swr',
});
};
final client = HttpGetCache(inner, store);
var res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'],
'max-age=$ma, stale-while-revalidate=$swr');
expect(inner.calls, 1);
res = await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'],
'max-age=$ma, stale-while-revalidate=$swr');
expect(inner.calls, 1);
await Future.delayed(const Duration(seconds: ma));
await client.stream(Request('GET', Uri.parse(target))).first;
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'],
'max-age=$ma, stale-while-revalidate=$swr');
expect(inner.calls, 1);
await Future.delayed(const Duration(seconds: swr + 1));
await client.get(Uri.parse(target));
expect(res.statusCode, 200);
expect(res.headers['Cache-Control'],
'max-age=$ma, stale-while-revalidate=$swr');
expect(inner.calls, 2);
});
test('stale-if-error', () async {
final inner = TestHttpClient();
const ma = 3;
const sie = 5;
const target = 'http://example.com/error-check';
int count = 0;
bool error = false;
inner.handle = (req) async {
if (error) return Response('', 500);
if (req.method == 'GET') count++;
return Response.bytes(utf8.encode('count: $count'), 200, headers: {
'Cache-Control': 'max-age=$ma, stale-if-error=$sie',
});
};
final client = HttpGetCache(inner, store);
final url = Uri.parse(target);
var res = await client.get(url);
expect(res.statusCode, 200);
expect(res.body, 'count: 1');
error = true;
await Future.delayed(const Duration(seconds: ma + 1));
res = await client.get(url);
expect(res.statusCode, 200);
expect(res.body, 'count: 1');
await Future.delayed(const Duration(seconds: sie + 1));
res = await client.get(url);
expect(res.statusCode, 500);
});
});
}
class TestHttpClient extends BaseClient {
TestHttpClient();
int calls = 0;
Future<Response> Function(BaseRequest request) handle = (req) async {
return Response('', 200);
};
@override
Future<StreamedResponse> send(BaseRequest request) async {
final res = await handle(request);
if (request.method == 'GET') calls++;
return StreamedResponse(
Stream.value(res.bodyBytes),
res.statusCode,
headers: res.headers,
request: request,
isRedirect: res.isRedirect,
persistentConnection: res.persistentConnection,
reasonPhrase: res.reasonPhrase,
contentLength: res.contentLength,
);
}
}
@@ -0,0 +1,77 @@
import 'dart:io';
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:http/testing.dart';
import 'package:http_get_cache/http_get_cache.dart';
void main() {
late Directory tempDir;
late HttpGetCacheDatabase db;
late SqliteHttpCacheStore store;
setUp(() {
tempDir = Directory.systemTemp.createTempSync('http_get_cache_test_');
final settings = DatabaseSettings(
'test.db',
databaseDir: tempDir.path,
cacheDir: tempDir.path,
);
db = HttpGetCacheDatabase(settings, NativeDatabase.memory());
store = SqliteHttpCacheStore(db);
});
tearDown(() {
db.close();
if (tempDir.existsSync()) {
tempDir.deleteSync(recursive: true);
}
});
test('Vary header differentiates cache entries', () async {
final client = MockClient((request) async {
return Response(
'Response for ${request.headers['User-Agent']}',
200,
headers: {
'cache-control': 'max-age=100',
'vary': 'User-Agent',
},
);
});
final cache = HttpGetCache(client, store);
// Request 1: User-Agent A
final req1 = Request('GET', Uri.parse('https://example.com/api'));
req1.headers['User-Agent'] = 'AgentA';
final res1 = await cache.save(req1);
expect(await res1.stream.bytesToString(), 'Response for AgentA');
// Request 2: User-Agent B
final req2 = Request('GET', Uri.parse('https://example.com/api'));
req2.headers['User-Agent'] = 'AgentB';
final res2 = await cache.save(req2);
expect(await res2.stream.bytesToString(), 'Response for AgentB');
// Verify correct cache hits
// Using a fail-client to ensure we hit the cache
final clientFail =
MockClient((req) async => throw Exception('Network call made!'));
final cacheHit = HttpGetCache(clientFail, store);
// Request 3: User-Agent A again -> Should get Cached A
final req3 = Request('GET', Uri.parse('https://example.com/api'));
req3.headers['User-Agent'] = 'AgentA';
final res3 = await (cacheHit.send(req3));
expect(await res3.stream.bytesToString(), 'Response for AgentA');
// Request 4: User-Agent B again -> Should get Cached B
final req4 = Request('GET', Uri.parse('https://example.com/api'));
req4.headers['User-Agent'] = 'AgentB';
final res4 = await (cacheHit.send(req4));
expect(await res4.stream.bytesToString(), 'Response for AgentB');
});
}