UNPKG

hydrogen-sanity

Version:
497 lines (423 loc) 14.7 kB
import {beforeEach, describe, expect, it, vi} from 'vitest' import {createSanityContext} from '../context' import {type AppLoadContext, PreviewSession, Session} from '../fixtures' import {action, loader} from './route' vi.mock('@sanity/preview-url-secret', () => ({ validatePreviewUrl: vi.fn(), })) vi.mock('../utils', async () => { const actual = await vi.importActual('../utils') return { ...actual, sanitizePerspective: vi.fn((value) => value || 'published'), } }) describe('the preview route', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockValidatePreviewUrl: any // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockSanitizePerspective: any const projectId = 'my-project-id' const dataset = 'my-dataset' const token = 'my-token' beforeEach(async () => { vi.clearAllMocks() mockValidatePreviewUrl = vi.mocked( (await import('@sanity/preview-url-secret')).validatePreviewUrl, ) mockSanitizePerspective = vi.mocked((await import('../utils')).sanitizePerspective) }) describe('entering preview mode', () => { const request = new Request('https://example.com?secret=test-secret') it('enters preview mode successfully', async () => { const previewSession = new PreviewSession() const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { token, session: previewSession, }, }) const context: AppLoadContext = { session: new Session(), sanity, } const perspective = 'drafts' const redirectTo = '/products' mockValidatePreviewUrl.mockResolvedValue({ isValid: true, redirectTo, studioPreviewPerspective: perspective, }) const response = await loader({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(302) expect((response as Response).headers.get('Location')).toBe(redirectTo) expect(previewSession.get('projectId')).toBe(projectId) expect(previewSession.get('perspective')).toBe(perspective) }) it('blocks access when preview mode is disabled', async () => { const sanity = await createSanityContext({ request, client: {projectId, dataset}, }) const context: AppLoadContext = { session: new Session(), sanity, } const response = await loader({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(403) expect(await (response as Response).text()).toMatchInlineSnapshot( `"Preview mode is not enabled in this environment."`, ) }) it('fails when no preview token is provided', async () => { await expect( createSanityContext({ request, client: {projectId, dataset}, preview: { token: '', // Testing with empty token to trigger the error session: new PreviewSession(), }, }), ).rejects.toThrowErrorMatchingInlineSnapshot( `[Error: Enabling preview mode requires a token.]`, ) }) it('fails when project ID is missing from client config', async () => { await expect( createSanityContext({ request, client: {dataset}, preview: { token, session: new PreviewSession(), }, }), ).rejects.toThrowErrorMatchingInlineSnapshot( `[Error: Configuration must contain \`projectId\`]`, ) }) it('works with Hydrogen session is provided', async () => { const hydrogenSession = new Session() const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { session: hydrogenSession, token, }, }) const context: AppLoadContext = { session: hydrogenSession, sanity, } const perspective = 'drafts' const redirectTo = '/collections' mockValidatePreviewUrl.mockResolvedValue({ isValid: true, redirectTo, studioPreviewPerspective: perspective, }) const response = await loader({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(302) expect((response as Response).headers.get('Location')).toBe(redirectTo) expect(hydrogenSession.get('projectId')).toBe(projectId) expect(hydrogenSession.get('perspective')).toBe(perspective) }) it('rejects invalid preview URLs', async () => { const previewSession = new PreviewSession() const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { token, session: previewSession, }, }) const context: AppLoadContext = { session: new Session(), sanity, } mockValidatePreviewUrl.mockResolvedValue({ isValid: false, }) const response = await loader({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(401) expect(await (response as Response).text()).toMatchInlineSnapshot(`"Invalid secret"`) }) it('uses default settings when redirect URL and perspective are not specified', async () => { const previewSession = new PreviewSession() const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { token, session: previewSession, }, }) const context: AppLoadContext = { session: new Session(), sanity, } mockValidatePreviewUrl.mockResolvedValue({ isValid: true, }) const response = await loader({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(302) expect((response as Response).headers.get('Location')).toBe('/') expect(previewSession.get('perspective')).toBe('drafts') }) }) describe('exiting preview mode', () => { it('exits preview mode with POST', async () => { const request = new Request('https://example.com', {method: 'POST'}) const session = new Session() session.set('projectId', projectId) session.set('perspective', 'drafts') const context: AppLoadContext = { session, // eslint-disable-next-line @typescript-eslint/no-explicit-any sanity: {} as any, } const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(302) expect((response as Response).headers.get('Location')).toBe('/') expect(session.get('projectId')).toBeUndefined() expect(session.get('perspective')).toBeUndefined() }) it('exits preview mode with DELETE', async () => { const request = new Request('https://example.com', {method: 'DELETE'}) const session = new Session() session.set('projectId', projectId) session.set('perspective', 'drafts') const context: AppLoadContext = { session, // eslint-disable-next-line @typescript-eslint/no-explicit-any sanity: {} as any, } const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(302) expect((response as Response).headers.get('Location')).toBe('/') expect(session.get('projectId')).toBeUndefined() expect(session.get('perspective')).toBeUndefined() }) it('exits preview mode when using a preview session', async () => { const request = new Request('https://example.com', {method: 'POST'}) const previewSession = new PreviewSession() previewSession.set('projectId', projectId) previewSession.set('perspective', 'drafts') const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { token, session: previewSession, }, }) const context: AppLoadContext = { session: new Session(), sanity, } const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(302) expect((response as Response).headers.get('Location')).toBe('/') expect((response as Response).headers.get('Set-Cookie')).toBe('destroyed-cookie') }) }) describe('changing preview perspective', () => { it('changes the preview perspective successfully', async () => { const formData = new FormData() formData.set('perspective', 'published') const request = new Request('https://example.com', { method: 'PUT', body: formData, }) const previewSession = new PreviewSession() previewSession.set('projectId', projectId) const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { token, session: previewSession, }, }) const context: AppLoadContext = { session: new Session(), sanity, } mockSanitizePerspective.mockReturnValue('published') const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(200) expect(await (response as Response).text()).toMatchInlineSnapshot(`"OK"`) expect(previewSession.get('perspective')).toBe('published') expect(mockSanitizePerspective).toHaveBeenCalledWith('published') }) it('handles perspective values with multiple options', async () => { const formData = new FormData() formData.set('perspective', 'drafts,published') const request = new Request('https://example.com', { method: 'PUT', body: formData, }) const previewSession = new PreviewSession() previewSession.set('projectId', projectId) const sanity = await createSanityContext({ request: new Request('https://example.com'), client: {projectId, dataset}, preview: { token, session: previewSession, }, }) const context: AppLoadContext = { session: new Session(), sanity, } mockSanitizePerspective.mockReturnValue(['drafts', 'published']) const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(200) expect(previewSession.get('perspective')).toBe('drafts,published') }) it('blocks perspective changes when preview mode is disabled', async () => { const request = new Request('https://example.com', {method: 'PUT'}) const sanity = await createSanityContext({ request: new Request('https://example.com'), client: {projectId, dataset}, }) const context: AppLoadContext = { session: new Session(), sanity, } const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(403) expect(await (response as Response).text()).toMatchInlineSnapshot( `"Preview mode is not enabled in this environment."`, ) }) it('rejects requests when project ID in session does not match', async () => { const request = new Request('https://example.com', {method: 'PUT'}) const previewSession = new PreviewSession() previewSession.set('projectId', 'different-project-id') const sanity = await createSanityContext({ request, client: {projectId, dataset}, preview: { session: previewSession, token, }, }) const context: AppLoadContext = { session: new Session(), sanity, } const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(400) expect(await (response as Response).text()).toMatchInlineSnapshot(`"Invalid projectId"`) }) }) it('rejects unsupported HTTP methods', async () => { const request = new Request('https://example.com', {method: 'PATCH'}) const context: AppLoadContext = { session: new Session(), // eslint-disable-next-line @typescript-eslint/no-explicit-any sanity: {} as any, } const response = await action({ context, request, params: {}, unstable_pattern: '', unstable_url: new URL('https://example.com'), }) expect(response).toBeInstanceOf(Response) expect((response as Response).status).toBe(405) expect(await (response as Response).text()).toMatchInlineSnapshot(`"Method not allowed"`) }) })