1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-09-24 17:51:14 +02:00

fix: cache eviction bug

This commit is contained in:
Thomas Heartman 2025-09-24 13:49:35 +02:00
parent b4ad9c964f
commit 18373cc7f4
No known key found for this signature in database
GPG Key ID: BD1F880DAED1EE78
4 changed files with 53 additions and 7 deletions

View File

@ -2,7 +2,10 @@ import type { FC } from 'react';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { testServerRoute, testServerSetup } from 'utils/testServer'; import { testServerRoute, testServerSetup } from 'utils/testServer';
import { useFeatureSearch } from './useFeatureSearch.ts'; import {
getFeatureSearchFetcher,
useFeatureSearch,
} from './useFeatureSearch.ts';
import { useSWRConfig } from 'swr'; import { useSWRConfig } from 'swr';
const server = testServerSetup(); const server = testServerSetup();
@ -93,4 +96,18 @@ describe('useFeatureSearch', () => {
rerender(<TestComponent params={{ project }} />); rerender(<TestComponent params={{ project }} />);
await screen.findByText(/Total: 0/); await screen.findByText(/Total: 0/);
}); });
test('should give the same cache key, regardless of parameter order', async () => {
const params1 = {
query: 'query-string',
project: 'IS:project',
};
const params2 = {
project: 'IS:project',
query: 'query-string',
};
const { KEY: key1 } = getFeatureSearchFetcher(params1);
const { KEY: key2 } = getFeatureSearchFetcher(params2);
expect(key1).toEqual(key2);
});
}); });

View File

@ -100,12 +100,13 @@ const createFeatureSearch = () => {
export const DEFAULT_PAGE_LIMIT = 25; export const DEFAULT_PAGE_LIMIT = 25;
const getFeatureSearchFetcher = (params: SearchFeaturesParams) => { export const getFeatureSearchFetcher = (params: SearchFeaturesParams) => {
const urlSearchParams = new URLSearchParams( const urlSearchParams = new URLSearchParams(
Array.from( Array.from(
Object.entries(params) Object.entries(params)
.filter(([_, value]) => !!value) .filter(([_, value]) => !!value)
.map(([key, value]) => [key, value.toString()]), // TODO: parsing non-string parameters .map(([key, value]) => [key, value.toString()]) // TODO: parsing non-string parameters
.toSorted(),
), ),
).toString(); ).toString();
const KEY = `${PATH}${urlSearchParams}`; const KEY = `${PATH}${urlSearchParams}`;

View File

@ -14,7 +14,7 @@ describe('manageCacheEntries', () => {
expect(cacheMock.has('prefix-3')).toBe(true); expect(cacheMock.has('prefix-3')).toBe(true);
}); });
it('should keep the SWR_CACHE_SIZE entries and delete the rest', () => { it('should keep the SWR_CACHE_SIZE entries and delete the rest, keeping the most recent entries', () => {
const cacheMock = new Map(); const cacheMock = new Map();
cacheMock.set('prefix-1', {}); cacheMock.set('prefix-1', {});
cacheMock.set('prefix-2', {}); cacheMock.set('prefix-2', {});
@ -23,8 +23,19 @@ describe('manageCacheEntries', () => {
clearCacheEntries(cacheMock, 'prefix-4', 'prefix-', 2); clearCacheEntries(cacheMock, 'prefix-4', 'prefix-', 2);
expect(cacheMock.has('prefix-4')).toBe(true); expect([...cacheMock.keys()]).toStrictEqual(['prefix-3', 'prefix-4']);
expect([...cacheMock.keys()].length).toBe(2); });
it('should not delete the current key, even if it would be cleared as part of the cache size limit', () => {
const cacheMock = new Map();
cacheMock.set('prefix-1', {});
cacheMock.set('prefix-2', {});
cacheMock.set('prefix-3', {});
cacheMock.set('prefix-4', {});
clearCacheEntries(cacheMock, 'prefix-2', 'prefix-', 2);
expect([...cacheMock.keys()]).toStrictEqual(['prefix-2', 'prefix-4']);
}); });
it('should handle case when SWR_CACHE_SIZE is larger than number of entries', () => { it('should handle case when SWR_CACHE_SIZE is larger than number of entries', () => {
@ -50,4 +61,16 @@ describe('manageCacheEntries', () => {
expect(cacheMock.has('other-2')).toBe(true); expect(cacheMock.has('other-2')).toBe(true);
expect(cacheMock.has('prefix-3')).toBe(true); expect(cacheMock.has('prefix-3')).toBe(true);
}); });
it('treats a negative cache size as zero', () => {
const cacheMock = new Map();
cacheMock.set('prefix-1', {});
cacheMock.set('prefix-2', {});
cacheMock.set('prefix-3', {});
cacheMock.set('prefix-4', {});
clearCacheEntries(cacheMock, 'prefix-3', 'prefix-', -1);
expect([...cacheMock.keys()]).toStrictEqual(['prefix-3']);
});
}); });

View File

@ -13,7 +13,12 @@ export const clearCacheEntries = (
const filteredKeys = keys.filter( const filteredKeys = keys.filter(
(key) => key.startsWith(clearPrefix) && key !== currentKey, (key) => key.startsWith(clearPrefix) && key !== currentKey,
); );
const keysToDelete = filteredKeys.slice(SWR_CACHE_SIZE - 1);
const entriesToLeave = SWR_CACHE_SIZE - 1;
const keysToDelete =
entriesToLeave <= 0
? filteredKeys
: filteredKeys.slice(0, -entriesToLeave);
keysToDelete.forEach((key) => cache.delete(key)); keysToDelete.forEach((key) => cache.delete(key));
}; };