Шаг 0.12 плана production-готовности: - accessibleTasksCache (45с) per userId:orgId внутри storage-метода - инвалидация в storage-слое: роли, делегирования, form access, assignees, createTask/updateTask, appRole пользователя - 9 новых юнит-тестов (65/65 зелёные)
104 lines
4.0 KiB
TypeScript
104 lines
4.0 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import {
|
|
TtlCache,
|
|
accessibleTasksCache,
|
|
accessibleTasksKey,
|
|
invalidateAccessibleTasksForUser,
|
|
invalidateAccessibleTasksForOrg,
|
|
} from '../server/utils/cache';
|
|
|
|
describe('TtlCache', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('возвращает значение в пределах TTL и undefined после истечения', () => {
|
|
const cache = new TtlCache(30);
|
|
cache.set('k', new Set([1, 2, 3]));
|
|
expect(cache.get<Set<number>>('k')?.has(2)).toBe(true);
|
|
|
|
vi.advanceTimersByTime(29_000);
|
|
expect(cache.get('k')).toBeDefined();
|
|
|
|
vi.advanceTimersByTime(2_000);
|
|
expect(cache.get('k')).toBeUndefined();
|
|
});
|
|
|
|
it('кэширует null как значение (отличая от промаха undefined)', () => {
|
|
const cache = new TtlCache(30);
|
|
cache.set('admin', null);
|
|
expect(cache.get('admin')).toBeNull();
|
|
expect(cache.get('missing')).toBeUndefined();
|
|
});
|
|
|
|
it('invalidatePrefix удаляет ключи по префиксу', () => {
|
|
const cache = new TtlCache(30);
|
|
cache.set('tasks:1:minimal:a', 1);
|
|
cache.set('tasks:1:minimal:b', 2);
|
|
cache.set('tasks:2:minimal:a', 3);
|
|
cache.invalidatePrefix('tasks:1:minimal:');
|
|
expect(cache.get('tasks:1:minimal:a')).toBeUndefined();
|
|
expect(cache.get('tasks:1:minimal:b')).toBeUndefined();
|
|
expect(cache.get('tasks:2:minimal:a')).toBe(3);
|
|
});
|
|
|
|
it('invalidateSuffix удаляет ключи по суффиксу', () => {
|
|
const cache = new TtlCache(30);
|
|
cache.set('accessible:5:1', 'a');
|
|
cache.set('accessible:7:1', 'b');
|
|
cache.set('accessible:5:2', 'c');
|
|
cache.invalidateSuffix(':1');
|
|
expect(cache.get('accessible:5:1')).toBeUndefined();
|
|
expect(cache.get('accessible:7:1')).toBeUndefined();
|
|
expect(cache.get('accessible:5:2')).toBe('c');
|
|
});
|
|
|
|
it('вытесняет самый старый ключ при переполнении maxSize', () => {
|
|
const cache = new TtlCache(30, 2);
|
|
cache.set('a', 1);
|
|
cache.set('b', 2);
|
|
cache.set('c', 3);
|
|
expect(cache.get('a')).toBeUndefined();
|
|
expect(cache.get('b')).toBe(2);
|
|
expect(cache.get('c')).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('accessibleTasksCache helpers', () => {
|
|
beforeEach(() => {
|
|
accessibleTasksCache.clear();
|
|
});
|
|
|
|
it('ключ имеет формат accessible:<userId>:<organizationId>', () => {
|
|
expect(accessibleTasksKey(12, 3)).toBe('accessible:12:3');
|
|
});
|
|
|
|
it('точечная инвалидация сбрасывает только одного пользователя', () => {
|
|
accessibleTasksCache.set(accessibleTasksKey(1, 1), new Set([10]));
|
|
accessibleTasksCache.set(accessibleTasksKey(2, 1), new Set([20]));
|
|
invalidateAccessibleTasksForUser(1, 1);
|
|
expect(accessibleTasksCache.get(accessibleTasksKey(1, 1))).toBeUndefined();
|
|
expect(accessibleTasksCache.get<Set<number>>(accessibleTasksKey(2, 1))?.has(20)).toBe(true);
|
|
});
|
|
|
|
it('инвалидация по организации сбрасывает всех её пользователей, не трогая другие организации', () => {
|
|
accessibleTasksCache.set(accessibleTasksKey(1, 1), new Set([10]));
|
|
accessibleTasksCache.set(accessibleTasksKey(2, 1), null);
|
|
accessibleTasksCache.set(accessibleTasksKey(1, 2), new Set([30]));
|
|
invalidateAccessibleTasksForOrg(1);
|
|
expect(accessibleTasksCache.get(accessibleTasksKey(1, 1))).toBeUndefined();
|
|
expect(accessibleTasksCache.get(accessibleTasksKey(2, 1))).toBeUndefined();
|
|
expect(accessibleTasksCache.get<Set<number>>(accessibleTasksKey(1, 2))?.has(30)).toBe(true);
|
|
});
|
|
|
|
it('null (admin/view_all) кэшируется и корректно читается', () => {
|
|
accessibleTasksCache.set(accessibleTasksKey(9, 1), null);
|
|
expect(accessibleTasksCache.get(accessibleTasksKey(9, 1))).toBeNull();
|
|
expect(accessibleTasksCache.get(accessibleTasksKey(9, 2))).toBeUndefined();
|
|
});
|
|
});
|