Skip to main content
Module

x/oak/send_test.ts

A middleware framework for handling HTTP with Deno 🐿️ 🦕
Extremely Popular
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
// Copyright 2018-2021 the oak authors. All rights reserved. MIT license.
import { assert, assertEquals, assertStrictEquals, test } from "./test_deps.ts";
import type { Application } from "./application.ts";import type { Context } from "./context.ts";import { readAll, Status } from "./deps.ts";import * as etag from "./etag.ts";import { httpErrors } from "./httpError.ts";import { send } from "./send.ts";
let encodingsAccepted = "identity";
function createMockApp< // deno-lint-ignore no-explicit-any S extends Record<string | number | symbol, any> = Record<string, any>,>( state = {} as S,): Application<S> { return { state, // deno-lint-ignore no-explicit-any } as any;}
function createMockContext< // deno-lint-ignore no-explicit-any S extends Record<string | number | symbol, any> = Record<string, any>,>( app: Application<S>, path = "/", method = "GET",) { // deno-lint-ignore no-explicit-any let body: any; let status = Status.OK; const headers = new Headers(); const resources: number[] = []; return ({ app, request: { acceptsEncodings() { return encodingsAccepted; }, headers: new Headers(), method, path, search: undefined, searchParams: new URLSearchParams(), url: new URL(`http://localhost${path}`), }, response: { get status(): Status { return status; }, set status(value: Status) { status = value; }, // deno-lint-ignore no-explicit-any get body(): any { return body; }, // deno-lint-ignore no-explicit-any set body(value: any) { body = value; }, addResource(rid: number) { resources.push(rid); }, destroy() { body = undefined; for (const rid of resources) { Deno.close(rid); } }, headers, toDomResponse() { return Promise.resolve(new Response(body, { status, headers })); }, toServerResponse() { return Promise.resolve({ status, body, headers, }); }, }, state: app.state, } as unknown) as Context<S>;}
// deno-lint-ignore no-explicit-anyfunction isDenoReader(value: any): value is Deno.Reader { return value && typeof value === "object" && "read" in value && typeof value.read === "function";}
function setup< // deno-lint-ignore no-explicit-any S extends Record<string | number | symbol, any> = Record<string, any>,>( path = "/", method = "GET",): { app: Application<S>; context: Context<S>;} { encodingsAccepted = "identity"; const app = createMockApp<S>(); const context = createMockContext<S>(app, path, method); return { app, context };}
test({ name: "send HTML", async fn() { const { context } = setup("/test.html"); const fixture = await Deno.readFile("./fixtures/test.html"); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".html"); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); assert(context.response.headers.get("last-modified") != null); assertEquals(context.response.headers.get("cache-control"), "max-age=0"); context.response.destroy(); },});
test({ name: "send gzip", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/test.json.gz"); encodingsAccepted = "gzip"; await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertEquals(context.response.headers.get("content-encoding"), "gzip"); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send brotli", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/test.json.br"); encodingsAccepted = "br"; await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertEquals(context.response.headers.get("content-encoding"), "br"); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send identity", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/test.json"); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send 404", async fn() { const { context } = setup("/foo.txt"); encodingsAccepted = "identity"; let didThrow = false; try { await send(context, context.request.url.pathname, { root: "./fixtures", }); } catch (e) { assert(e instanceof httpErrors.NotFound); didThrow = true; } assert(didThrow); },});
test({ name: "send file with spaces", async fn() { const { context } = setup("/test%20file.json"); const fixture = await Deno.readFile("./fixtures/test file.json"); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send hidden file throws 403", async fn() { const { context } = setup("/.test.json"); encodingsAccepted = "identity"; let didThrow = false; try { await send(context, context.request.url.pathname, { root: "./fixtures", }); } catch (e) { assert(e instanceof httpErrors.Forbidden); didThrow = true; } assert(didThrow); },});
test({ name: "send file from hidden dir throws 403", async fn() { const { context } = setup("/.test/test.json"); encodingsAccepted = "identity"; let didThrow = false; try { await send(context, context.request.url.pathname, { root: "./fixtures", }); } catch (e) { assert(e instanceof httpErrors.Forbidden); didThrow = true; } assert(didThrow); },});
test({ name: "send hidden file succeeds when hidden:true", async fn() { const { context } = setup("/.test.json"); const fixture = await Deno.readFile("./fixtures/.test.json"); await send(context, context.request.url.pathname, { root: "./fixtures", hidden: true, maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send file from hidden root succeeds", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/.test/test.json"); await send(context, context.request.url.pathname, { root: "./fixtures/.test", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send url: /../file sends /file", async fn() { const { context } = setup("/../test.json"); const fixture = await Deno.readFile("./fixtures/test.json"); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send path: /../file throws 403", async fn() { const { context } = setup("/../test.json"); encodingsAccepted = "identity"; let didThrow = false; try { await send(context, "/../test.json", { root: "./fixtures", }); } catch (e) { assert(e instanceof httpErrors.Forbidden); didThrow = true; } assert(didThrow); },});
test({ name: "send allows .. in root", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/test.json"); await send(context, context.request.url.pathname, { root: "../oak/fixtures", maxbuffer: 0, }); const serverResponse = context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send If-Modified-Since and it doesn't modified", async fn() { const { context } = setup("/test.json"); const fixtureStat = await Deno.stat("./fixtures/test.json"); context.request.headers.set( "If-Modified-Since", fixtureStat.mtime!.toUTCString(), ); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = await context.response.toServerResponse(); assertStrictEquals(serverResponse.body, undefined); assertEquals(serverResponse.status, 304); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), null, ); context.response.destroy(); },});
test({ name: "send If-Modified-Since and it does modified", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/test.json"); const fixtureStat = await Deno.stat("./fixtures/test.json"); context.request.headers.set( "If-Modified-Since", new Date(fixtureStat.mtime!.getTime() - 10000).toUTCString(), ); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 0, }); const serverResponse = await context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(serverResponse.status, 200); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); context.response.destroy(); },});
test({ name: "send sets etag header - less than maxbuffer", async fn() { const { context } = setup("/test.json"); const fixture = await Deno.readFile("./fixtures/test.json"); await send(context, context.request.url.pathname, { root: "./fixtures" }); const serverResponse = await context.response.toServerResponse(); const body = (await serverResponse).body; assert(body instanceof Uint8Array); assertEquals(body, fixture); assertEquals(serverResponse.status, 200); assertEquals(context.response.type, ".json"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); const etagHeader = context.response.headers.get("etag"); assertEquals(etagHeader, etag.calculate(fixture)); },});
test({ name: "send sets etag header - greater than maxbuffer", async fn() { const { context } = setup("/test.jpg"); const fixture = await Deno.readFile("./fixtures/test.jpg"); await send(context, context.request.url.pathname, { root: "./fixtures", maxbuffer: 300000, }); const serverResponse = await context.response.toServerResponse(); const bodyReader = (await serverResponse).body; assert(isDenoReader(bodyReader)); const body = await readAll(bodyReader); assertEquals(body, fixture); assertEquals(serverResponse.status, 200); assertEquals(context.response.type, ".jpg"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); const etagHeader = context.response.headers.get("etag"); assert(etagHeader && etagHeader.startsWith(`W/"4a3b7-`)); context.response.destroy(); },});
test({ name: "if-none-match header - not modified", async fn() { const { context } = setup("/test.jpg"); const fixture = await Deno.readFile("./fixtures/test.jpg"); context.request.headers.set("If-None-Match", etag.calculate(fixture)); await send(context, context.request.url.pathname, { root: "./fixtures" }); const serverResponse = await context.response.toServerResponse(); assertEquals(serverResponse.status, 304); assertEquals(context.response.headers.get("etag"), etag.calculate(fixture)); },});
test({ name: "if-none-match header - modified", async fn() { const { context } = setup("/test.jpg"); const fixture = await Deno.readFile("./fixtures/test.jpg"); context.request.headers.set( "If-None-Match", `"17-dFpfAd6+866Bo994m4Epzil7k2A"`, ); await send(context, context.request.url.pathname, { root: "./fixtures" }); const serverResponse = await context.response.toServerResponse(); assertEquals(serverResponse.status, 200); assertEquals(context.response.type, ".jpg"); assertStrictEquals(context.response.headers.get("content-encoding"), null); assertEquals( context.response.headers.get("content-length"), String(fixture.length), ); assertEquals(context.response.headers.get("etag"), etag.calculate(fixture)); },});
test({ name: "range header", ignore: Deno.build.os === "windows", async fn() { const { context } = setup("/test.json"); context.request.headers.set("Range", "bytes=0-5"); await send(context, context.request.url.pathname, { root: "./fixtures" }); const response = await context.response.toDomResponse(); assertEquals(response.status, 206); assertEquals(context.response.type, ".json"); assertEquals(context.response.headers.get("content-length"), "5"); assertEquals(await response.text(), `{\n "h`); },});
test({ name: "range header - multiple ranges", ignore: Deno.build.os === "windows", async fn() { const { context } = setup("/test.json"); context.request.headers.set("Range", "bytes=0-5, 6-9"); await send(context, context.request.url.pathname, { root: "./fixtures" }); const response = await context.response.toDomResponse(); assertEquals(response.status, 206); assert( response.headers.get("content-type")!.startsWith( `multipart/byteranges; boundary=`, ), ); assertEquals(response.headers.get("content-length"), "294"); const actual = await response.text(); assert( actual.includes( `\nContent-Type: application/json; charset=utf-8\nContent-Range: 0-5/23\n\n{\n "h\n`, ), ); assert( actual.includes( `\nContent-Type: application/json; charset=utf-8\nContent-Range: 6-9/23\n\nello\n`, ), ); },});