learning_ai_common_plat/packages/offline-queue/src/index.test.ts

144 lines
4.9 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { createOfflineQueue } from './index.js';
function createMemoryStorage() {
const store: Record<string, string> = {};
return {
getItem: (k: string) => store[k] ?? null,
setItem: (k: string, v: string) => {
store[k] = v;
},
_store: store,
};
}
describe('createOfflineQueue', () => {
it('should start with zero length', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
expect(queue.length()).toBe(0);
});
it('should enqueue items', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: { name: 'test' } });
expect(queue.length()).toBe(1);
queue.enqueue({ id: '2', action: 'update', path: '/sessions/2', payload: { name: 'test2' } });
expect(queue.length()).toBe(2);
});
it('should replace existing entry with same id + action', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: { v: 1 } });
queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: { v: 2 } });
expect(queue.length()).toBe(1);
});
it('should not replace entries with different action', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
queue.enqueue({ id: '1', action: 'create', path: '/sessions', payload: {} });
queue.enqueue({ id: '1', action: 'update', path: '/sessions/1', payload: {} });
expect(queue.length()).toBe(2);
});
it('should flush all items successfully', async () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
queue.enqueue({ id: '1', action: 'create', path: '/a', payload: { n: 1 } });
queue.enqueue({ id: '2', action: 'create', path: '/b', payload: { n: 2 } });
const executor = vi.fn().mockResolvedValue(undefined);
const result = await queue.flush(executor);
expect(result.flushed).toBe(2);
expect(result.failed).toBe(0);
expect(queue.length()).toBe(0);
expect(executor).toHaveBeenCalledTimes(2);
});
it('should retry failed items on next flush', async () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage, maxRetries: 3 });
queue.enqueue({ id: '1', action: 'create', path: '/a', payload: {} });
const failingExecutor = vi.fn().mockRejectedValue(new Error('offline'));
const result = await queue.flush(failingExecutor);
expect(result.flushed).toBe(0);
expect(result.failed).toBe(1);
expect(queue.length()).toBe(1);
});
it('should drop items after max retries', async () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage, maxRetries: 2 });
queue.enqueue({ id: '1', action: 'create', path: '/a', payload: {} });
const failingExecutor = vi.fn().mockRejectedValue(new Error('offline'));
// Retry 1
await queue.flush(failingExecutor);
expect(queue.length()).toBe(1);
// Retry 2 — should be dropped
await queue.flush(failingExecutor);
expect(queue.length()).toBe(0);
});
it('should cap queue at maxQueueSize', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage, maxQueueSize: 3 });
for (let i = 0; i < 5; i++) {
queue.enqueue({ id: `${i}`, action: 'create', path: `/item/${i}`, payload: {} });
}
expect(queue.length()).toBe(3);
});
it('should persist to storage', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
queue.enqueue({ id: '1', action: 'create', path: '/a', payload: { x: 1 } });
const stored = JSON.parse(storage._store['test-q']);
expect(stored).toHaveLength(1);
expect(stored[0].id).toBe('1');
});
it('should clear the queue', () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
queue.enqueue({ id: '1', action: 'create', path: '/a', payload: {} });
queue.enqueue({ id: '2', action: 'create', path: '/b', payload: {} });
expect(queue.length()).toBe(2);
queue.clear();
expect(queue.length()).toBe(0);
});
it('should return empty result when flushing empty queue', async () => {
const storage = createMemoryStorage();
const queue = createOfflineQueue({ storageKey: 'test-q', storage });
const executor = vi.fn();
const result = await queue.flush(executor);
expect(result.flushed).toBe(0);
expect(result.failed).toBe(0);
expect(executor).not.toHaveBeenCalled();
});
});