-
Notifications
You must be signed in to change notification settings - Fork 239
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2347efd
commit 9b6d2d5
Showing
4 changed files
with
406 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
import { beforeEach, describe, expect, mock, test } from "bun:test"; | ||
import { resetFetchMock, testOneFetchCall } from "./mock-fetch.test"; | ||
import { Unkey, type UnkeyError } from "../src"; | ||
|
||
describe("Unkey client", () => { | ||
test("default config", () => { | ||
const rootKey = "root key"; | ||
const client = new Unkey({ rootKey }); | ||
|
||
expect(client.baseUrl).toBe("https://api.unkey.dev"); | ||
// @ts-expect-error | ||
expect(client.rootKey).toBe(rootKey); | ||
expect(client.retry.attempts).toBe(5); | ||
// @ts-expect-error | ||
expect(client.retry.backoff).toStrictEqual(expect.any(Function)); | ||
}); | ||
|
||
test("respects config args", () => { | ||
const baseUrl = "https://example.com/some/path"; | ||
const rootKey = "root key"; | ||
const retry = { | ||
attempts: 3, | ||
backoff(n: number) { | ||
return n * 100; | ||
}, | ||
}; | ||
const client = new Unkey({ baseUrl, rootKey, retry }); | ||
|
||
expect(client.baseUrl).toBe(baseUrl); | ||
// @ts-expect-error | ||
expect(client.rootKey).toBe(rootKey); | ||
expect(client.retry.attempts).toBe(retry.attempts); | ||
expect(client.retry.backoff).toBe(retry.backoff); | ||
}); | ||
|
||
test("throws for invalid rootKey", () => { | ||
// @ts-expect-error | ||
expect(() => new Unkey()).toThrow(); | ||
// @ts-expect-error | ||
expect(() => new Unkey({ rootKey: null })).toThrow( | ||
"Unkey root key must be set, maybe you passed in `undefined` or an empty string?", | ||
); | ||
expect(() => new Unkey({ rootKey: "" })).toThrow( | ||
"Unkey root key must be set, maybe you passed in `undefined` or an empty string?", | ||
); | ||
}); | ||
|
||
test("retry.attempts works", async () => { | ||
const client = new Unkey({ | ||
rootKey: "root key", | ||
retry: { | ||
attempts: 3, | ||
backoff: () => 0, | ||
}, | ||
}); | ||
|
||
const error: UnkeyError = { | ||
code: "FETCH_ERROR", | ||
docs: "docs", | ||
message: "failure", | ||
requestId: "request id", | ||
}; | ||
|
||
const mockFetch = mock(async () => ({ | ||
ok: false, | ||
json: async () => error, | ||
})); | ||
// @ts-ignore | ||
globalThis.fetch = mockFetch; | ||
const response = await client.apis.get({ apiId: "apiId" }); | ||
resetFetchMock(); | ||
|
||
expect(response).toStrictEqual({ error }); | ||
expect(mockFetch).toHaveBeenCalledTimes(4); | ||
}); | ||
|
||
test("calls succeed if fetch fails some retries", async () => { | ||
const client = new Unkey({ | ||
rootKey: "root key", | ||
retry: { | ||
attempts: 3, | ||
backoff: () => 0, | ||
}, | ||
}); | ||
|
||
const result = { | ||
id: "id", | ||
name: "name", | ||
workspaceId: "workspace id", | ||
}; | ||
const mockFetch = mock( | ||
async () => | ||
({ | ||
ok: true, | ||
json: async () => result, | ||
}) as { | ||
ok: boolean; | ||
json?: () => Promise<any>; | ||
}, | ||
) | ||
.mockResolvedValueOnce({ ok: false }) | ||
.mockResolvedValueOnce({ ok: false }); | ||
// @ts-ignore | ||
globalThis.fetch = mockFetch; | ||
const response = await client.apis.get({ apiId: "apiId" }); | ||
resetFetchMock(); | ||
|
||
expect(response).toStrictEqual({ result }); | ||
expect(mockFetch).toHaveBeenCalledTimes(3); | ||
}); | ||
|
||
test("returns an error if fetch throws", async () => { | ||
const client = new Unkey({ | ||
rootKey: "root key", | ||
retry: { | ||
attempts: 3, | ||
backoff: () => 0, | ||
}, | ||
}); | ||
|
||
const error: UnkeyError = { | ||
code: "FETCH_ERROR", | ||
docs: "docs", | ||
message: "failure", | ||
requestId: "request id", | ||
}; | ||
|
||
const mockFetch = mock(() => Promise.reject({ message: "failure" })); | ||
// @ts-ignore | ||
globalThis.fetch = mockFetch; | ||
const response = await client.apis.get({ apiId: "apiId" }); | ||
resetFetchMock(); | ||
|
||
expect(response).toStrictEqual({ | ||
error: { | ||
code: "FETCH_ERROR", | ||
message: "failure", | ||
docs: "https://developer.mozilla.org/en-US/docs/Web/API/fetch", | ||
requestId: "N/A", | ||
}, | ||
}); | ||
expect(mockFetch).toHaveBeenCalledTimes(4); | ||
}); | ||
|
||
test("retry.backoff is called as expected on each failure", async () => { | ||
const backoff = mock((n: number) => 0); | ||
const client = new Unkey({ | ||
rootKey: "root key", | ||
retry: { | ||
attempts: 3, | ||
backoff, | ||
}, | ||
}); | ||
|
||
const error: UnkeyError = { | ||
code: "FETCH_ERROR", | ||
docs: "docs", | ||
message: "failure", | ||
requestId: "request id", | ||
}; | ||
|
||
const mockFetch = mock(() => Promise.reject({ message: "failure" })); | ||
// @ts-ignore | ||
globalThis.fetch = mockFetch; | ||
const response = await client.apis.get({ apiId: "apiId" }); | ||
resetFetchMock(); | ||
|
||
expect(response).toStrictEqual({ | ||
error: { | ||
code: "FETCH_ERROR", | ||
message: "failure", | ||
docs: "https://developer.mozilla.org/en-US/docs/Web/API/fetch", | ||
requestId: "N/A", | ||
}, | ||
}); | ||
expect(backoff.mock.calls).toEqual([[0], [1], [2], [3]]); | ||
expect(backoff).toHaveBeenCalledTimes(4); | ||
expect(mockFetch).toHaveBeenCalledTimes(4); | ||
}); | ||
}); | ||
|
||
describe("snapshot api calls", () => { | ||
beforeEach(() => resetFetchMock()); | ||
const client = new Unkey({ rootKey: "root key" }); | ||
|
||
describe("client.keys", () => { | ||
test("create", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/keys", | ||
method: "POST", | ||
jsonBody: { | ||
name: "key name", | ||
apiId: "key api id", | ||
prefix: "key prefix", | ||
byteLength: 100, | ||
ownerId: "owner id", | ||
meta: { | ||
metaKeyA: "a", | ||
metaKeyB: "b", | ||
}, | ||
expires: 0, | ||
ratelimit: { | ||
type: "fast" as "fast", | ||
limit: 100, | ||
refillRate: 100, | ||
refillInterval: 100, | ||
}, | ||
remaining: 100, | ||
}, | ||
execute: (req) => client.keys.create(req), | ||
})); | ||
|
||
test("update", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/keys/keyId", | ||
method: "PUT", | ||
jsonBody: { | ||
keyId: "keyId", | ||
name: "key name", | ||
ownerId: "owner id", | ||
meta: { | ||
metaKeyA: "a", | ||
metaKeyB: "b", | ||
}, | ||
expires: 0, | ||
ratelimit: { | ||
type: "fast" as "fast", | ||
limit: 100, | ||
refillRate: 100, | ||
refillInterval: 100, | ||
}, | ||
remaining: 100, | ||
}, | ||
execute: (req) => client.keys.update(req), | ||
})); | ||
|
||
test("verify", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/keys/verify", | ||
method: "POST", | ||
jsonBody: { | ||
key: "key", | ||
apiId: "api id", | ||
}, | ||
execute: (req) => client.keys.verify(req), | ||
})); | ||
|
||
test("revoke", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/keys/keyId", | ||
method: "DELETE", | ||
execute: () => client.keys.revoke({ keyId: "keyId" }), | ||
})); | ||
}); | ||
|
||
describe("client.apis", () => { | ||
test("update", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/apis.createApi", | ||
method: "POST", | ||
jsonBody: { | ||
name: "api name", | ||
}, | ||
execute: (req) => client.apis.create(req), | ||
})); | ||
|
||
test("remove", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/apis.removeApi", | ||
method: "POST", | ||
jsonBody: { | ||
apiId: "api id", | ||
}, | ||
execute: (req) => client.apis.remove(req), | ||
})); | ||
|
||
test("get", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/apis/apiId", | ||
method: "GET", | ||
execute: () => client.apis.get({ apiId: "apiId" }), | ||
})); | ||
|
||
test("listKeys", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/apis/apiId/keys?limit=10&offset=5&ownerId=owner+id", | ||
method: "GET", | ||
execute: () => | ||
client.apis.listKeys({ | ||
apiId: "apiId", | ||
limit: 10, | ||
offset: 5, | ||
ownerId: "owner id", | ||
}), | ||
})); | ||
|
||
describe("client._internal", () => { | ||
test("createRootKey", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/internal/rootkeys", | ||
method: "POST", | ||
jsonBody: { | ||
name: "root key name", | ||
expires: 0, | ||
forWorkspaceId: "workspace id", | ||
}, | ||
execute: (req) => client._internal.createRootKey(req), | ||
})); | ||
|
||
test("deleteRootKey", async () => | ||
testOneFetchCall({ | ||
url: "https://api.unkey.dev/v1/internal.removeRootKey", | ||
method: "POST", | ||
jsonBody: { | ||
keyId: "key id", | ||
}, | ||
execute: (req) => client._internal.deleteRootKey(req), | ||
})); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { type AnyFunction } from "bun"; | ||
import { expect, mock, type Mock } from "bun:test"; | ||
|
||
interface TestOneFetchCallArgs<T> { | ||
url: string; | ||
method?: string; | ||
headers?: Record<string, string>; | ||
jsonBody?: T; | ||
execute: (jsonBody: T) => Promise<any>; | ||
} | ||
|
||
const originalFetch = globalThis.fetch; | ||
export function resetFetchMock() { | ||
globalThis.fetch = originalFetch; | ||
} | ||
|
||
export async function testOneFetchCall<T>({ | ||
url, | ||
method, | ||
headers, | ||
jsonBody, | ||
execute, | ||
}: TestOneFetchCallArgs<T>): Promise<Mock<AnyFunction>> { | ||
const mockFetch = mock(async (url, options) => ({ | ||
ok: true, | ||
json: async () => ({}), | ||
})); | ||
|
||
// @ts-ignore | ||
globalThis.fetch = mockFetch; | ||
await execute(jsonBody as T); | ||
|
||
expect(mockFetch).toHaveBeenCalledTimes(1); | ||
|
||
const [callUrl, callOptions] = mockFetch.mock.lastCall!; | ||
expect(callUrl.toString()).toBe(url); | ||
expect<string | undefined>(callOptions.method).toBe(method); | ||
expect(callOptions.headers).toStrictEqual({ | ||
"Content-Type": "application/json", | ||
Authorization: "Bearer root key", | ||
...(headers ?? {}), | ||
}); | ||
expect<string | undefined>(callOptions.body).toBe( | ||
jsonBody === undefined ? undefined : JSON.stringify(jsonBody), | ||
); | ||
|
||
return mockFetch; | ||
} |
Oops, something went wrong.