Spaces:
Running
Running
Download server/searchToken.test.ts from Felladrin/MiniSearch: direct link, hf CLI and curl.
- Browser
- Download file 6.28 kB
-
https://huggingface.co/spaces/Felladrin/MiniSearch/resolve/main/server/searchToken.test.ts
- Command line
-
hf download hf://spaces/Felladrin/MiniSearch/server/searchToken.test.ts
-
curl -L -o searchToken.test.ts https://huggingface.co/spaces/Felladrin/MiniSearch/resolve/main/server/searchToken.test.ts
6.28 kB
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| // `existsSync` is mocked so that if the read-from-file path ever comes back, | |
| // this file fails on the assertion below rather than on a missing mock. | |
| const mockExistsSync = vi.fn(); | |
| const mockReadFileSync = vi.fn(); | |
| const mockWriteFileSync = vi.fn(); | |
| const mockChmodSync = vi.fn(); | |
| vi.mock("node:fs", () => ({ | |
| default: { | |
| existsSync: (...args: unknown[]) => mockExistsSync(...args), | |
| readFileSync: (...args: unknown[]) => mockReadFileSync(...args), | |
| writeFileSync: (...args: unknown[]) => mockWriteFileSync(...args), | |
| chmodSync: (...args: unknown[]) => mockChmodSync(...args), | |
| }, | |
| existsSync: (...args: unknown[]) => mockExistsSync(...args), | |
| readFileSync: (...args: unknown[]) => mockReadFileSync(...args), | |
| writeFileSync: (...args: unknown[]) => mockWriteFileSync(...args), | |
| chmodSync: (...args: unknown[]) => mockChmodSync(...args), | |
| })); | |
| vi.mock("temp-dir", () => ({ default: "/tmp" })); | |
| beforeEach(() => { | |
| // The module holds the token for the life of the process, so every test | |
| // needs its own copy of it. | |
| vi.resetModules(); | |
| vi.clearAllMocks(); | |
| mockExistsSync.mockReset(); | |
| mockReadFileSync.mockReset(); | |
| mockWriteFileSync.mockReset(); | |
| mockChmodSync.mockReset(); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| }); | |
| describe("searchToken", () => { | |
| it("should generate a fresh token instead of adopting one already in the file", async () => { | |
| // The published image used to ship a token in the temp directory, and a | |
| // container that read it shared one token with every other container built | |
| // from that image, and with anyone who pulled the image and read the layer. | |
| mockExistsSync.mockReturnValue(true); | |
| mockReadFileSync.mockReturnValue("token-baked-into-the-image"); | |
| const { getSearchToken } = await import("./searchToken"); | |
| const token = getSearchToken(); | |
| expect(token).not.toBe("token-baked-into-the-image"); | |
| expect(token).toMatch(/^[0-9a-f]{64}$/); | |
| expect(mockWriteFileSync).toHaveBeenCalledTimes(1); | |
| expect(token).toBe(mockWriteFileSync.mock.calls[0][1]); | |
| }); | |
| it("should still serve a usable token when the file cannot be written", async () => { | |
| // `getSearchToken()` runs inside the argon2 try in | |
| // `verifyTokenAndRateLimit.ts`, so a throw out of the token module would | |
| // read as a bad token and 401 every request with nothing in the log. | |
| const accessError = Object.assign(new Error("EACCES: permission denied"), { | |
| code: "EACCES", | |
| }); | |
| mockWriteFileSync.mockImplementation(() => { | |
| throw accessError; | |
| }); | |
| const { getSearchToken } = await import("./searchToken"); | |
| const token = getSearchToken(); | |
| expect(token).toMatch(/^[0-9a-f]{64}$/); | |
| expect(getSearchToken()).toBe(token); | |
| }); | |
| it("should keep the token it generated when the file changes", async () => { | |
| const { getSearchToken } = await import("./searchToken"); | |
| const firstToken = getSearchToken(); | |
| // Another process rewriting the file must not re-key this one: the clients | |
| // already holding the old token have no way of being told about a new one. | |
| mockReadFileSync.mockReturnValue("token-from-another-process"); | |
| expect(getSearchToken()).toBe(firstToken); | |
| expect(mockWriteFileSync).toHaveBeenCalledTimes(1); | |
| }); | |
| it("should serve the new token after regenerating it", async () => { | |
| const { getSearchToken, regenerateSearchToken } = await import( | |
| "./searchToken" | |
| ); | |
| const firstToken = getSearchToken(); | |
| regenerateSearchToken(); | |
| expect(getSearchToken()).not.toBe(firstToken); | |
| expect(getSearchToken()).toBe( | |
| mockWriteFileSync.mock.calls[mockWriteFileSync.mock.calls.length - 1][1], | |
| ); | |
| }); | |
| it("regenerateSearchToken should draw the token from a cryptographic source", async () => { | |
| mockWriteFileSync.mockReturnValue(undefined); | |
| const mathRandomSpy = vi.spyOn(Math, "random"); | |
| const { regenerateSearchToken } = await import("./searchToken"); | |
| regenerateSearchToken(); | |
| regenerateSearchToken(); | |
| const [firstToken, secondToken] = mockWriteFileSync.mock.calls.map( | |
| (call) => call[1] as string, | |
| ); | |
| expect(mathRandomSpy).not.toHaveBeenCalled(); | |
| // 32 random bytes, hex-encoded | |
| expect(firstToken).toMatch(/^[0-9a-f]{64}$/); | |
| expect(firstToken).not.toBe(secondToken); | |
| }); | |
| it("regenerateSearchToken should restrict the token file to its owner", async () => { | |
| mockWriteFileSync.mockReturnValue(undefined); | |
| mockChmodSync.mockReturnValue(undefined); | |
| const { regenerateSearchToken } = await import("./searchToken"); | |
| regenerateSearchToken(); | |
| const [filePath, , options] = mockWriteFileSync.mock.calls[0]; | |
| expect(options).toEqual({ mode: 0o600 }); | |
| // A file left behind by an earlier build keeps its old permissions, | |
| // which `mode` alone would not correct. | |
| expect(mockChmodSync).toHaveBeenCalledWith(filePath, 0o600); | |
| }); | |
| }); | |
| describe("hasSearchTokenFileChanged", () => { | |
| it("should be false before a token has been read", async () => { | |
| const { hasSearchTokenFileChanged } = await import("./searchToken"); | |
| expect(hasSearchTokenFileChanged()).toBe(false); | |
| expect(mockReadFileSync).not.toHaveBeenCalled(); | |
| }); | |
| it("should be false while the file still holds this process's token", async () => { | |
| const { getSearchToken, hasSearchTokenFileChanged } = await import( | |
| "./searchToken" | |
| ); | |
| const token = getSearchToken(); | |
| mockReadFileSync.mockReturnValue(token); | |
| expect(hasSearchTokenFileChanged()).toBe(false); | |
| }); | |
| it("should be true once another process rewrites the file", async () => { | |
| const { getSearchToken, hasSearchTokenFileChanged } = await import( | |
| "./searchToken" | |
| ); | |
| getSearchToken(); | |
| mockReadFileSync.mockReturnValue("token-from-another-process"); | |
| expect(hasSearchTokenFileChanged()).toBe(true); | |
| }); | |
| it("should be true when the file is gone", async () => { | |
| const { getSearchToken, hasSearchTokenFileChanged } = await import( | |
| "./searchToken" | |
| ); | |
| getSearchToken(); | |
| mockReadFileSync.mockImplementation(() => { | |
| throw new Error("ENOENT"); | |
| }); | |
| expect(hasSearchTokenFileChanged()).toBe(true); | |
| }); | |
| }); | |