Advertisement

← vibesdk

worker/api/handlers/space-preview.test.ts

5.2 KB Download file
import { beforeEach, describe, expect, it, vi } from 'vitest';

// The handler reads the app's current previewVersion via AppService. The test
// env has no D1 binding, so mock it with a controllable value (bumping it
// simulates a visibility toggle). This also avoids loading AppService's heavy
// DB dependencies in the worker test isolate.
let currentPreviewVersion = 0;
vi.mock('../../database/services/AppService', () => ({
	AppService: class {
		async getPreviewVersion() {
			return currentPreviewVersion;
		}
	},
}));

const { handleSpacePreview } = await import('./space-preview');
const {
	buildPreviewCookie,
	signSpacePreviewToken,
	SPACE_PREVIEW_COOKIE_NAME,
} = await import('../../utils/spacePreviewToken');

const JWT_SECRET = 'test-secret-for-space-preview-token-validation-1234567890';

beforeEach(() => {
	currentPreviewVersion = 0;
});

type HandlerEnv = Parameters<typeof handleSpacePreview>[1];

interface ForwardCapture {
	lastRequest: Request | null;
}

function makeEnv(
	overrides: Record<string, unknown> = {},
	capture: ForwardCapture = { lastRequest: null },
): HandlerEnv {
	const stub = {
		fetch: async (req: Request) => {
			capture.lastRequest = req;
			return new Response('<html><body><img src="/logo.png"></body></html>', {
				headers: { 'content-type': 'text/html' },
			});
		},
	};
	const namespace = {
		idFromName: (name: string) => name,
		get: () => stub,
	};
	return {
		JWT_SECRET,
		CUSTOM_DOMAIN: 'example.com',
		CUSTOM_PREVIEW_DOMAIN: '',
		SPACE_DO: namespace,
		...overrides,
	} as unknown as HandlerEnv;
}

function previewRequest(token?: string, cookie?: string): Request {
	const url = token
		? `https://example.com/space/space-a/preview/main/?t=${encodeURIComponent(token)}`
		: 'https://example.com/space/space-a/preview/main/';
	return new Request(url, cookie ? { headers: { Cookie: cookie } } : undefined);
}

describe('handleSpacePreview', () => {
	it('forwards a valid ?t= token and sets the preview cookie (Lax, same-origin)', async () => {
		const env = makeEnv();
		const token = await signSpacePreviewToken(env, {
			spaceName: 'space-a',
			branch: 'main',
			userId: 'user-a',
			previewVersion: 0,
		});

		const res = await handleSpacePreview(previewRequest(token), env, 'space-a', 'main');
		expect(res.status).toBe(200);
		const setCookie = res.headers.get('Set-Cookie') ?? '';
		expect(setCookie).toContain(`${SPACE_PREVIEW_COOKIE_NAME}=`);
		expect(setCookie).toContain('HttpOnly');
		expect(setCookie).toContain('Path=/space/space-a/preview/main');
		expect(setCookie).toContain('SameSite=Lax');
	});

	it('forwards a request carrying a valid preview cookie (no ?t=)', async () => {
		const env = makeEnv();
		const token = await signSpacePreviewToken(env, {
			spaceName: 'space-a',
			branch: 'main',
			userId: 'user-a',
			previewVersion: 0,
		});
		const cookie = buildPreviewCookie({
			token,
			spaceName: 'space-a',
			branch: 'main',
			crossSite: false,
			secure: true,
		});

		const res = await handleSpacePreview(
			previewRequest(undefined, cookie),
			env,
			'space-a',
			'main',
		);
		expect(res.status).toBe(200);
	});

	it('returns 401 when neither token nor cookie is present', async () => {
		const env = makeEnv();
		const res = await handleSpacePreview(previewRequest(), env, 'space-a', 'main');
		expect(res.status).toBe(401);
	});

	it('returns 401 for a token minted for a different branch', async () => {
		const env = makeEnv();
		const token = await signSpacePreviewToken(env, {
			spaceName: 'space-a',
			branch: 'feature',
			userId: 'user-a',
			previewVersion: 0,
		});

		const res = await handleSpacePreview(previewRequest(token), env, 'space-a', 'main');
		expect(res.status).toBe(401);
	});

	it('rejects a token after the app previewVersion is bumped (visibility toggle)', async () => {
		const env = makeEnv();
		const token = await signSpacePreviewToken(env, {
			spaceName: 'space-a',
			branch: 'main',
			userId: 'user-a',
			previewVersion: 0,
		});

		// Minted at version 0 — accepted while the app is still at version 0.
		expect(
			(await handleSpacePreview(previewRequest(token), env, 'space-a', 'main')).status,
		).toBe(200);

		// A visibility toggle bumps previewVersion; the same token is now stale.
		currentPreviewVersion = 1;
		expect(
			(await handleSpacePreview(previewRequest(token), env, 'space-a', 'main')).status,
		).toBe(401);
	});

	it('on a separate preview domain, sets a partitioned cookie and strips ?t= before forwarding', async () => {
		const capture: ForwardCapture = { lastRequest: null };
		const env = makeEnv({ CUSTOM_PREVIEW_DOMAIN: 'preview.example.dev' }, capture);
		const token = await signSpacePreviewToken(env, {
			spaceName: 'space-a',
			branch: 'main',
			userId: 'user-a',
			previewVersion: 0,
		});

		const res = await handleSpacePreview(previewRequest(token), env, 'space-a', 'main');
		expect(res.status).toBe(200);
		const setCookie = res.headers.get('Set-Cookie') ?? '';
		expect(setCookie).toContain('SameSite=None');
		expect(setCookie).toContain('Secure');
		expect(setCookie).toContain('Partitioned');
		// The generated app never sees the token.
		expect(new URL(capture.lastRequest!.url).searchParams.get('t')).toBeNull();
	});
});