openpencil/tests/engine/app/storage/cache-eviction.test.ts
Danila Poyarkov 56a3b77ac5 feat(app): add local-first storage substrate
- Recover the IndexedDB and in-memory document cache behind the storage domain

- Add persistent outbox supersession, cache eviction, import validation, and naming helpers

- Keep the substrate provider-neutral and cover its pure behavior

Co-authored-by: Rob Coenen <753704+rcoenen@users.noreply.github.com>
2026-07-26 14:30:39 +03:00

77 lines
2.5 KiB
TypeScript

import { beforeEach, describe, expect, test } from 'bun:test'
import { evictLocalFigCache } from '@/app/storage/cache-eviction'
import {
createMemoryLocalCanvasStore,
getLocalCanvasStore,
resetLocalCanvasStoreForTests
} from '@/app/storage/local-store'
import type { LocalSyncStatus } from '@/app/storage/local-store'
const MB = 1024 * 1024
async function seed(
id: string,
sizeMb: number,
lastOpenedAt: string,
syncStatus: LocalSyncStatus = 'synced'
) {
const local = getLocalCanvasStore()
await local.writeCanvas({
id,
providerId: 's3-compatible',
name: id,
figBytes: new Uint8Array(sizeMb * MB),
syncStatus
})
await local.updateMeta(id, { lastOpenedAt })
}
describe('evictLocalFigCache', () => {
beforeEach(() => {
resetLocalCanvasStoreForTests(createMemoryLocalCanvasStore())
})
test('does nothing under budget', async () => {
await seed('a', 2, '2026-01-01')
const evicted = await evictLocalFigCache(new Set(), 10 * MB)
expect(evicted).toBe(0)
})
test('evicts least-recently-opened synced figs until under budget', async () => {
await seed('old', 4, '2026-01-01')
await seed('mid', 4, '2026-02-01')
await seed('new', 4, '2026-03-01')
const evicted = await evictLocalFigCache(new Set(), 8 * MB)
expect(evicted).toBe(1)
const local = getLocalCanvasStore()
const oldMeta = await local.getMeta('old')
expect(oldMeta?.hasFig).toBe(false)
expect(await local.readFig('old')).toBeNull()
// meta row and identity survive — the card stays listed
expect(oldMeta?.name).toBe('old')
expect((await local.getMeta('new'))?.hasFig).toBe(true)
})
test('never evicts unsynced or open canvases', async () => {
await seed('dirty', 4, '2026-01-01', 'pending')
await seed('open', 4, '2026-01-02')
await seed('fresh', 4, '2026-03-01')
const evicted = await evictLocalFigCache(new Set(['open']), 4 * MB)
const local = getLocalCanvasStore()
expect((await local.getMeta('dirty'))?.hasFig).toBe(true)
expect((await local.getMeta('open'))?.hasFig).toBe(true)
// only 'fresh' was evictable
expect(evicted).toBe(1)
expect((await local.getMeta('fresh'))?.hasFig).toBe(false)
})
test('backfills figSize for legacy rows instead of skipping them', async () => {
await seed('legacy', 6, '2026-01-01')
const local = getLocalCanvasStore()
await local.updateMeta('legacy', { figSize: undefined })
const evicted = await evictLocalFigCache(new Set(), 1 * MB)
expect(evicted).toBe(1)
})
})