Spaces:
Paused
Paused
Download client/modules/accessKey.test.ts from Nymbo/MiniSearch: direct link, hf CLI and curl.
- Browser
- Download file 6.79 kB
-
https://huggingface.co/spaces/Nymbo/MiniSearch/resolve/main/client/modules/accessKey.test.ts
- Command line
-
hf download hf://spaces/Nymbo/MiniSearch/client/modules/accessKey.test.ts
-
curl -L -o accessKey.test.ts https://huggingface.co/spaces/Nymbo/MiniSearch/resolve/main/client/modules/accessKey.test.ts
6.79 kB
| import { argon2Verify } from "hash-wasm"; | |
| import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; | |
| const mockFetch = vi.fn(); | |
| vi.stubGlobal("fetch", mockFetch); | |
| /** Digest length in bytes, read from the trailing field of the encoded hash. */ | |
| function getDigestLength(encodedHash: string) { | |
| const digest = encodedHash.split("$").pop() as string; | |
| return atob(digest.replace(/-/g, "+").replace(/_/g, "/")).length; | |
| } | |
| /** Runs a validation and returns the hash that was sent to the server. */ | |
| async function getHashSentFor(accessKey: string) { | |
| const { validateAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: true, | |
| json: vi.fn().mockResolvedValue({ valid: true }), | |
| }); | |
| await validateAccessKey(accessKey); | |
| const [, requestInit] = mockFetch.mock.calls[0]; | |
| return JSON.parse(requestInit.body).accessKeyHash as string; | |
| } | |
| vi.mock("./logEntries", () => ({ | |
| addLogEntry: vi.fn(), | |
| })); | |
| vi.mock("@mantine/notifications", () => ({ | |
| notifications: { | |
| show: vi.fn(), | |
| }, | |
| })); | |
| const TIMEOUT_HOURS = 24; | |
| describe("Access Key Module", () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| localStorage.clear(); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| localStorage.clear(); | |
| }); | |
| describe("validateAccessKey", () => { | |
| it("should return valid for a valid access key", async () => { | |
| const { validateAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: true, | |
| json: vi.fn().mockResolvedValue({ valid: true }), | |
| }); | |
| const result = await validateAccessKey("test-key-123"); | |
| expect(result).toBe("valid"); | |
| expect(mockFetch).toHaveBeenCalled(); | |
| }); | |
| it("should return invalid for an invalid access key", async () => { | |
| const { validateAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: true, | |
| json: vi.fn().mockResolvedValue({ valid: false }), | |
| }); | |
| const result = await validateAccessKey("invalid-key"); | |
| expect(result).toBe("invalid"); | |
| }); | |
| it("should return invalid on network error", async () => { | |
| const { validateAccessKey } = await import("./accessKey"); | |
| mockFetch.mockRejectedValue(new Error("Network error")); | |
| const result = await validateAccessKey("test-key"); | |
| expect(result).toBe("invalid"); | |
| }); | |
| it("should send a hash the server can verify against the access key", async () => { | |
| const hash = await getHashSentFor("test-key-123"); | |
| // Mirrors the server-side check in validateAccessKeyServerHook.ts | |
| await expect( | |
| argon2Verify({ password: "test-key-123", hash }), | |
| ).resolves.toBe(true); | |
| await expect( | |
| argon2Verify({ password: "another-key", hash }), | |
| ).resolves.toBe(false); | |
| }); | |
| it("should use a digest long enough to resist blind forgery", async () => { | |
| expect(getDigestLength(await getHashSentFor("test-key-123"))).toBe(32); | |
| }); | |
| it("should return invalid when response is not ok", async () => { | |
| const { validateAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: false, | |
| status: 401, | |
| json: vi.fn().mockResolvedValue({ valid: false }), | |
| }); | |
| const result = await validateAccessKey("test-key"); | |
| expect(result).toBe("invalid"); | |
| }); | |
| it("treats a 429 as a rate-limited refusal and stores no hash", async () => { | |
| const { validateAccessKey } = await import("./accessKey"); | |
| const { notifications } = await import("@mantine/notifications"); | |
| mockFetch.mockResolvedValue({ | |
| ok: false, | |
| status: 429, | |
| }); | |
| const result = await validateAccessKey("test-key"); | |
| expect(result).toBe("rateLimited"); | |
| expect(localStorage.getItem("accessKeyHash")).toBeNull(); | |
| expect(notifications.show).toHaveBeenCalledWith( | |
| expect.objectContaining({ title: "Too many attempts" }), | |
| ); | |
| }); | |
| }); | |
| describe("verifyStoredAccessKey", () => { | |
| it("should return true when stored key is valid", async () => { | |
| const { verifyStoredAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: true, | |
| json: vi.fn().mockResolvedValue({ valid: true }), | |
| }); | |
| localStorage.setItem( | |
| "accessKeyHash", | |
| JSON.stringify({ hash: "test-hash", timestamp: Date.now() }), | |
| ); | |
| const result = await verifyStoredAccessKey(TIMEOUT_HOURS); | |
| expect(result).toBe(true); | |
| }); | |
| it("should return false when no key is stored", async () => { | |
| const { verifyStoredAccessKey } = await import("./accessKey"); | |
| const result = await verifyStoredAccessKey(TIMEOUT_HOURS); | |
| expect(result).toBe(false); | |
| }); | |
| it("should return false when stored key is invalid", async () => { | |
| const { verifyStoredAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: true, | |
| json: vi.fn().mockResolvedValue({ valid: false }), | |
| }); | |
| localStorage.setItem( | |
| "accessKeyHash", | |
| JSON.stringify({ hash: "invalid-hash", timestamp: Date.now() }), | |
| ); | |
| const result = await verifyStoredAccessKey(TIMEOUT_HOURS); | |
| expect(result).toBe(false); | |
| }); | |
| it("should return false without asking the server when the timeout is zero", async () => { | |
| const { verifyStoredAccessKey } = await import("./accessKey"); | |
| localStorage.setItem( | |
| "accessKeyHash", | |
| JSON.stringify({ hash: "test-hash", timestamp: Date.now() }), | |
| ); | |
| const result = await verifyStoredAccessKey(0); | |
| expect(result).toBe(false); | |
| expect(mockFetch).not.toHaveBeenCalled(); | |
| }); | |
| it("should return false when stored key is expired", async () => { | |
| const { verifyStoredAccessKey } = await import("./accessKey"); | |
| const expiredTimestamp = Date.now() - 25 * 60 * 60 * 1000; | |
| localStorage.setItem( | |
| "accessKeyHash", | |
| JSON.stringify({ hash: "old-hash", timestamp: expiredTimestamp }), | |
| ); | |
| const result = await verifyStoredAccessKey(TIMEOUT_HOURS); | |
| expect(result).toBe(false); | |
| }); | |
| it("keeps the stored hash on a 429 instead of deleting it", async () => { | |
| const { verifyStoredAccessKey } = await import("./accessKey"); | |
| mockFetch.mockResolvedValue({ | |
| ok: false, | |
| status: 429, | |
| }); | |
| localStorage.setItem( | |
| "accessKeyHash", | |
| JSON.stringify({ hash: "test-hash", timestamp: Date.now() }), | |
| ); | |
| const result = await verifyStoredAccessKey(TIMEOUT_HOURS); | |
| expect(result).toBe(false); | |
| // A rate-limited validation must not be read as a wrong key. | |
| expect(localStorage.getItem("accessKeyHash")).not.toBeNull(); | |
| }); | |
| }); | |
| }); | |