From efab4d28e2a257a03d66a62ad0ae92ebe9fcb40a Mon Sep 17 00:00:00 2001 From: Tobe O Date: Sun, 1 Apr 2018 22:29:36 -0400 Subject: [PATCH] Tests --- ...n_if_modified_since_in_cache_test_dart.xml | 8 ++ .../tests_in_cache_test_dart.xml | 6 ++ README.md | 2 +- lib/src/cache.dart | 30 +++--- lib/src/util.dart | 6 ++ test/cache_test.dart | 101 ++++++++++++++++++ 6 files changed, 137 insertions(+), 16 deletions(-) create mode 100644 .idea/runConfigurations/sends_304_on_if_modified_since_in_cache_test_dart.xml create mode 100644 .idea/runConfigurations/tests_in_cache_test_dart.xml create mode 100644 lib/src/util.dart create mode 100644 test/cache_test.dart diff --git a/.idea/runConfigurations/sends_304_on_if_modified_since_in_cache_test_dart.xml b/.idea/runConfigurations/sends_304_on_if_modified_since_in_cache_test_dart.xml new file mode 100644 index 00000000..57b298cf --- /dev/null +++ b/.idea/runConfigurations/sends_304_on_if_modified_since_in_cache_test_dart.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/tests_in_cache_test_dart.xml b/.idea/runConfigurations/tests_in_cache_test_dart.xml new file mode 100644 index 00000000..33520a9a --- /dev/null +++ b/.idea/runConfigurations/tests_in_cache_test_dart.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/README.md b/README.md index ac775e77..a9b69342 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Future configureServer(Angel app) async { cache.patterns.addAll([ 'robots.txt', new RegExp(r'\.(png|jpg|gif|txt)$'), - new Glob('/public/**/*'), + new Glob('public/**/*'), ]); // REQUIRED: The middleware that serves cached responses diff --git a/lib/src/cache.dart b/lib/src/cache.dart index 794fde2b..181a0af0 100644 --- a/lib/src/cache.dart +++ b/lib/src/cache.dart @@ -1,12 +1,7 @@ import 'dart:async'; import 'package:angel_framework/angel_framework.dart'; -import 'package:intl/intl.dart'; import 'package:pool/pool.dart'; - -final DateFormat _fmt = new DateFormat('EEE, d MMM yyyy HH:mm:ss'); - -/// Formats a date (converted to UTC), ex: `Sun, 03 May 2015 23:02:37 GMT`. -String _formatDateForHttp(DateTime dt) => _fmt.format(dt.toUtc()) + ' GMT'; +import 'util.dart'; /// A flexible response cache for Angel. /// @@ -39,7 +34,7 @@ class ResponseCache { /// This prevents the server from even having to access the cache, and plays very well with static assets. Future ifModifiedSince(RequestContext req, ResponseContext res) async { if (req.headers.value('if-modified-since') != null) { - var modifiedSince = _fmt + var modifiedSince = fmt .parse(req.headers.value('if-modified-since').replaceAll('GMT', '')); // Check if there is a cache entry. @@ -47,8 +42,9 @@ class ResponseCache { if (pattern.allMatches(req.uri.path).isNotEmpty && _cache.containsKey(req.uri.path)) { var response = _cache[req.uri.path]; + //print('${response.timestamp} vs ${modifiedSince}'); - if (response.timestamp.compareTo(modifiedSince) <= 0) { + if (response.timestamp.compareTo(modifiedSince) < 0) { res.statusCode = 304; return false; } @@ -61,8 +57,7 @@ class ResponseCache { /// Serves content from the cache, if applicable. Future handleRequest(RequestContext req, ResponseContext res) async { - if (!await ifModifiedSince(req, res)) - return false; + if (!await ifModifiedSince(req, res)) return false; // Check if there is a cache entry. for (var pattern in patterns) { @@ -70,9 +65,13 @@ class ResponseCache { var now = new DateTime.now().toUtc(); if (_cache.containsKey(req.uri.path)) { - // If the cache timeout has been met, don't send the cached response. var response = _cache[req.uri.path]; - if (now.difference(response.timestamp) >= timeout) return true; + + if (timeout != null) { + // If the cache timeout has been met, don't send the cached response. + if (now.difference(response.timestamp) >= timeout) return true; + } + _setCachedHeaders(response.timestamp, req, res); res ..headers.addAll(response.headers) @@ -110,7 +109,8 @@ class ResponseCache { } // Save the response. - var writeLock = _writeLocks.putIfAbsent(req.uri.path, () => new Pool(1)); + var writeLock = + _writeLocks.putIfAbsent(req.uri.path, () => new Pool(1)); await writeLock.withResource(() { _cache[req.uri.path] = new _CachedResponse( new Map.from(res.headers), res.buffer.toBytes(), now); @@ -129,11 +129,11 @@ class ResponseCache { res.headers ..['cache-control'] = '$privacy, max-age=${timeout?.inSeconds ?? 0}' - ..['last-modified'] = _formatDateForHttp(modified); + ..['last-modified'] = formatDateForHttp(modified); if (timeout != null) { var expiry = new DateTime.now().add(timeout); - res.headers['expires'] = _formatDateForHttp(expiry); + res.headers['expires'] = formatDateForHttp(expiry); } } } diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 00000000..90a91172 --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,6 @@ +import 'package:intl/intl.dart'; + +final DateFormat fmt = new DateFormat('EEE, d MMM yyyy HH:mm:ss'); + +/// Formats a date (converted to UTC), ex: `Sun, 03 May 2015 23:02:37 GMT`. +String formatDateForHttp(DateTime dt) => fmt.format(dt.toUtc()) + ' GMT'; \ No newline at end of file diff --git a/test/cache_test.dart b/test/cache_test.dart new file mode 100644 index 00000000..4ec6c603 --- /dev/null +++ b/test/cache_test.dart @@ -0,0 +1,101 @@ +import 'dart:async'; +import 'package:angel_cache/src/util.dart'; +import 'package:angel_cache/angel_cache.dart'; +import 'package:angel_framework/angel_framework.dart'; +import 'package:angel_test/angel_test.dart'; +import 'package:http/http.dart' as http; +import 'package:glob/glob.dart'; +import 'package:test/test.dart'; + +main() async { + group('no timeout', () { + TestClient client; + DateTime lastModified; + http.Response response1, response2; + + setUp(() async { + var app = new Angel(); + var cache = new ResponseCache() + ..patterns.addAll([ + new Glob('/*.txt'), + ]); + + app.use(cache.handleRequest); + + app.get( + '/date.txt', + (ResponseContext res) => + res.write(new DateTime.now().toIso8601String())); + + app.addRoute('PURGE', '*', (RequestContext req) { + cache.purge(req.uri.path); + print('Purged ${req.uri.path}'); + }); + + app.responseFinalizers.add(cache.responseFinalizer); + + var oldHandler = app.errorHandler; + app.errorHandler = (e, req, res) { + if (e.error == null) return oldHandler(e, req, res); + return Zone.current.handleUncaughtError(e.error, e.stackTrace); + }; + + client = await connectTo(app); + response1 = await client.get('/date.txt'); + response2 = await client.get('/date.txt'); + lastModified = new DateTime.now().toUtc(); + print('Response 1 status: ${response1.statusCode}'); + print('Response 2 status: ${response2.statusCode}'); + print('Response 1 body: ${response1.body}'); + print('Response 2 body: ${response2.body}'); + print('Response 1 headers: ${response1.headers}'); + print('Response 2 headers: ${response2.headers}'); + }); + + tearDown(() => client.close()); + + test('saves content', () async { + expect(response1.body, response2.body); + }); + + test('saves headers', () async { + response1.headers.forEach((k, v) { + expect(response2.headers, containsPair(k, v)); + }); + }); + + test('first response is normal', () { + expect(response1.statusCode, 200); + }); + + test('sends last-modified', () { + expect(response2.headers.keys, contains('last-modified')); + }); + + test('invalidate', () async { + await client.sendUnstreamed( + 'PURGE', '/date.txt', {'x-http-method-override': 'PURGE'}); + var response = await client.get('/date.txt'); + print('Response after invalidation: ${response.body}'); + expect(response.body, isNot(response1.body)); + }); + + test('sends 304 on if-modified-since', () async { + var response = await client.get('/date.txt', + headers: {'if-modified-since': formatDateForHttp(lastModified)}); + expect(response.statusCode, 304); + }); + + test('last-modified in the past', () async { + var response = await client.get('/date.txt', headers: { + 'if-modified-since': + formatDateForHttp(lastModified.subtract(const Duration(days: 10))) + }); + print('Response: ${response.body}'); + expect(response.statusCode, 200); + expect(response.body, isNot(response1.body)); + }); + }); + + group('with timeout', () {}); +}