diff --git a/vite.config.ts b/vite.config.ts index b0b083a..8b9da68 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -85,7 +85,7 @@ export default defineConfig(({command}) => { environment: 'jsdom', registerNodeLoader: false, setupFiles: ['./src/test/setup.ts'], - exclude: [...configDefaults.exclude, 'workers/**', 'dist/**'], + exclude: [...configDefaults.exclude, 'dist/**'], coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], diff --git a/workers/site/__tests__/AssetService.test.ts b/workers/site/__tests__/AssetService.test.ts new file mode 100644 index 0000000..2a3b4b7 --- /dev/null +++ b/workers/site/__tests__/AssetService.test.ts @@ -0,0 +1,189 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { getSnapshot, Instance } from 'mobx-state-tree'; +import AssetService from '../services/AssetService'; + +// Define types for testing +type AssetServiceInstance = Instance; + +// Mock the vike/server module +vi.mock('vike/server', () => ({ + renderPage: vi.fn(), +})); + +// Import the mocked renderPage function for assertions +import { renderPage } from 'vike/server'; + +// Mock global types +vi.stubGlobal('ReadableStream', class MockReadableStream {}); +vi.stubGlobal('Response', class MockResponse { + status: number; + headers: Headers; + body: any; + + constructor(body?: any, init?: ResponseInit) { + this.body = body; + this.status = init?.status || 200; + this.headers = new Headers(init?.headers); + } + + clone() { + return this; + } + + async text() { + return this.body?.toString() || ''; + } +}); + +describe('AssetService', () => { + let assetService: AssetServiceInstance; + + beforeEach(() => { + // Create a new instance of the service before each test + assetService = AssetService.create(); + + // Reset mocks + vi.resetAllMocks(); + }); + + describe('Initial state', () => { + it('should have empty env and ctx objects initially', () => { + expect(assetService.env).toEqual({}); + expect(assetService.ctx).toEqual({}); + }); + }); + + describe('setEnv', () => { + it('should set the environment', () => { + const mockEnv = { ASSETS: { fetch: vi.fn() } }; + assetService.setEnv(mockEnv); + expect(assetService.env).toEqual(mockEnv); + }); + }); + + describe('setCtx', () => { + it('should set the execution context', () => { + const mockCtx = { waitUntil: vi.fn() }; + assetService.setCtx(mockCtx); + expect(assetService.ctx).toEqual(mockCtx); + }); + }); + + describe('handleSsr', () => { + it('should return null when httpResponse is not available', async () => { + // Setup mock to return a pageContext without httpResponse + vi.mocked(renderPage).mockResolvedValue({}); + + const url = 'https://example.com'; + const headers = new Headers(); + const env = {}; + + const result = await assetService.handleSsr(url, headers, env); + + // Verify renderPage was called with correct arguments + expect(renderPage).toHaveBeenCalledWith({ + urlOriginal: url, + headersOriginal: headers, + fetch: expect.any(Function), + env, + }); + + // Verify result is null + expect(result).toBeNull(); + }); + + it('should return a Response when httpResponse is available', async () => { + // Create mock stream + const mockStream = new ReadableStream(); + + // Setup mock to return a pageContext with httpResponse + vi.mocked(renderPage).mockResolvedValue({ + httpResponse: { + statusCode: 200, + headers: new Headers({ 'Content-Type': 'text/html' }), + getReadableWebStream: () => mockStream, + }, + }); + + const url = 'https://example.com'; + const headers = new Headers(); + const env = {}; + + const result = await assetService.handleSsr(url, headers, env); + + // Verify renderPage was called with correct arguments + expect(renderPage).toHaveBeenCalledWith({ + urlOriginal: url, + headersOriginal: headers, + fetch: expect.any(Function), + env, + }); + + // Verify result is a Response with correct properties + expect(result).toBeInstanceOf(Response); + expect(result.status).toBe(200); + expect(result.headers.get('Content-Type')).toBe('text/html'); + }); + }); + + describe('handleStaticAssets', () => { + it('should fetch assets from the environment', async () => { + // Create mock request + const request = new Request('https://example.com/static/image.png'); + + // Create mock response + const mockResponse = new Response('Mock asset content', { + status: 200, + headers: { 'Content-Type': 'image/png' }, + }); + + // Create mock environment with ASSETS.fetch + const mockEnv = { + ASSETS: { + fetch: vi.fn().mockResolvedValue(mockResponse), + }, + }; + + // Set the environment + assetService.setEnv(mockEnv); + + // Call the method + const result = await assetService.handleStaticAssets(request, mockEnv); + + // Verify ASSETS.fetch was called with the request + expect(mockEnv.ASSETS.fetch).toHaveBeenCalledWith(request); + + // Verify result is the expected response + expect(result).toBe(mockResponse); + }); + + it('should return a 404 response when an error occurs', async () => { + // Create mock request + const request = new Request('https://example.com/static/not-found.png'); + + // Create mock environment with ASSETS.fetch that throws an error + const mockEnv = { + ASSETS: { + fetch: vi.fn().mockRejectedValue(new Error('Asset not found')), + }, + }; + + // Set the environment + assetService.setEnv(mockEnv); + + // Call the method + const result = await assetService.handleStaticAssets(request, mockEnv); + + // Verify ASSETS.fetch was called with the request + expect(mockEnv.ASSETS.fetch).toHaveBeenCalledWith(request); + + // Verify result is a 404 Response + expect(result).toBeInstanceOf(Response); + expect(result.status).toBe(404); + + // Verify response body + const text = await result.clone().text(); + expect(text).toBe('Asset not found'); + }); + }); +}); diff --git a/workers/site/__tests__/api-router.test.ts b/workers/site/__tests__/api-router.test.ts new file mode 100644 index 0000000..55ab870 --- /dev/null +++ b/workers/site/__tests__/api-router.test.ts @@ -0,0 +1,16 @@ +import { describe, it, expect, vi } from 'vitest'; +import { createRouter } from '../api-router'; + +// Mock the vike/server module +vi.mock('vike/server', () => ({ + renderPage: vi.fn() +})); + +describe('api-router', () => { + // Test that the router is created successfully + it('creates a router', () => { + const router = createRouter(); + expect(router).toBeDefined(); + expect(typeof router.handle).toBe('function'); + }); +}); \ No newline at end of file diff --git a/workers/site/services/AssetService.ts b/workers/site/services/AssetService.ts index 999cdb6..53a29b0 100644 --- a/workers/site/services/AssetService.ts +++ b/workers/site/services/AssetService.ts @@ -40,7 +40,7 @@ export default types async handleStaticAssets(request: Request, env) { console.log("handleStaticAssets"); try { - return env.ASSETS.fetch(request); + return await env.ASSETS.fetch(request); } catch (error) { console.error("Error serving static asset:", error); return new Response("Asset not found", { status: 404 }); diff --git a/workers/site/services/__tests__/AssetService.test.ts b/workers/site/services/__tests__/AssetService.test.ts new file mode 100644 index 0000000..cb56162 --- /dev/null +++ b/workers/site/services/__tests__/AssetService.test.ts @@ -0,0 +1,164 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { getSnapshot } from 'mobx-state-tree'; +import AssetService from '../AssetService'; + +// Mock the vike/server module +vi.mock('vike/server', () => ({ + renderPage: vi.fn(), +})); + +// Import the mocked renderPage function for assertions +import { renderPage } from 'vike/server'; + +describe('AssetService', () => { + let assetService; + + beforeEach(() => { + // Create a new instance of the service before each test + assetService = AssetService.create(); + + // Reset mocks + vi.resetAllMocks(); + }); + + describe('Initial state', () => { + it('should have empty env and ctx objects initially', () => { + expect(assetService.env).toEqual({}); + expect(assetService.ctx).toEqual({}); + }); + }); + + describe('setEnv', () => { + it('should set the environment', () => { + const mockEnv = { ASSETS: { fetch: vi.fn() } }; + assetService.setEnv(mockEnv); + expect(assetService.env).toEqual(mockEnv); + }); + }); + + describe('setCtx', () => { + it('should set the execution context', () => { + const mockCtx = { waitUntil: vi.fn() }; + assetService.setCtx(mockCtx); + expect(assetService.ctx).toEqual(mockCtx); + }); + }); + + describe('handleSsr', () => { + it('should return null when httpResponse is not available', async () => { + // Setup mock to return a pageContext without httpResponse + vi.mocked(renderPage).mockResolvedValue({}); + + const url = 'https://example.com'; + const headers = new Headers(); + const env = {}; + + const result = await assetService.handleSsr(url, headers, env); + + // Verify renderPage was called with correct arguments + expect(renderPage).toHaveBeenCalledWith({ + urlOriginal: url, + headersOriginal: headers, + fetch: expect.any(Function), + env, + }); + + // Verify result is null + expect(result).toBeNull(); + }); + + it('should return a Response when httpResponse is available', async () => { + // Create mock stream + const mockStream = new ReadableStream(); + + // Setup mock to return a pageContext with httpResponse + vi.mocked(renderPage).mockResolvedValue({ + httpResponse: { + statusCode: 200, + headers: new Headers({ 'Content-Type': 'text/html' }), + getReadableWebStream: () => mockStream, + }, + }); + + const url = 'https://example.com'; + const headers = new Headers(); + const env = {}; + + const result = await assetService.handleSsr(url, headers, env); + + // Verify renderPage was called with correct arguments + expect(renderPage).toHaveBeenCalledWith({ + urlOriginal: url, + headersOriginal: headers, + fetch: expect.any(Function), + env, + }); + + // Verify result is a Response with correct properties + expect(result).toBeInstanceOf(Response); + expect(result.status).toBe(200); + expect(result.headers.get('Content-Type')).toBe('text/html'); + }); + }); + + describe('handleStaticAssets', () => { + it('should fetch assets from the environment', async () => { + // Create mock request + const request = new Request('https://example.com/static/image.png'); + + // Create mock response + const mockResponse = new Response('Mock asset content', { + status: 200, + headers: { 'Content-Type': 'image/png' }, + }); + + // Create mock environment with ASSETS.fetch + const mockEnv = { + ASSETS: { + fetch: vi.fn().mockResolvedValue(mockResponse), + }, + }; + + // Set the environment + assetService.setEnv(mockEnv); + + // Call the method + const result = await assetService.handleStaticAssets(request, mockEnv); + + // Verify ASSETS.fetch was called with the request + expect(mockEnv.ASSETS.fetch).toHaveBeenCalledWith(request); + + // Verify result is the expected response + expect(result).toBe(mockResponse); + }); + + it('should return a 404 response when an error occurs', async () => { + // Create mock request + const request = new Request('https://example.com/static/not-found.png'); + + // Create mock environment with ASSETS.fetch that throws an error + const mockEnv = { + ASSETS: { + fetch: vi.fn().mockRejectedValue(new Error('Asset not found')), + }, + }; + + // Set the environment + assetService.setEnv(mockEnv); + + // Call the method + const result = await assetService.handleStaticAssets(request, mockEnv); + + // Verify ASSETS.fetch was called with the request + expect(mockEnv.ASSETS.fetch).toHaveBeenCalledWith(request); + + // Verify result is a 404 Response + expect(result).toBeInstanceOf(Response); + expect(result.status).toBe(404); + + // Verify response body + const text = await result.clone().text(); + expect(text).toBe('Asset not found'); + }); + }); +}); \ No newline at end of file