From ac59d0af9fd6906996db223aeea6573f29e07dc3 Mon Sep 17 00:00:00 2001 From: selftaughtdev Date: Tue, 8 Sep 2026 08:25:56 +0530 Subject: [PATCH] docs and tests for download endpoints --- README.md | 16 +++++++++ routes/index.ts | 2 ++ tests/routes/files-download.test.ts | 56 +++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 tests/routes/files-download.test.ts diff --git a/README.md b/README.md index 993d309..d116b4f 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,22 @@ Response: { } ``` +#### Download File + +Returns the file contents with `Content-Disposition: attachment`, which makes +browsers save the file instead of rendering it. Binary files are served as +`application/octet-stream`, text files as `text/plain`. + +```http +GET /files/download?file_id=1 +# or +GET /files/download?file_path=path/to/file.txt +# or +GET /files/download/path/to/file.txt + +Response: raw file contents +``` + #### List Files ```http diff --git a/routes/index.ts b/routes/index.ts index ade1972..41da4b0 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -13,6 +13,8 @@ This is a simple file server API, it has the following API: /health - Health check /files/get?file_path=... - Get a file +/files/download?file_path=... - Download a file (attachment) +/files/download/ - Download a file by path /files/list - List all files /files/upsert - Upsert a file diff --git a/tests/routes/files-download.test.ts b/tests/routes/files-download.test.ts new file mode 100644 index 0000000..ba7b9e5 --- /dev/null +++ b/tests/routes/files-download.test.ts @@ -0,0 +1,56 @@ +import { test, expect } from "bun:test" +import { getTestServer } from "tests/fixtures/get-test-server" + +test("download by file_id", async () => { + const { axios } = await getTestServer() + + const upsertRes = await axios.post("/files/upsert", { + file_path: "/by-id.txt", + text_content: "Downloaded by id", + }) + const fileId = upsertRes.data.file.file_id + + const downloadRes = await axios.get("/files/download", { + params: { file_id: fileId }, + }) + expect(downloadRes.status).toBe(200) + expect(downloadRes.data).toBe("Downloaded by id") + expect(downloadRes.headers.get("content-disposition")).toBe( + 'attachment; filename="by-id.txt"', + ) +}) + +test("download nested path via path form", async () => { + const { axios } = await getTestServer() + + await axios.post("/files/upsert", { + file_path: "/nested/dir/file.txt", + text_content: "Nested content", + }) + + const downloadRes = await axios.get("/files/download/nested/dir/file.txt") + expect(downloadRes.status).toBe(200) + expect(downloadRes.data).toBe("Nested content") + expect(downloadRes.headers.get("content-disposition")).toBe( + 'attachment; filename="file.txt"', + ) +}) + +test("nested path form returns 404 for missing file", async () => { + const { axios } = await getTestServer() + + expect( + axios.get("/files/download/some/missing/file.txt"), + ).rejects.toMatchObject({ + status: 404, + data: "File not found", + }) +}) + +test("index page lists download endpoints", async () => { + const { axios } = await getTestServer() + + const indexRes = await axios.get("/") + expect(indexRes.status).toBe(200) + expect(indexRes.data).toContain("/files/download") +})