UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

194 lines (152 loc) 6.79 kB
import { mkdir, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { brotliCompressSync, gzipSync } from "node:zlib"; import { Alepha } from "alepha"; import { AlephaServer, ServerProvider } from "alepha/server"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; import { $serve, AlephaServerStatic, type ServePrimitiveOptions, } from "../index.ts"; // --- Test Setup: Create a temporary directory for static files --- const tempTestDir = join(tmpdir(), `alepha-static-test-${Date.now()}`); const tempWeirdFileName = "weird file 02020&&&&&éééé"; beforeAll(async () => { await mkdir(tempTestDir, { recursive: true }); // Create some test files await writeFile(join(tempTestDir, "index.html"), "<h1>Hello World</h1>"); await writeFile(join(tempTestDir, "style.css"), "body { color: red; }"); await writeFile(join(tempTestDir, "script.js"), "console.log('test');"); await writeFile(join(tempTestDir, ".secret"), "should-not-be-served"); await writeFile(join(tempTestDir, tempWeirdFileName), "ok"); // Create pre-compressed versions const cssContent = "body { color: blue; }"; await writeFile(join(tempTestDir, "compressed.css"), cssContent); await writeFile(join(tempTestDir, "compressed.css.gz"), gzipSync(cssContent)); await writeFile( join(tempTestDir, "compressed.css.br"), brotliCompressSync(cssContent), ); }); afterAll(async () => { // Clean up the temporary directory await rm(tempTestDir, { recursive: true, force: true }); }); // --- Test Suite --- describe("alepha/server/static", () => { const setupServer = async (serveOptions: ServePrimitiveOptions) => { class TestApp { staticContent = $serve({ root: tempTestDir, ...serveOptions }); } const alepha = Alepha.create({ env: { LOG_LEVEL: "error" } }) .with(AlephaServer) .with(AlephaServerStatic) .with(TestApp); await alepha.start(); const server = alepha.inject(ServerProvider); return { hostname: server.hostname, }; }; test("should serve a basic static file with correct content-type", async () => { const { hostname } = await setupServer({}); const response = await fetch(`${hostname}/style.css`); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("text/css"); expect(await response.text()).toBe("body { color: red; }"); }); test("should serve a file with invalid character", async () => { const { hostname } = await setupServer({}); const r1 = await fetch(`${hostname}/${tempWeirdFileName}`); expect(r1.status).toBe(200); expect(await r1.text()).toBe("ok"); const r2 = await fetch(`${hostname}/${encodeURI(tempWeirdFileName)}`); expect(r2.status).toBe(200); expect(await r2.text()).toBe("ok"); }); test("should serve index.html for root path", async () => { const { hostname } = await setupServer({}); const response = await fetch(`${hostname}/`); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("text/html"); expect(await response.text()).toBe("<h1>Hello World</h1>"); }); test("should handle ETag and Last-Modified headers for caching", async () => { const { hostname } = await setupServer({}); const initialResponse = await fetch(`${hostname}/script.js`); const etag = initialResponse.headers.get("etag"); const lastModified = initialResponse.headers.get("last-modified"); expect(etag).toBeDefined(); expect(lastModified).toBeDefined(); // Second request with caching headers const cachedResponse = await fetch(`${hostname}/script.js`, { headers: { "if-none-match": etag!, "if-modified-since": lastModified!, }, }); expect(cachedResponse.status).toBe(304); // Not Modified expect(await cachedResponse.text()).toBe(""); // Body should be empty }); test("should serve pre-compressed .gz file if accepted", async () => { const { hostname } = await setupServer({}); const response = await fetch(`${hostname}/compressed.css`, { headers: { "Accept-Encoding": "gzip, deflate, br" }, }); expect(response.status).toBe(200); expect(response.headers.get("content-encoding")).toBe("br"); expect(response.headers.get("content-type")).toBe("text/css"); // The fetched content will be automatically decompressed by fetch expect(await response.text()).toBe("body { color: blue; }"); }); test("should serve pre-compressed .br file if accepted and preferred", async () => { const { hostname } = await setupServer({}); // Brotli is generally preferred by servers if available const response = await fetch(`${hostname}/compressed.css`, { headers: { "Accept-Encoding": "br, gzip" }, }); expect(response.status).toBe(200); expect(response.headers.get("content-encoding")).toBe("br"); expect(await response.text()).toBe("body { color: blue; }"); }); test("should not serve dotfiles by default", async () => { const { hostname } = await setupServer({}); const response = await fetch(`${hostname}/.secret`); expect(response.status).toBe(404); }); test("should serve dotfiles if ignoreDotEnvFiles is false", async () => { const { hostname } = await setupServer({ ignoreDotEnvFiles: false }); const response = await fetch(`${hostname}/.secret`); expect(response.status).toBe(200); expect(await response.text()).toBe("should-not-be-served"); }); test("should use historyApiFallback for SPA routing", async () => { const { hostname } = await setupServer({ historyApiFallback: true }); // A path that doesn't correspond to a real file const response = await fetch(`${hostname}/some/deep/spa/route`); expect(response.status).toBe(200); expect(response.headers.get("content-type")).toBe("text/html"); expect(await response.text()).toBe("<h1>Hello World</h1>"); // Should still not fallback for paths that look like files const fileResponse = await fetch(`${hostname}/non-existent/style.css`); expect(fileResponse.status).toBe(404); }); test("should apply Cache-Control headers for configured file types", async () => { const { hostname } = await setupServer({ cacheControl: { fileTypes: [".css"], maxAge: [1, "day"], immutable: true, }, }); const cssResponse = await fetch(`${hostname}/style.css`); expect(cssResponse.headers.get("cache-control")).toBe( "public, max-age=86400, immutable", ); // JS file should not have the header const jsResponse = await fetch(`${hostname}/script.js`); expect(jsResponse.headers.get("cache-control")).toBeNull(); }); });