Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

111 lines
4.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest';
import type { RuntimeStore } from '@openmaic/storage';
import { deleteStageRuntimeSafely } from '@/lib/runtime/store';
function stubStore(deleteStageRuntime: (stageId: string) => Promise<void>): RuntimeStore {
return { deleteStageRuntime } as unknown as RuntimeStore;
}
describe('deleteStageRuntimeSafely', () => {
afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
vi.useRealTimers();
});
it('skips the cascade entirely when the runtime DB was never created', async () => {
// The probe must not fall into openDb(), which would CREATE the database
// just to delete nothing from it.
vi.stubGlobal('indexedDB', {
databases: vi.fn().mockResolvedValue([{ name: 'some-other-db', version: 1 }]),
});
const deleteStageRuntime = vi.fn().mockResolvedValue(undefined);
await expect(
deleteStageRuntimeSafely('stage-42', stubStore(deleteStageRuntime)),
).resolves.toBeUndefined();
expect(deleteStageRuntime).not.toHaveBeenCalled();
});
it('cascades when the probe reports the runtime DB exists', async () => {
vi.stubGlobal('indexedDB', {
databases: vi.fn().mockResolvedValue([{ name: 'maic-runtime', version: 1 }]),
});
const deleteStageRuntime = vi.fn().mockResolvedValue(undefined);
await deleteStageRuntimeSafely('stage-42', stubStore(deleteStageRuntime));
expect(deleteStageRuntime).toHaveBeenCalledExactlyOnceWith('stage-42');
});
it('cascades when the probe API is unavailable', async () => {
// Older Firefox: indexedDB exists but has no databases(). Skipping here
// would strand real cleanup, so the bounded cascade proceeds.
vi.stubGlobal('indexedDB', {});
const deleteStageRuntime = vi.fn().mockResolvedValue(undefined);
await deleteStageRuntimeSafely('stage-42', stubStore(deleteStageRuntime));
expect(deleteStageRuntime).toHaveBeenCalledExactlyOnceWith('stage-42');
});
it('cascades the deletion to the runtime store with the right stageId', async () => {
const deleteStageRuntime = vi.fn().mockResolvedValue(undefined);
await deleteStageRuntimeSafely('stage-42', stubStore(deleteStageRuntime));
expect(deleteStageRuntime).toHaveBeenCalledExactlyOnceWith('stage-42');
});
it('never throws when the runtime store fails; warns instead', async () => {
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const deleteStageRuntime = vi.fn().mockRejectedValue(new Error('runtime DB is broken'));
await expect(
deleteStageRuntimeSafely('stage-42', stubStore(deleteStageRuntime)),
).resolves.toBeUndefined();
expect(warn).toHaveBeenCalledOnce();
expect(String(warn.mock.calls[0])).toContain('stage-42');
});
it('resolves after the timeout when the runtime store hangs', async () => {
vi.useFakeTimers();
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const never = new Promise<void>(() => {});
const pending = deleteStageRuntimeSafely(
'stage-42',
stubStore(() => never),
);
await vi.advanceTimersByTimeAsync(5000);
await expect(pending).resolves.toBeUndefined();
expect(warn).toHaveBeenCalledOnce();
expect(String(warn.mock.calls[0])).toContain('stage-42');
});
it('a rejection landing after the timeout is not an unhandled rejection', async () => {
vi.useFakeTimers();
vi.spyOn(console, 'warn').mockImplementation(() => {});
let rejectLate!: (error: Error) => void;
const late = new Promise<void>((_, reject) => {
rejectLate = reject;
});
const onUnhandled = vi.fn();
process.on('unhandledRejection', onUnhandled);
try {
const pending = deleteStageRuntimeSafely(
'stage-42',
stubStore(() => late),
);
await vi.advanceTimersByTimeAsync(5000);
await pending; // timed out and resolved; the cascade is still pending
rejectLate(new Error('failed long after the deletion moved on'));
// let the event loop reach the unhandled-rejection check
vi.useRealTimers();
await new Promise((resolve) => setTimeout(resolve, 10));
expect(onUnhandled).not.toHaveBeenCalled();
} finally {
process.off('unhandledRejection', onUnhandled);
}
});
});