Skip to content

Commit 8a4d4ef

Browse files
authored
Fix response from cache metadata with tests (#381)
* Fix response from cache metadata with tests
1 parent 6cd274b commit 8a4d4ef

File tree

5 files changed

+49
-32
lines changed

5 files changed

+49
-32
lines changed

.changeset/fluffy-news-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@apollo/datasource-rest': patch
3+
---
4+
5+
Add responseFromCache to DataSourceFetchResult

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/HTTPCache.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface SneakyCachePolicy extends CachePolicy {
2929

3030
interface ResponseWithCacheWritePromise {
3131
response: FetcherResponse;
32-
responseFromCache?: Boolean;
32+
responseFromCache?: boolean;
3333
cacheWritePromise?: Promise<void>;
3434
}
3535

@@ -247,6 +247,7 @@ export class HTTPCache<CO extends CacheOptions = CacheOptions> {
247247
const returnedResponse = response.clone();
248248
return {
249249
response: returnedResponse,
250+
responseFromCache: false,
250251
cacheWritePromise: this.readResponseAndWriteToCache({
251252
response,
252253
policy,

src/RESTDataSource.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export interface DataSourceFetchResult<TResult> {
160160
response: FetcherResponse;
161161
requestDeduplication: RequestDeduplicationResult;
162162
httpCache: HTTPCacheResult;
163+
responseFromCache?: boolean;
163164
}
164165

165166
// RESTDataSource has two layers of caching. The first layer is purely in-memory
@@ -536,16 +537,13 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
536537
? outgoingRequest.cacheOptions
537538
: this.cacheOptionsFor?.bind(this);
538539
try {
539-
const { response, cacheWritePromise } = await this.httpCache.fetch(
540-
url,
541-
outgoingRequest,
542-
{
540+
const { response, responseFromCache, cacheWritePromise } =
541+
await this.httpCache.fetch(url, outgoingRequest, {
543542
cacheKey,
544543
cacheOptions,
545544
httpCacheSemanticsCachePolicyOptions:
546545
outgoingRequest.httpCacheSemanticsCachePolicyOptions,
547-
},
548-
);
546+
});
549547

550548
if (cacheWritePromise) {
551549
this.catchCacheWritePromiseErrors(cacheWritePromise);
@@ -566,6 +564,7 @@ export abstract class RESTDataSource<CO extends CacheOptions = CacheOptions> {
566564
httpCache: {
567565
cacheWritePromise,
568566
},
567+
responseFromCache,
569568
};
570569
} catch (error) {
571570
this.didEncounterError(error as Error, outgoingRequest, url);

src/__tests__/RESTDataSource.test.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,12 +1720,14 @@ describe('RESTDataSource', () => {
17201720
'cache-control': 'public, max-age=31536000, immutable',
17211721
});
17221722
nock(apiUrl).get('/foo/2').reply(200);
1723-
const { httpCache } = await dataSource.getFoo(1);
1724-
expect(httpCache.cacheWritePromise).toBeDefined();
1725-
await httpCache.cacheWritePromise;
1723+
const firstResponse = await dataSource.getFoo(1);
1724+
expect(firstResponse.responseFromCache).toBeFalsy();
1725+
expect(firstResponse.httpCache.cacheWritePromise).toBeDefined();
1726+
await firstResponse.httpCache.cacheWritePromise;
17261727

17271728
// Call a second time which should be cached
1728-
await dataSource.getFoo(1);
1729+
const secondResponse = await dataSource.getFoo(1);
1730+
expect(secondResponse.responseFromCache).toBe(true);
17291731
});
17301732

17311733
it('does not cache 302 responses', async () => {
@@ -1741,16 +1743,18 @@ describe('RESTDataSource', () => {
17411743
'cache-control': 'public, max-age=31536000, immutable',
17421744
});
17431745
nock(apiUrl).get('/foo/2').reply(200);
1744-
const { httpCache } = await dataSource.getFoo(1);
1745-
expect(httpCache.cacheWritePromise).toBeUndefined();
1746+
const firstResponse = await dataSource.getFoo(1);
1747+
expect(firstResponse.responseFromCache).toBeFalsy();
1748+
expect(firstResponse.httpCache.cacheWritePromise).toBeUndefined();
17461749

17471750
// Call a second time which should NOT be cached (it's a temporary redirect!).
17481751
nock(apiUrl).get('/foo/1').reply(302, '', {
17491752
location: 'https://api.example.com/foo/2',
17501753
'cache-control': 'public, max-age=31536000, immutable',
17511754
});
17521755
nock(apiUrl).get('/foo/2').reply(200);
1753-
await dataSource.getFoo(1);
1756+
const secondResponse = await dataSource.getFoo(1);
1757+
expect(secondResponse.responseFromCache).toBeFalsy();
17541758
});
17551759

17561760
it('allows setting cache options for each request', async () => {
@@ -1773,12 +1777,14 @@ describe('RESTDataSource', () => {
17731777
})();
17741778

17751779
nock(apiUrl).get('/foo/1').reply(200);
1776-
const { httpCache } = await dataSource.getFoo(1);
1777-
expect(httpCache.cacheWritePromise).toBeDefined();
1778-
await httpCache.cacheWritePromise;
1780+
const firstResponse = await dataSource.getFoo(1);
1781+
expect(firstResponse.responseFromCache).toBeFalsy();
1782+
expect(firstResponse.httpCache.cacheWritePromise).toBeDefined();
1783+
await firstResponse.httpCache.cacheWritePromise;
17791784

17801785
// Call a second time which should be cached
1781-
await dataSource.getFoo(1);
1786+
const secondResponse = await dataSource.getFoo(1);
1787+
expect(secondResponse.responseFromCache).toBe(true);
17821788
});
17831789

17841790
it('allows setting custom cache options for each request', async () => {
@@ -1800,16 +1806,17 @@ describe('RESTDataSource', () => {
18001806
const spyOnHttpFetch = jest.spyOn(dataSource['httpCache'], 'fetch');
18011807

18021808
nock(apiUrl).get('/foo/1').reply(200);
1803-
const { httpCache } = await dataSource.getFoo(1);
1804-
expect(httpCache.cacheWritePromise).toBeDefined();
1805-
await httpCache.cacheWritePromise;
1809+
const firstResponse = await dataSource.getFoo(1);
1810+
expect(firstResponse.httpCache.cacheWritePromise).toBeDefined();
1811+
await firstResponse.httpCache.cacheWritePromise;
18061812
expect(spyOnHttpFetch.mock.calls[0][2]).toEqual({
18071813
cacheKey: 'GET https://api.example.com/foo/1',
18081814
cacheOptions: { ttl: 1000000, tags: ['foo', 'bar'] },
18091815
});
18101816

18111817
// Call a second time which should be cached
1812-
await dataSource.getFoo(1);
1818+
const secondResponse = await dataSource.getFoo(1);
1819+
expect(secondResponse.responseFromCache).toBe(true);
18131820
});
18141821

18151822
it('allows setting a short TTL for the cache', async () => {
@@ -1835,9 +1842,10 @@ describe('RESTDataSource', () => {
18351842
})();
18361843

18371844
nock(apiUrl).get('/foo/1').reply(200);
1838-
const { httpCache } = await dataSource.getFoo(1);
1839-
expect(httpCache.cacheWritePromise).toBeDefined();
1840-
await httpCache.cacheWritePromise;
1845+
const firstResponse = await dataSource.getFoo(1);
1846+
expect(firstResponse.responseFromCache).toBeFalsy();
1847+
expect(firstResponse.httpCache.cacheWritePromise).toBeDefined();
1848+
await firstResponse.httpCache.cacheWritePromise;
18411849

18421850
// expire the cache (note: 999ms, just shy of the 1s ttl, will reliably fail this test)
18431851
jest.advanceTimersByTime(1000);
@@ -1864,12 +1872,15 @@ describe('RESTDataSource', () => {
18641872
'set-cookie':
18651873
'whatever=blah; expires=Mon, 01-Jan-2050 00:00:00 GMT; path=/; domain=www.example.com',
18661874
});
1867-
const { httpCache } = await dataSource.getFoo(1, false);
1868-
expect(httpCache.cacheWritePromise).toBeDefined();
1869-
await httpCache.cacheWritePromise;
1875+
const firstResponse = await dataSource.getFoo(1, false);
1876+
expect(firstResponse.responseFromCache).toBeFalsy();
1877+
expect(firstResponse.httpCache.cacheWritePromise).toBeDefined();
1878+
await firstResponse.httpCache.cacheWritePromise;
1879+
18701880
// Call a second time which should be cached despite `set-cookie` due to
18711881
// `shared: false`.
1872-
await dataSource.getFoo(1, false);
1882+
const secondResponse = await dataSource.getFoo(1, false);
1883+
expect(secondResponse.responseFromCache).toBe(true);
18731884

18741885
nock(apiUrl).get('/foo/2').times(2).reply(200, '{}', {
18751886
'Cache-Control': 'max-age=60,must-revalidate',
@@ -1883,7 +1894,8 @@ describe('RESTDataSource', () => {
18831894
).toBeUndefined();
18841895
// Call a second time which should be not be cached because of
18851896
// `set-cookie` with `shared: true`. (Note the `.times(2)` above.)
1886-
await dataSource.getFoo(2, true);
1897+
const cookieResponse = await dataSource.getFoo(2, true);
1898+
expect(cookieResponse.responseFromCache).toBeFalsy();
18871899
});
18881900

18891901
it('should not crash in revalidation flow header handling when sending non-array non-string headers', async () => {

0 commit comments

Comments
 (0)