Something went wrong. Try again.
Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
Something went wrong. Try again.
6.3 kB · 160 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161import { describe, expect, test } from 'bun:test'import { Effect, Exit, Scope } from 'effect'import { acquireHosting, type HostingLifecycleDependencies } from './lifecycle'
function fixtures(fail?: string) { const events: string[] = [] const action = (name: string) => async () => { events.push(name) if (name === fail) throw new Error('secret') } const dependencies: HostingLifecycleDependencies = { initializeExporters: action('exporters-start'), shutdownExporters: action('exporters-stop'), closeDatabase: action('database-stop'), closePrivateDatabase: action('private-stop'), closeRevalidation: action('queue-stop'), startCleanup: () => { events.push('cleanup-start') }, stopCleanup: () => { events.push('cleanup-stop') }, startInvalidation: action('invalidation-start'), stopInvalidation: action('invalidation-stop'), bootstrap: action('bootstrap'), createServer: () => { events.push('http-start') if (fail === 'http-start') throw new Error('secret') return { stop() {} } }, stopServer: async () => { events.push('http-stop') return { forced: false, gracefulStopFailed: false, forceStopFailed: false } }, logFailure: (component) => { events.push(`failure:${component}`) }, logHttpStop: () => {}, } return { events, dependencies }}
describe('hosting resource lifecycle', () => { test('partial HTTP acquisition failure unwinds already acquired resources', async () => { const { events, dependencies } = fixtures('http-start') await expect(Effect.runPromise(Effect.scoped(acquireHosting(dependencies)))).rejects.toBeDefined() expect(events).toEqual([ 'exporters-start', 'cleanup-start', 'invalidation-start', 'bootstrap', 'http-start', 'invalidation-stop', 'cleanup-stop', 'queue-stop', 'private-stop', 'database-stop', 'exporters-stop', ]) }) test('shutdown drains HTTP first and closes databases after invalidation work', async () => { const { events, dependencies } = fixtures() const scope = Scope.makeUnsafe() await Effect.runPromise(Scope.provide(scope)(acquireHosting(dependencies))) await Effect.runPromise(Scope.close(scope, Exit.void)) expect(events.slice(5)).toEqual([ 'http-stop', 'invalidation-stop', 'cleanup-stop', 'queue-stop', 'private-stop', 'database-stop', 'exporters-stop', ]) }) test('one failed cleanup does not skip database or exporter cleanup and logs no cause', async () => { const { events, dependencies } = fixtures('invalidation-stop') await Effect.runPromise(Effect.scoped(acquireHosting(dependencies))) expect(events).toContain('failure:cache invalidation') expect(events.slice(-3)).toEqual(['private-stop', 'database-stop', 'exporters-stop']) expect(events.join(',')).not.toContain('secret') })
test('interruption during startup awaits acquisition and releases it without opening HTTP', async () => { const { events, dependencies } = fixtures() const began = Promise.withResolvers<void>() const release = Promise.withResolvers<void>() dependencies.initializeExporters = async () => { events.push('exporters-start') began.resolve() await release.promise } const abort = new AbortController() const startup = Effect.runPromise(Effect.scoped(acquireHosting(dependencies)), { signal: abort.signal }) await began.promise abort.abort() expect(events).toEqual(['exporters-start']) release.resolve() await expect(startup).rejects.toBeDefined() expect(events).toEqual(['exporters-start', 'exporters-stop']) })
test('storage is acquired before users and released after their actual drain', async () => { const { events, dependencies } = fixtures() const began = Promise.withResolvers<void>() const release = Promise.withResolvers<void>() dependencies.acquireStorage = Effect.acquireRelease( Effect.sync(() => events.push('storage-start')), () => Effect.sync(() => events.push('storage-stop')), ).pipe(Effect.asVoid) dependencies.stopInvalidation = async () => { events.push('invalidation-stop') began.resolve() await release.promise } const scope = Scope.makeUnsafe() await Effect.runPromise(Scope.provide(scope)(acquireHosting(dependencies))) const closing = Effect.runPromise(Scope.close(scope, Exit.void)) await began.promise expect(events).not.toContain('storage-stop') expect(events).not.toContain('database-stop') release.resolve() await closing expect(events.indexOf('storage-start')).toBeLessThan(events.indexOf('invalidation-start')) expect(events.indexOf('storage-stop')).toBeGreaterThan(events.indexOf('cleanup-stop')) expect(events.indexOf('storage-stop')).toBeLessThan(events.indexOf('database-stop')) })
test('partial native storage acquisition releases resources before closing clients', async () => { const { events, dependencies } = fixtures() dependencies.acquireStorage = Effect.gen(function* () { yield* Effect.acquireRelease( Effect.sync(() => events.push('disk-start')), () => Effect.sync(() => events.push('disk-stop')), ) return yield* Effect.fail({ message: 'private secret cause' }) }) const result = await Effect.runPromise(Effect.result(Effect.scoped(acquireHosting(dependencies)))) expect(result._tag).toBe('Failure') if (result._tag !== 'Failure') throw new Error('Expected storage startup failure') expect(result.failure.component).toBe('storage') expect(JSON.stringify(result.failure)).not.toContain('secret') expect(events).not.toContain('http-start') expect(events.indexOf('disk-stop')).toBeLessThan(events.indexOf('database-stop')) })
test('expected startup failures cannot serialize hostile foreign causes', async () => { const { dependencies } = fixtures() dependencies.createServer = () => { throw { message: 'redis://user:secret@private-host', body: 'secret body', path: '/private/secret' } } const result = await Effect.runPromise(Effect.result(Effect.scoped(acquireHosting(dependencies)))) expect(result._tag).toBe('Failure') if (result._tag !== 'Failure') throw new Error('Expected startup failure') expect(JSON.stringify(result.failure)).not.toContain('secret') expect(JSON.stringify(result.failure)).not.toContain('private-host') expect('cause' in result.failure).toBe(false) expect(result.failure.component).toBe('http') })})