diff --git a/src/core/utils/cookieStore.test.ts b/src/core/utils/cookieStore.test.ts new file mode 100644 index 000000000..ee27b1e08 --- /dev/null +++ b/src/core/utils/cookieStore.test.ts @@ -0,0 +1,65 @@ +// @vitest-environment node +import { afterAll, afterEach, beforeAll, expect, it, vi } from 'vitest' +import { cookieStore } from './cookieStore' + +const url = 'https://example.com' + +function createFakeLocalStorage(): Storage { + const store = new Map() + return { + get length() { + return store.size + }, + getItem: (key) => (store.has(key) ? store.get(key)! : null), + setItem: (key, value) => void store.set(key, String(value)), + removeItem: (key) => void store.delete(key), + clear: () => store.clear(), + key: (index) => Array.from(store.keys())[index] ?? null, + } +} + +beforeAll(() => { + Reflect.set(globalThis, 'localStorage', createFakeLocalStorage()) +}) + +afterAll(() => { + Reflect.deleteProperty(globalThis, 'localStorage') +}) + +afterEach(() => { + vi.restoreAllMocks() + localStorage.clear() +}) + +it('does not crash request handling when localStorage quota is exceeded', async () => { + const quotaError = new DOMException( + 'The quota has been exceeded.', + 'QuotaExceededError', + ) + vi.spyOn(localStorage, 'setItem').mockImplementation(() => { + throw quotaError + }) + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + + // Persisting the cookie fails because the storage quota is exceeded, + // but that must not throw and crash the request handling pipeline. + await expect( + cookieStore.setCookie('name=value', url), + ).resolves.toBeUndefined() + + // The cookie must remain available in-memory for the current session. + expect( + cookieStore.getCookies(url).map((cookie) => `${cookie.key}=${cookie.value}`), + ).toEqual(['name=value']) + + // A warning must be emitted so the failure is not silent. + expect(warnSpy).toHaveBeenCalledTimes(1) +}) + +it('persists cookies to localStorage when the quota is available', async () => { + await expect( + cookieStore.setCookie('token=abc', url), + ).resolves.toBeUndefined() + + expect(localStorage.getItem('__msw-cookie-store__')).toContain('token') +}) diff --git a/src/core/utils/cookieStore.ts b/src/core/utils/cookieStore.ts index 5b7646ea3..514d00f56 100644 --- a/src/core/utils/cookieStore.ts +++ b/src/core/utils/cookieStore.ts @@ -88,7 +88,18 @@ class CookieStore { } } - localStorage.setItem(this.#storageKey, JSON.stringify(data)) + try { + localStorage.setItem(this.#storageKey, JSON.stringify(data)) + } catch (error) { + // Persisting cookies to `localStorage` can fail (e.g. with a + // `QuotaExceededError` when the storage is full). Treat persistence as + // best-effort: the cookies remain available in-memory for the current + // session, and the failure must not crash the request handling pipeline. + console.warn( + '[MSW] Failed to persist cookies to "localStorage". Cookies will still work for the current session but will not survive a page reload. This is likely because the storage quota has been exceeded.', + error, + ) + } } }