fix(web): converge search runtime boundaries (round 8)
This commit is contained in:
@@ -177,16 +177,19 @@ async function liveContext(config: ConstructorParameters<typeof SessionQuerySqli
|
||||
}
|
||||
|
||||
describe('SQLite session search', () => {
|
||||
it('defaults and validates persisted inspection concurrency through its Cordis config', async () => {
|
||||
it('defaults and validates opening policy and persisted inspection concurrency through its Cordis config', async () => {
|
||||
const defaultCtx = await liveContext()
|
||||
expect((defaultCtx.sessionQuery as SessionQuerySqlite).config.openAt).toBe('startup')
|
||||
expect((defaultCtx.sessionQuery as SessionQuerySqlite).config.persistedInspectConcurrency)
|
||||
.toBe(SESSION_QUERY_DEFAULT_PERSISTED_INSPECT_CONCURRENCY)
|
||||
|
||||
const configuredValue = 2
|
||||
const configured = new SessionQuerySqlite.Config({
|
||||
path: ':memory:',
|
||||
openAt: 'first-search',
|
||||
persistedInspectConcurrency: configuredValue,
|
||||
})
|
||||
expect(configured.openAt).toBe('first-search')
|
||||
expect(configured.persistedInspectConcurrency).toBe(configuredValue)
|
||||
const configuredCtx = await liveContext(configured)
|
||||
expect((configuredCtx.sessionQuery as SessionQuerySqlite).config.persistedInspectConcurrency)
|
||||
@@ -198,6 +201,72 @@ describe('SQLite session search', () => {
|
||||
persistedInspectConcurrency,
|
||||
})).toThrow()
|
||||
}
|
||||
expect(() => new SessionQuerySqlite.Config({
|
||||
path: ':memory:',
|
||||
openAt: 'later' as never,
|
||||
})).toThrow()
|
||||
})
|
||||
|
||||
it('mounts and disposes first-search mode without opening its database', async () => {
|
||||
const path = await temporaryPath('unopened.db')
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
const search = await ctx.plugin(SessionQuerySqlite, {
|
||||
path,
|
||||
openAt: 'first-search',
|
||||
})
|
||||
|
||||
await expect(stat(path)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
await search.dispose()
|
||||
await expect(stat(path)).rejects.toMatchObject({ code: 'ENOENT' })
|
||||
})
|
||||
|
||||
it('opens once on the first search and reuses readiness for later searches', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionQuerySqlite, {
|
||||
path: ':memory:',
|
||||
openAt: 'first-search',
|
||||
})
|
||||
const service = ctx.sessionQuery as SessionQuerySqlite
|
||||
const internals = service as unknown as { _open(): Promise<void> }
|
||||
const open = vi.spyOn(internals, '_open')
|
||||
|
||||
await expect(service.searchSessions({ query: 'first' })).resolves.toEqual({ items: [] })
|
||||
await expect(service.searchSessions({ query: 'second' })).resolves.toEqual({ items: [] })
|
||||
|
||||
expect(open).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('shares one readiness promise across concurrent first searches', async () => {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
await ctx.plugin(SessionQuerySqlite, {
|
||||
path: ':memory:',
|
||||
openAt: 'first-search',
|
||||
})
|
||||
const service = ctx.sessionQuery as SessionQuerySqlite
|
||||
const internals = service as unknown as { _open(): Promise<void> }
|
||||
const originalOpen = internals._open.bind(internals)
|
||||
const release = Promise.withResolvers<undefined>()
|
||||
const started = Promise.withResolvers<undefined>()
|
||||
const open = vi.spyOn(internals, '_open').mockImplementation(async () => {
|
||||
started.resolve(undefined)
|
||||
await release.promise
|
||||
await originalOpen()
|
||||
})
|
||||
|
||||
const first = service.searchSessions({ query: 'first' })
|
||||
const second = service.searchSessions({ query: 'second' })
|
||||
await started.promise
|
||||
expect(open).toHaveBeenCalledOnce()
|
||||
release.resolve(undefined)
|
||||
|
||||
await expect(Promise.all([first, second])).resolves.toEqual([
|
||||
{ items: [] },
|
||||
{ items: [] },
|
||||
])
|
||||
expect(open).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('searches two-character Unicode61 tokens in live-only sessions', async () => {
|
||||
@@ -522,6 +591,7 @@ describe('SQLite session search', () => {
|
||||
{ path: ':memory:', persistedInspectConcurrency: 0 },
|
||||
{ path: ':memory:', persistedInspectConcurrency: Number.MAX_SAFE_INTEGER + 1 },
|
||||
{ path: ':memory:', defaultLimit: 3, maxLimit: 2 },
|
||||
{ path: ':memory:', openAt: 'later' },
|
||||
{ path: ':memory:', journalMode: 'memory' },
|
||||
]) {
|
||||
const direct = new Context()
|
||||
@@ -1238,6 +1308,30 @@ describe('SQLite schema, cancellation, and real persistence integration', () =>
|
||||
}
|
||||
})
|
||||
|
||||
it('defers an invalid database failure only in first-search mode', async () => {
|
||||
const path = await temporaryPath('lazy-invalid.db')
|
||||
const foreign = new DatabaseSync(path)
|
||||
foreign.exec('CREATE TABLE canonical(value TEXT)')
|
||||
foreign.close()
|
||||
|
||||
const lazyCtx = new Context()
|
||||
await lazyCtx.plugin(SessionStore)
|
||||
const lazy = await lazyCtx.plugin(SessionQuerySqlite, {
|
||||
path,
|
||||
openAt: 'first-search',
|
||||
})
|
||||
expect(lazyCtx.sessionQuery).toBeInstanceOf(SessionQuerySqlite)
|
||||
await expect(lazyCtx.sessionQuery.searchSessions({ query: 'needle' }))
|
||||
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
|
||||
await lazy.dispose()
|
||||
|
||||
const eagerCtx = new Context()
|
||||
await eagerCtx.plugin(SessionStore)
|
||||
await expect(eagerCtx.plugin(SessionQuerySqlite, { path }))
|
||||
.rejects.toThrow(expectCode('SESSION_QUERY_INDEX_FAILED'))
|
||||
expect(eagerCtx.sessionQuery).toBeUndefined()
|
||||
})
|
||||
|
||||
it.each(['sessions', 'events'] as const)(
|
||||
'forwards one exact reconciliation signal through both snapshot lists and persisted inspection for %s search',
|
||||
async (scope) => {
|
||||
|
||||
Reference in New Issue
Block a user