feat: справочник компаний кэшируется в localStorage на 4 часа
Раньше он жил только пока открыта страница и загружался заново при каждой перезагрузке. Теперь между перезагрузками API не дёргается 4 часа; просроченный, битый или записанный «из будущего» кэш игнорируется и удаляется, недоступное хранилище кэш не ломает. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
COMPANIES_CACHE_KEY,
|
||||
COMPANIES_CACHE_TTL_MS,
|
||||
IStorageLike,
|
||||
readCachedCompanies,
|
||||
writeCachedCompanies
|
||||
} from './companiesCache';
|
||||
import { ICompany } from './models/ICompany';
|
||||
|
||||
class MemoryStorage implements IStorageLike {
|
||||
public readonly data = new Map<string, string>();
|
||||
// eslint-disable-next-line @rushstack/no-new-null
|
||||
public getItem(key: string): string | null {
|
||||
return this.data.has(key) ? (this.data.get(key) as string) : null;
|
||||
}
|
||||
public setItem(key: string, value: string): void {
|
||||
this.data.set(key, value);
|
||||
}
|
||||
public removeItem(key: string): void {
|
||||
this.data.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const COMPANIES: ICompany[] = [
|
||||
{ Id: '9de064a4-44c8-88ef-11ec-8284930fad96', Name: 'ТОО TANSU Construction', Bin: '021040005165' },
|
||||
{ Id: '0a1b2c3d-0000-4000-8000-000000000002', Name: 'ТОО TANSU Строй' }
|
||||
];
|
||||
|
||||
const NOW = 1_800_000_000_000;
|
||||
|
||||
describe('кэш справочника компаний', () => {
|
||||
it('в пустом хранилище кэша нет', () => {
|
||||
expect(readCachedCompanies(new MemoryStorage(), NOW)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('записанное читается обратно, пока не прошло 4 часа', () => {
|
||||
const storage = new MemoryStorage();
|
||||
writeCachedCompanies(storage, COMPANIES, NOW);
|
||||
|
||||
expect(readCachedCompanies(storage, NOW + 1000)).toEqual(COMPANIES);
|
||||
expect(readCachedCompanies(storage, NOW + COMPANIES_CACHE_TTL_MS - 1)).toEqual(COMPANIES);
|
||||
});
|
||||
|
||||
it('через 4 часа кэш устаревает и удаляется', () => {
|
||||
const storage = new MemoryStorage();
|
||||
writeCachedCompanies(storage, COMPANIES, NOW);
|
||||
|
||||
expect(readCachedCompanies(storage, NOW + COMPANIES_CACHE_TTL_MS)).toBeUndefined();
|
||||
expect(storage.data.has(COMPANIES_CACHE_KEY)).toBe(false);
|
||||
});
|
||||
|
||||
it('срок — ровно 4 часа', () => {
|
||||
expect(COMPANIES_CACHE_TTL_MS).toBe(14_400_000);
|
||||
});
|
||||
|
||||
it('запись из будущего (часы переведены назад) не доверяется', () => {
|
||||
const storage = new MemoryStorage();
|
||||
writeCachedCompanies(storage, COMPANIES, NOW + 60_000);
|
||||
|
||||
expect(readCachedCompanies(storage, NOW)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('битый JSON и неожиданная форма — как отсутствующий кэш', () => {
|
||||
const broken = new MemoryStorage();
|
||||
broken.setItem(COMPANIES_CACHE_KEY, '{не json');
|
||||
expect(readCachedCompanies(broken, NOW)).toBeUndefined();
|
||||
expect(broken.data.has(COMPANIES_CACHE_KEY)).toBe(false);
|
||||
|
||||
const wrongShape = new MemoryStorage();
|
||||
wrongShape.setItem(COMPANIES_CACHE_KEY, JSON.stringify({ savedAt: NOW, items: [{ Id: 1 }] }));
|
||||
expect(readCachedCompanies(wrongShape, NOW)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('недоступное хранилище не ломает ни чтение, ни запись', () => {
|
||||
const throwing: IStorageLike = {
|
||||
getItem: () => {
|
||||
throw new Error('запрещено');
|
||||
},
|
||||
setItem: () => {
|
||||
throw new Error('переполнено');
|
||||
},
|
||||
removeItem: () => {
|
||||
throw new Error('запрещено');
|
||||
}
|
||||
};
|
||||
|
||||
expect(readCachedCompanies(throwing, NOW)).toBeUndefined();
|
||||
expect(() => writeCachedCompanies(throwing, COMPANIES, NOW)).not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user