diff --git a/.gitignore b/.gitignore index f15d1da..9fa6360 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,7 @@ dist # TernJS port file .tern-port -.idea \ No newline at end of file +.idea + +# Visual Studio Code +.vscode diff --git a/src/http-data-source.ts b/src/http-data-source.ts index be3ab2d..b7965ed 100644 --- a/src/http-data-source.ts +++ b/src/http-data-source.ts @@ -425,8 +425,8 @@ export abstract class HTTPDataSource extends DataSource { } const options = { - ...request, ...this.globalRequestOptions, + ...request, headers, } @@ -434,7 +434,7 @@ export abstract class HTTPDataSource extends DataSource { if (requestIsCacheable) { // try to fetch from shared cache - if (request.requestCache) { + if (options.requestCache) { try { const cacheItem = await this.cache.get(cacheKey) if (cacheItem) { diff --git a/test/http-data-source.test.ts b/test/http-data-source.test.ts index f820fcf..ec4cf0f 100644 --- a/test/http-data-source.test.ts +++ b/test/http-data-source.test.ts @@ -1665,6 +1665,238 @@ test('Response is not cached due to origin error', async (t) => { t.is(cacheMap.size, 0) }) +test('Should cache a GET response and respond with the result on subsequent calls when cache is setup by global request cache options', async (t) => { + t.plan(15) + + const path = '/' + + const wanted = { name: 'foo' } + + const server = http.createServer((req, res) => { + t.is(req.method, 'GET') + res.writeHead(200, { + 'content-type': 'application/json', + }) + res.write(JSON.stringify(wanted)) + res.end() + res.socket?.unref() + }) + + t.teardown(server.close.bind(server)) + + server.listen() + + const baseURL = getBaseUrlOf(server) + + let dataSource = new (class extends HTTPDataSource { + constructor() { + super(baseURL, { + requestOptions: { + requestCache: { + maxTtl: 10, + maxTtlIfError: 20, + } + }, + }) + } + getFoo() { + return this.get(path) + } + })() + + const cacheMap = new Map() + const datasSourceConfig = { + context: { + a: 1, + }, + cache: { + async delete(key: string) { + return cacheMap.delete(key) + }, + async get(key: string) { + return cacheMap.get(key) + }, + async set(key: string, value: string) { + cacheMap.set(key, value) + }, + }, + } + + dataSource.initialize(datasSourceConfig) + + let response = await dataSource.getFoo() + t.false(response.isFromCache) + t.false(response.memoized) + t.is(response.maxTtl, 10) + t.deepEqual(response.body, wanted) + + response = await dataSource.getFoo() + t.false(response.isFromCache) + t.true(response.memoized) + t.is(response.maxTtl, 10) + t.deepEqual(response.body, wanted) + + dataSource = new (class extends HTTPDataSource { + constructor() { + super(baseURL, { + requestOptions: { + requestCache: { + maxTtl: 10, + maxTtlIfError: 20, + } + }, + }) + } + getFoo() { + return this.get(path) + } + })() + + dataSource.initialize(datasSourceConfig) + + response = await dataSource.getFoo() + t.true(response.isFromCache) + t.false(response.memoized) + t.is(response.maxTtl, 10) + t.deepEqual(response.body, wanted) + + const cached = JSON.parse(cacheMap.get(baseURL + path)!) + + t.is(cacheMap.size, 2) + t.like(cached, { + statusCode: 200, + trailers: {}, + opaque: null, + headers: { + connection: 'keep-alive', + 'keep-alive': 'timeout=5', + 'transfer-encoding': 'chunked', + }, + body: wanted, + }) +}) + +test('Should cache a GET response using method-level cache options overriding global request cache options', async (t) => { + t.plan(15) + + const path = '/' + + const wanted = { name: 'foo' } + + const server = http.createServer((req, res) => { + t.is(req.method, 'GET') + res.writeHead(200, { + 'content-type': 'application/json', + }) + res.write(JSON.stringify(wanted)) + res.end() + res.socket?.unref() + }) + + t.teardown(server.close.bind(server)) + + server.listen() + + const baseURL = getBaseUrlOf(server) + + let dataSource = new (class extends HTTPDataSource { + constructor() { + super(baseURL, { + requestOptions: { + requestCache: { + maxTtl: 10, + maxTtlIfError: 20, + } + }, + }) + } + getFoo() { + return this.get(path, { + requestCache: { + maxTtl: 20, + maxTtlIfError: 30, + }, + }) + } + })() + + const cacheMap = new Map() + const datasSourceConfig = { + context: { + a: 1, + }, + cache: { + async delete(key: string) { + return cacheMap.delete(key) + }, + async get(key: string) { + return cacheMap.get(key) + }, + async set(key: string, value: string) { + cacheMap.set(key, value) + }, + }, + } + + dataSource.initialize(datasSourceConfig) + + let response = await dataSource.getFoo() + t.false(response.isFromCache) + t.false(response.memoized) + t.is(response.maxTtl, 20) + t.deepEqual(response.body, wanted) + + response = await dataSource.getFoo() + t.false(response.isFromCache) + t.true(response.memoized) + t.is(response.maxTtl, 20) + t.deepEqual(response.body, wanted) + + dataSource = new (class extends HTTPDataSource { + constructor() { + super(baseURL, { + requestOptions: { + requestCache: { + maxTtl: 10, + maxTtlIfError: 20, + } + }, + }) + } + getFoo() { + return this.get(path, { + requestCache: { + maxTtl: 20, + maxTtlIfError: 30, + }, + }) + } + })() + + dataSource.initialize(datasSourceConfig) + + response = await dataSource.getFoo() + t.true(response.isFromCache) + t.false(response.memoized) + t.is(response.maxTtl, 20) + t.deepEqual(response.body, wanted) + + const cached = JSON.parse(cacheMap.get(baseURL + path)!) + + t.is(cacheMap.size, 2) + t.like(cached, { + statusCode: 200, + trailers: {}, + opaque: null, + headers: { + connection: 'keep-alive', + 'keep-alive': 'timeout=5', + 'transfer-encoding': 'chunked', + }, + body: wanted, + }) +}) + test('Should be able to pass custom Undici Pool', async (t) => { t.plan(2)