Skip to content

Commit

Permalink
fix(plugin-cache-memory): cache http status (#4)
Browse files Browse the repository at this point in the history
* fix(plugin-cache-memory): cache http status

---------

Co-authored-by: Артём Лысенок <[email protected]>
  • Loading branch information
Artsiom-L and Артём Лысенок authored Jun 18, 2024
1 parent 3896609 commit 1377344
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 20 deletions.
1 change: 1 addition & 0 deletions packages/cache-utils/src/constants/metaTypes.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const CACHE = 'cache';
export const PROTOCOL_HTTP = 'PROTOCOL_HTTP';
36 changes: 21 additions & 15 deletions packages/plugin-cache-memory/src/memory.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const next = jest.fn();
const context = new Context({ request: { url: 'test' } });

context.updateExternalMeta = jest.fn(context.updateExternalMeta.bind(context));
context.updateInternalMeta = jest.fn(context.updateInternalMeta.bind(context));

describe('plugins/cache/memory', () => {
beforeEach(() => {
Expand All @@ -45,30 +46,35 @@ describe('plugins/cache/memory', () => {
});

it('init, value from cache', () => {
const response = { a: 1 };
const responseData = { response: {a: 1}, status: 200 };

mockLru.has.mockImplementation(() => true);
mockLru.get.mockImplementation(() => response);
mockLru.get.mockImplementation(() => responseData);
plugin.init(context, next, null);

expect(mockLru.get).toHaveBeenCalledWith('test');
expect(context.updateExternalMeta).toHaveBeenCalledWith(metaTypes.CACHE, {
memoryCache: true,
memoryCacheOutdated: false,
});
expect(context.updateInternalMeta).toHaveBeenCalledWith(metaTypes.PROTOCOL_HTTP, {
response: {
status: responseData.status
}
});
expect(next).toHaveBeenCalledWith({
response,
response: responseData.response,
status: Status.COMPLETE,
});
});

it('init, value from cache outdated, but allowStale is false', () => {
const plugin = memoryCache({ allowStale: false });
const response = { a: 1 };
const responseData = { response: {a: 1}, status: 200 };
const makeRequest: any = jest.fn(() => Promise.resolve());

mockLru.has.mockImplementation(() => false);
mockLru.peek.mockImplementation(() => response);
mockLru.peek.mockImplementation(() => responseData);
plugin.init(context, next, makeRequest);

jest.runAllTimers();
Expand All @@ -80,36 +86,36 @@ describe('plugins/cache/memory', () => {

it('init, value in cache is outdated and allowStale is true', () => {
const plugin = memoryCache({ allowStale: true, staleTtl: 523 });
const response = { a: 1 };
const responseData = { response: {a: 1}, status: 200 };
const makeRequest: any = jest.fn(() => Promise.resolve());

mockLru.has.mockImplementation(() => false);
mockLru.peek.mockImplementation(() => response);
mockLru.peek.mockImplementation(() => responseData);
plugin.init(context, next, makeRequest);

jest.runAllTimers();

expect(mockLru.get).not.toHaveBeenCalledWith('test');
expect(mockLru.set).toHaveBeenCalledWith('test', response, { ttl: 523 });
expect(mockLru.set).toHaveBeenCalledWith('test', responseData, { ttl: 523 });
expect(makeRequest).toHaveBeenCalledWith({ url: 'test', memoryCacheForce: true, memoryCacheBackground: true });
expect(context.updateExternalMeta).toHaveBeenCalledWith(metaTypes.CACHE, {
memoryCache: true,
memoryCacheOutdated: true,
});
expect(next).toHaveBeenCalledWith({
response,
response: responseData.response,
status: Status.COMPLETE,
});
});

it('init, value in cache is outdated and request memoryCacheAllowStale is true', () => {
const plugin = memoryCache({ allowStale: false });
const response = { a: 1 };
const responseData = { response: {a: 1}, status: 200 };
const makeRequest: any = jest.fn(() => Promise.resolve());

context.setState({ request: { url: 'test', memoryCacheAllowStale: true } });
mockLru.has.mockImplementation(() => false);
mockLru.peek.mockImplementation(() => response);
mockLru.peek.mockImplementation(() => responseData);
plugin.init(context, next, makeRequest);

jest.runAllTimers();
Expand All @@ -126,18 +132,18 @@ describe('plugins/cache/memory', () => {
memoryCacheOutdated: true,
});
expect(next).toHaveBeenCalledWith({
response,
response: responseData.response,
status: Status.COMPLETE,
});
});

it('on complete saves to cache', () => {
const response = { a: 1 };
const responseData = { response: {a: 1}, status: 200 };

context.setState({ response, request: { url: 'test', memoryCacheTtl: 123 } });
context.setState({ response: responseData.response, request: { url: 'test', memoryCacheTtl: 123 } });
plugin.complete(context, next, null);

expect(next).toHaveBeenCalled();
expect(mockLru.set).toHaveBeenCalledWith('test', response, { ttl: 123 });
expect(mockLru.set).toHaveBeenCalledWith('test', responseData, { ttl: 123 });
});
});
41 changes: 36 additions & 5 deletions packages/plugin-cache-memory/src/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,15 @@ export interface MemoryPluginOptions {
allowStale?: boolean;
staleTtl?: number;
staleBackgroundRequestTimeout?: number;
memoryConstructor?: (options: Options<string, Response>) => LRUCache<string, Response>;
memoryConstructor?: (options: Options<string, Response>) => LRUCache<string, StoredValue>;
getCacheKey?: (arg) => string;
}

interface StoredValue {
response: Response
status?: number
}

/**
* Caches requests response into memory.
* Uses library `@tinkoff/lru-cache-nano` as memory storage.
Expand Down Expand Up @@ -63,7 +68,7 @@ export default ({
memoryConstructor = (options: Options<string, Response>) => new (require('@tinkoff/lru-cache-nano'))(options),
getCacheKey = undefined,
}: MemoryPluginOptions = {}): Plugin => {
const lruCache: LRUCache<string, Response> = memoryConstructor({
const lruCache: LRUCache<string, StoredValue> = memoryConstructor({
...lruOptions,
allowStale: true, // should be true for the opportunity to control it for individual requests
});
Expand All @@ -80,9 +85,19 @@ export default ({
memoryCacheOutdated: false,
});

const cashedValue = lruCache.get(cacheKey);
// when plugin break flow, plugin-http won't be called and meta will be empty,
// so we need to enrich the meta with status data, as it is done in a normal flow
if (cashedValue.status) {
context.updateInternalMeta(metaTypes.PROTOCOL_HTTP, {
response: {
status: cashedValue.status
}
});
}
return next({
status: Status.COMPLETE,
response: lruCache.get(cacheKey),
response: cashedValue.response,
});
}

Expand All @@ -96,6 +111,13 @@ export default ({
memoryCache: true,
memoryCacheOutdated: true,
});
if (outdated.status) {
context.updateInternalMeta(metaTypes.PROTOCOL_HTTP, {
response: {
status: outdated.status
}
});
}

lruCache.set(cacheKey, outdated, { ttl: staleTtl }); // remember outdated value, to prevent losing it
setTimeout(
Expand Down Expand Up @@ -123,7 +145,7 @@ export default ({

return next({
status: Status.COMPLETE,
response: outdated,
response: outdated.response,
});
}

Expand All @@ -133,7 +155,16 @@ export default ({
const cacheKey = getCacheKeyUtil(context, getCacheKey);
const ttl: number = prop('memoryCacheTtl', context.getRequest());

lruCache.set(cacheKey, context.getResponse(), { ttl });
const value: StoredValue = {
response: context.getResponse(),
};

const httpMeta = context.getInternalMeta(metaTypes.PROTOCOL_HTTP);

if (httpMeta?.response?.status) {
value.status = httpMeta.response.status
}
lruCache.set(cacheKey, value, { ttl });

context.updateExternalMeta(metaTypes.CACHE, {
memoryCacheBackground: prop('memoryCacheBackground', context.getRequest()),
Expand Down

0 comments on commit 1377344

Please sign in to comment.