quickbase-js 0.1.0-beta.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +270 -60
- package/dist/esm/quickbase.d.ts +2553 -3220
- package/dist/esm/quickbase.js +1545 -3443
- package/dist/esm/quickbase.js.map +1 -1
- package/dist/esm/quickbase.min.js +1 -1
- package/dist/esm/quickbase.min.js.map +1 -1
- package/dist/umd/quickbase.umd.js +1544 -3625
- package/dist/umd/quickbase.umd.js.map +1 -1
- package/dist/umd/quickbase.umd.min.js +1 -1
- package/dist/umd/quickbase.umd.min.js.map +1 -1
- package/package.json +25 -12
- package/CHANGELOG.md +0 -184
- package/build-common.js +0 -131
- package/build-esm.js +0 -71
- package/build-umd.js +0 -69
- package/examples/quicbase-jsCDN.html +0 -36
- package/examples/react.js +0 -48
- package/examples/vanillajs.html +0 -47
- package/open-api/fix-spec-main.ts +0 -97
- package/open-api/generate-override-report.ts +0 -214
- package/open-api/generate-unified-interface.ts +0 -336
- package/open-api/output/override-report.html +0 -11
- package/open-api/output/override-report.json +0 -14395
- package/open-api/output/quickbase-fixed.json +0 -11191
- package/open-api/regenerate-client.ts +0 -116
- package/open-api/schema/enhance-general.ts +0 -100
- package/open-api/schema/fix-arrays.ts +0 -69
- package/open-api/schema/tags/apps.ts +0 -90
- package/open-api/schema/tags/fields.ts +0 -471
- package/open-api/schema/tags/groups.ts +0 -51
- package/open-api/schema/tags/index.ts +0 -66
- package/open-api/schema/tags/records.ts +0 -115
- package/open-api/schema/tags/tables.ts +0 -211
- package/open-api/schema/tags/users.ts +0 -47
- package/open-api/types/spec.ts +0 -42
- package/open-api/utils/generateJsDoc.ts +0 -96
- package/open-api/utils/infer-schema.ts +0 -40
- package/open-api/utils/naming.ts +0 -10
- package/playwright.config.ts +0 -37
- package/rollup.config.js +0 -114
- package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +0 -46878
- package/specs/split/apps.json +0 -1315
- package/specs/split/auth.json +0 -191
- package/specs/split/fields.json +0 -3869
- package/specs/split/records.json +0 -754
- package/specs/split/reports.json +0 -619
- package/specs/split/tables.json +0 -1605
- package/src/RateLimitError.ts +0 -12
- package/src/ThrottleBucket.ts +0 -41
- package/src/authorizationStrategy.ts +0 -246
- package/src/generated-unified/QuickbaseClient.ts +0 -1049
- package/src/generated-unified/missing-types-report.json +0 -3
- package/src/index.ts +0 -1
- package/src/invokeMethod.ts +0 -242
- package/src/quickbaseClient.ts +0 -271
- package/src/rateLimiter.ts +0 -25
- package/src/tokenCache.ts +0 -35
- package/src/utils.ts +0 -7
- package/test-esm.js +0 -1
- package/test-results.json +0 -2050
- package/test-umd.js +0 -1
- package/test.ts +0 -1
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +0 -159
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +0 -219
- package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +0 -162
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +0 -153
- package/tests/playwright/qb/auth/getTempToken.test.ts +0 -152
- package/tests/playwright/qb/auth/tempTokenPrefetch.test.ts +0 -179
- package/tests/playwright/qb/auth/tempTokenRenewal.test.ts +0 -133
- package/tests/setup.ts +0 -53
- package/tests/vitest/qb/apps/copyApp.test.ts +0 -76
- package/tests/vitest/qb/apps/createApp.test.ts +0 -64
- package/tests/vitest/qb/apps/deleteApp.test.ts +0 -76
- package/tests/vitest/qb/apps/getApp.test.ts +0 -44
- package/tests/vitest/qb/apps/getAppEvents.test.ts +0 -67
- package/tests/vitest/qb/apps/updateApp.test.ts +0 -58
- package/tests/vitest/qb/auth/mockQuickbaseServer.ts +0 -60
- package/tests/vitest/qb/auth/retryUserToken401.test.ts +0 -73
- package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +0 -65
- package/tests/vitest/qb/fields/createField.test.ts +0 -77
- package/tests/vitest/qb/fields/deleteFields.test.ts +0 -83
- package/tests/vitest/qb/fields/getField.test.ts +0 -85
- package/tests/vitest/qb/fields/getFieldUsage.test.ts +0 -82
- package/tests/vitest/qb/fields/getFields.test.ts +0 -232
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +0 -77
- package/tests/vitest/qb/fields/updateField.test.ts +0 -93
- package/tests/vitest/qb/formulas/runFormula.test.ts +0 -96
- package/tests/vitest/qb/index.ts +0 -29
- package/tests/vitest/qb/records/deleteRecords.test.ts +0 -110
- package/tests/vitest/qb/records/runQuery.test.ts +0 -146
- package/tests/vitest/qb/records/upsert.test.ts +0 -102
- package/tests/vitest/qb/tables/createRelationship.test.ts +0 -102
- package/tests/vitest/qb/tables/createTable.test.ts +0 -55
- package/tests/vitest/qb/tables/deleteRelationship.test.ts +0 -143
- package/tests/vitest/qb/tables/deleteTable.test.ts +0 -50
- package/tests/vitest/qb/tables/getAppTables.test.ts +0 -46
- package/tests/vitest/qb/tables/getRelationships.test.ts +0 -80
- package/tests/vitest/qb/tables/getTable.test.ts +0 -26
- package/tests/vitest/qb/tables/updateRelationship.test.ts +0 -250
- package/tests/vitest/qb/tables/updateTable.test.ts +0 -48
- package/tests/vitest/unit/ThrottleBucket.test.ts +0 -95
- package/tests/vitest/unit/apps/copyApp.test.ts +0 -581
- package/tests/vitest/unit/apps/createApp.test.ts +0 -186
- package/tests/vitest/unit/apps/deleteApp.test.ts +0 -216
- package/tests/vitest/unit/apps/getApp.test.ts +0 -128
- package/tests/vitest/unit/apps/getAppEvents.test.ts +0 -193
- package/tests/vitest/unit/apps/updateApp.test.ts +0 -412
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +0 -63
- package/tests/vitest/unit/auth/getTempToken.test.ts +0 -119
- package/tests/vitest/unit/auth/retryTempToken401.test.ts +0 -95
- package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +0 -235
- package/tests/vitest/unit/auth/retryUserToken401.test.ts +0 -69
- package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +0 -325
- package/tests/vitest/unit/fields/createField.test.ts +0 -451
- package/tests/vitest/unit/fields/deleteFields.test.ts +0 -141
- package/tests/vitest/unit/fields/getField.test.ts +0 -189
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +0 -180
- package/tests/vitest/unit/fields/getFields.test.ts +0 -122
- package/tests/vitest/unit/fields/getFieldsUsage.test.ts +0 -160
- package/tests/vitest/unit/fields/updateField.test.ts +0 -285
- package/tests/vitest/unit/formulas/runFormula.test.ts +0 -205
- package/tests/vitest/unit/index.ts +0 -34
- package/tests/vitest/unit/inferHttpMethod.test.ts +0 -131
- package/tests/vitest/unit/rateLimit.test.ts +0 -191
- package/tests/vitest/unit/records/deleteRecords.test.ts +0 -112
- package/tests/vitest/unit/records/runQuery.test.ts +0 -102
- package/tests/vitest/unit/records/upsert.test.ts +0 -234
- package/tests/vitest/unit/tables/createRelationship.test.ts +0 -212
- package/tests/vitest/unit/tables/createTable.test.ts +0 -70
- package/tests/vitest/unit/tables/deleteRelationship.test.ts +0 -97
- package/tests/vitest/unit/tables/deleteTable.test.ts +0 -77
- package/tests/vitest/unit/tables/getAppTables.test.ts +0 -85
- package/tests/vitest/unit/tables/getRelationships.test.ts +0 -259
- package/tests/vitest/unit/tables/getTable.test.ts +0 -233
- package/tests/vitest/unit/tables/updateRelationship.test.ts +0 -307
- package/tests/vitest/unit/tables/updateTable.test.ts +0 -80
- package/tests/vitest/unit/tokenLifespan.test.ts +0 -204
- package/tests/vitest/unit/umd.test.ts +0 -8
- package/text.txt +0 -104
- package/tools/log-gen.ts +0 -244
- package/tools/log-gen.yaml +0 -5424
- package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
- package/tsconfig.build.json +0 -17
- package/tsconfig.json +0 -32
- package/versionrc.json +0 -9
- package/vitest.config.ts +0 -27
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
// tests/vitest/unit/auth/getTempToken.test.ts
|
|
2
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
3
|
-
import {
|
|
4
|
-
createClient,
|
|
5
|
-
mockFetch,
|
|
6
|
-
QB_REALM,
|
|
7
|
-
QB_TABLE_ID_1,
|
|
8
|
-
} from "@tests/setup.ts";
|
|
9
|
-
import { TokenCache } from "../../../../src/tokenCache"; // Corrected path
|
|
10
|
-
import { AuthApi } from "../../../../src/generated/apis/AuthApi";
|
|
11
|
-
|
|
12
|
-
describe("QuickbaseClient Unit - getTempTokenDBID", () => {
|
|
13
|
-
let client: ReturnType<typeof createClient>;
|
|
14
|
-
let tokenCache: TokenCache;
|
|
15
|
-
let authApi: AuthApi;
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
mockFetch.mockClear();
|
|
19
|
-
tokenCache = new TokenCache();
|
|
20
|
-
client = createClient(mockFetch, {
|
|
21
|
-
useTempTokens: true,
|
|
22
|
-
debug: true,
|
|
23
|
-
tokenCache,
|
|
24
|
-
});
|
|
25
|
-
authApi = new AuthApi(client["configuration"]);
|
|
26
|
-
vi.spyOn(authApi, "getTempTokenDBIDRaw").mockImplementation(
|
|
27
|
-
async (params) => {
|
|
28
|
-
const mockToken = "b123xyz_temp_token";
|
|
29
|
-
if (params.dbid === QB_TABLE_ID_1) {
|
|
30
|
-
return {
|
|
31
|
-
value: () => Promise.resolve({ temporaryAuthorization: mockToken }),
|
|
32
|
-
} as any;
|
|
33
|
-
}
|
|
34
|
-
throw new Error("Unexpected dbid");
|
|
35
|
-
}
|
|
36
|
-
);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("initializes without errors", () => {
|
|
40
|
-
expect(client).toBeDefined();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("has getTempTokenDBID method", () => {
|
|
44
|
-
expect(typeof client.getTempTokenDBID).toBe("function");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("fetches and caches temp token on first call", async () => {
|
|
48
|
-
const mockToken = "b123xyz_temp_token";
|
|
49
|
-
|
|
50
|
-
mockFetch.mockResolvedValueOnce(
|
|
51
|
-
new Response(JSON.stringify({ temporaryAuthorization: mockToken }), {
|
|
52
|
-
status: 200,
|
|
53
|
-
headers: { "Content-Type": "application/json" },
|
|
54
|
-
})
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const result = await client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 });
|
|
58
|
-
expect(result).toEqual({ temporaryAuthorization: mockToken });
|
|
59
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
60
|
-
`https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
|
|
61
|
-
expect.objectContaining({
|
|
62
|
-
method: "GET",
|
|
63
|
-
headers: expect.objectContaining({
|
|
64
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
65
|
-
"Content-Type": "application/json",
|
|
66
|
-
}),
|
|
67
|
-
credentials: "include",
|
|
68
|
-
})
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("reuses cached temp token on second call", async () => {
|
|
73
|
-
const mockToken = "b123xyz_temp_token";
|
|
74
|
-
|
|
75
|
-
mockFetch.mockResolvedValueOnce(
|
|
76
|
-
new Response(JSON.stringify({ temporaryAuthorization: mockToken }), {
|
|
77
|
-
status: 200,
|
|
78
|
-
headers: { "Content-Type": "application/json" },
|
|
79
|
-
})
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const firstResult = await client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 });
|
|
83
|
-
expect(firstResult).toEqual({ temporaryAuthorization: mockToken });
|
|
84
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
85
|
-
|
|
86
|
-
mockFetch.mockClear();
|
|
87
|
-
const secondResult = await client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 });
|
|
88
|
-
expect(secondResult).toEqual({ temporaryAuthorization: mockToken });
|
|
89
|
-
expect(mockFetch).not.toHaveBeenCalled();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("handles API error", async () => {
|
|
93
|
-
mockFetch.mockResolvedValueOnce(
|
|
94
|
-
new Response(JSON.stringify({ message: "Unauthorized" }), {
|
|
95
|
-
status: 401,
|
|
96
|
-
headers: { "Content-Type": "application/json" },
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
await expect(
|
|
101
|
-
client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 })
|
|
102
|
-
).rejects.toSatisfy((error: Error) => {
|
|
103
|
-
expect(error).toBeInstanceOf(Error);
|
|
104
|
-
expect(error.message).toBe("API Error: Unauthorized (Status: 401)");
|
|
105
|
-
return true;
|
|
106
|
-
});
|
|
107
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
108
|
-
`https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
|
|
109
|
-
expect.objectContaining({
|
|
110
|
-
method: "GET",
|
|
111
|
-
headers: expect.objectContaining({
|
|
112
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
113
|
-
"Content-Type": "application/json",
|
|
114
|
-
}),
|
|
115
|
-
credentials: "include",
|
|
116
|
-
})
|
|
117
|
-
);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// tests/vitest/unit/auth/retryTempToken401.test.ts
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, vi } from "vitest";
|
|
4
|
-
import {
|
|
5
|
-
createClient,
|
|
6
|
-
mockFetch,
|
|
7
|
-
QB_REALM,
|
|
8
|
-
QB_TABLE_ID_1,
|
|
9
|
-
} from "@tests/setup.ts";
|
|
10
|
-
|
|
11
|
-
describe("QuickbaseClient Unit - Temp Token Retry on 401", () => {
|
|
12
|
-
it("creates a new token on 401 and retries successfully", async () => {
|
|
13
|
-
mockFetch
|
|
14
|
-
.mockResolvedValueOnce(
|
|
15
|
-
new Response(
|
|
16
|
-
JSON.stringify({ temporaryAuthorization: "initial_token" }),
|
|
17
|
-
{ status: 200 }
|
|
18
|
-
)
|
|
19
|
-
)
|
|
20
|
-
.mockResolvedValueOnce(
|
|
21
|
-
new Response(JSON.stringify({ message: "Unauthorized" }), {
|
|
22
|
-
status: 401,
|
|
23
|
-
})
|
|
24
|
-
)
|
|
25
|
-
.mockResolvedValueOnce(
|
|
26
|
-
new Response(
|
|
27
|
-
JSON.stringify({ temporaryAuthorization: "new_token_456" }),
|
|
28
|
-
{ status: 200 }
|
|
29
|
-
)
|
|
30
|
-
)
|
|
31
|
-
.mockResolvedValueOnce(
|
|
32
|
-
new Response(JSON.stringify([{ id: 1, label: "Field1" }]), {
|
|
33
|
-
status: 200,
|
|
34
|
-
})
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const consoleSpy = vi.spyOn(console, "log");
|
|
38
|
-
const client = createClient(mockFetch, {
|
|
39
|
-
realm: QB_REALM,
|
|
40
|
-
useTempTokens: true,
|
|
41
|
-
debug: true,
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const result = await client.getFields({ tableId: QB_TABLE_ID_1 });
|
|
45
|
-
|
|
46
|
-
expect(result).toEqual([{ id: 1, label: "Field1" }]);
|
|
47
|
-
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
48
|
-
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
49
|
-
1,
|
|
50
|
-
`https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
|
|
51
|
-
expect.objectContaining({
|
|
52
|
-
credentials: "include",
|
|
53
|
-
method: "GET",
|
|
54
|
-
})
|
|
55
|
-
);
|
|
56
|
-
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
57
|
-
2,
|
|
58
|
-
`https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
|
|
59
|
-
expect.objectContaining({
|
|
60
|
-
method: "GET",
|
|
61
|
-
headers: expect.objectContaining({
|
|
62
|
-
Authorization: "QB-TEMP-TOKEN initial_token",
|
|
63
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
64
|
-
"Content-Type": "application/json",
|
|
65
|
-
}),
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
69
|
-
3,
|
|
70
|
-
`https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
|
|
71
|
-
expect.objectContaining({
|
|
72
|
-
credentials: "include",
|
|
73
|
-
method: "GET",
|
|
74
|
-
})
|
|
75
|
-
);
|
|
76
|
-
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
77
|
-
4,
|
|
78
|
-
`https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
|
|
79
|
-
expect.objectContaining({
|
|
80
|
-
method: "GET",
|
|
81
|
-
headers: expect.objectContaining({
|
|
82
|
-
Authorization: "QB-TEMP-TOKEN new_token_456",
|
|
83
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
84
|
-
"Content-Type": "application/json",
|
|
85
|
-
}),
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
89
|
-
"Authorization error for getFields (temp token), refreshing token:"
|
|
90
|
-
);
|
|
91
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
92
|
-
"[getFields] Retrying with token: new_token_..."
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
-
import { createClient, mockFetch } from "@tests/setup.ts";
|
|
3
|
-
|
|
4
|
-
describe("QuickbaseClient - Temp Token Retry Limit", () => {
|
|
5
|
-
let client: ReturnType<typeof createClient>;
|
|
6
|
-
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
mockFetch.mockClear();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("exhausts retries after maxRetries (3) and throws an error", async () => {
|
|
12
|
-
client = createClient(mockFetch, {
|
|
13
|
-
useTempTokens: true,
|
|
14
|
-
debug: true,
|
|
15
|
-
maxRetries: 3, // Explicitly set maxRetries to 3
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const mockDbid = "mockDbid123";
|
|
19
|
-
let callCount = 0;
|
|
20
|
-
|
|
21
|
-
mockFetch.mockImplementation((url) => {
|
|
22
|
-
callCount++;
|
|
23
|
-
// Initial temp token fetch
|
|
24
|
-
if (url.includes("auth/temporary") && callCount === 1) {
|
|
25
|
-
return Promise.resolve({
|
|
26
|
-
ok: true,
|
|
27
|
-
status: 200,
|
|
28
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_1" }),
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
// First attempt: 401
|
|
32
|
-
if (url.includes("fields") && callCount === 2) {
|
|
33
|
-
return Promise.resolve({
|
|
34
|
-
ok: false,
|
|
35
|
-
status: 401,
|
|
36
|
-
json: () => Promise.resolve({ message: "Unauthorized 1" }),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
// Second temp token fetch
|
|
40
|
-
if (url.includes("auth/temporary") && callCount === 3) {
|
|
41
|
-
return Promise.resolve({
|
|
42
|
-
ok: true,
|
|
43
|
-
status: 200,
|
|
44
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_2" }),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
// Second attempt: 401
|
|
48
|
-
if (url.includes("fields") && callCount === 4) {
|
|
49
|
-
return Promise.resolve({
|
|
50
|
-
ok: false,
|
|
51
|
-
status: 401,
|
|
52
|
-
json: () => Promise.resolve({ message: "Unauthorized 2" }),
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
// Third temp token fetch
|
|
56
|
-
if (url.includes("auth/temporary") && callCount === 5) {
|
|
57
|
-
return Promise.resolve({
|
|
58
|
-
ok: true,
|
|
59
|
-
status: 200,
|
|
60
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_3" }),
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
// Third attempt: 401
|
|
64
|
-
if (url.includes("fields") && callCount === 6) {
|
|
65
|
-
return Promise.resolve({
|
|
66
|
-
ok: false,
|
|
67
|
-
status: 401,
|
|
68
|
-
json: () => Promise.resolve({ message: "Unauthorized 3" }),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
// Fourth temp token fetch
|
|
72
|
-
if (url.includes("auth/temporary") && callCount === 7) {
|
|
73
|
-
return Promise.resolve({
|
|
74
|
-
ok: true,
|
|
75
|
-
status: 200,
|
|
76
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_4" }),
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
// Fourth attempt: 401
|
|
80
|
-
if (url.includes("fields") && callCount === 8) {
|
|
81
|
-
return Promise.resolve({
|
|
82
|
-
ok: false,
|
|
83
|
-
status: 401,
|
|
84
|
-
json: () => Promise.resolve({ message: "Unauthorized 4" }),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const consoleSpy = vi.spyOn(console, "log");
|
|
91
|
-
|
|
92
|
-
await expect(client.getFields({ tableId: mockDbid })).rejects.toThrow(
|
|
93
|
-
"API Error: Unauthorized 4 (Status: 401)"
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
expect(mockFetch).toHaveBeenCalledTimes(8); // 4 token fetches + 4 API attempts
|
|
97
|
-
|
|
98
|
-
const tokenLogs = consoleSpy.mock.calls.filter((call) =>
|
|
99
|
-
call[0].includes("Fetched and cached new token")
|
|
100
|
-
);
|
|
101
|
-
expect(tokenLogs).toHaveLength(4);
|
|
102
|
-
expect(tokenLogs).toContainEqual([
|
|
103
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
104
|
-
"token_1",
|
|
105
|
-
]);
|
|
106
|
-
expect(tokenLogs).toContainEqual([
|
|
107
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
108
|
-
"token_2",
|
|
109
|
-
]);
|
|
110
|
-
expect(tokenLogs).toContainEqual([
|
|
111
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
112
|
-
"token_3",
|
|
113
|
-
]);
|
|
114
|
-
expect(tokenLogs).toContainEqual([
|
|
115
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
116
|
-
"token_4",
|
|
117
|
-
]);
|
|
118
|
-
|
|
119
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
120
|
-
"Authorization error for getFields (temp token), refreshing token:"
|
|
121
|
-
);
|
|
122
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
123
|
-
"[getFields] Retrying with token: token_2..."
|
|
124
|
-
);
|
|
125
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
126
|
-
"[getFields] Retrying with token: token_3..."
|
|
127
|
-
);
|
|
128
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
129
|
-
"[getFields] Retrying with token: token_4..."
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
consoleSpy.mockRestore();
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("exhausts retries after maxRetries (2) and throws an error", async () => {
|
|
136
|
-
client = createClient(mockFetch, {
|
|
137
|
-
useTempTokens: true,
|
|
138
|
-
debug: true,
|
|
139
|
-
maxRetries: 2, // Explicitly set maxRetries to 2
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const mockDbid = "mockDbid123";
|
|
143
|
-
let callCount = 0;
|
|
144
|
-
|
|
145
|
-
mockFetch.mockImplementation((url) => {
|
|
146
|
-
callCount++;
|
|
147
|
-
// Initial temp token fetch
|
|
148
|
-
if (url.includes("auth/temporary") && callCount === 1) {
|
|
149
|
-
return Promise.resolve({
|
|
150
|
-
ok: true,
|
|
151
|
-
status: 200,
|
|
152
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_1" }),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// First attempt: 401
|
|
156
|
-
if (url.includes("fields") && callCount === 2) {
|
|
157
|
-
return Promise.resolve({
|
|
158
|
-
ok: false,
|
|
159
|
-
status: 401,
|
|
160
|
-
json: () => Promise.resolve({ message: "Unauthorized 1" }),
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
// Second temp token fetch
|
|
164
|
-
if (url.includes("auth/temporary") && callCount === 3) {
|
|
165
|
-
return Promise.resolve({
|
|
166
|
-
ok: true,
|
|
167
|
-
status: 200,
|
|
168
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_2" }),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
// Second attempt: 401
|
|
172
|
-
if (url.includes("fields") && callCount === 4) {
|
|
173
|
-
return Promise.resolve({
|
|
174
|
-
ok: false,
|
|
175
|
-
status: 401,
|
|
176
|
-
json: () => Promise.resolve({ message: "Unauthorized 2" }),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
// Third temp token fetch
|
|
180
|
-
if (url.includes("auth/temporary") && callCount === 5) {
|
|
181
|
-
return Promise.resolve({
|
|
182
|
-
ok: true,
|
|
183
|
-
status: 200,
|
|
184
|
-
json: () => Promise.resolve({ temporaryAuthorization: "token_3" }),
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
// Third attempt: 401
|
|
188
|
-
if (url.includes("fields") && callCount === 6) {
|
|
189
|
-
return Promise.resolve({
|
|
190
|
-
ok: false,
|
|
191
|
-
status: 401,
|
|
192
|
-
json: () => Promise.resolve({ message: "Unauthorized 3" }),
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const consoleSpy = vi.spyOn(console, "log");
|
|
199
|
-
|
|
200
|
-
await expect(client.getFields({ tableId: mockDbid })).rejects.toThrow(
|
|
201
|
-
"API Error: Unauthorized 3 (Status: 401)"
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
expect(mockFetch).toHaveBeenCalledTimes(6); // 3 token fetches + 3 API attempts
|
|
205
|
-
|
|
206
|
-
const tokenLogs = consoleSpy.mock.calls.filter((call) =>
|
|
207
|
-
call[0].includes("Fetched and cached new token")
|
|
208
|
-
);
|
|
209
|
-
expect(tokenLogs).toHaveLength(3);
|
|
210
|
-
expect(tokenLogs).toContainEqual([
|
|
211
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
212
|
-
"token_1",
|
|
213
|
-
]);
|
|
214
|
-
expect(tokenLogs).toContainEqual([
|
|
215
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
216
|
-
"token_2",
|
|
217
|
-
]);
|
|
218
|
-
expect(tokenLogs).toContainEqual([
|
|
219
|
-
"Fetched and cached new token for dbid: mockDbid123",
|
|
220
|
-
"token_3",
|
|
221
|
-
]);
|
|
222
|
-
|
|
223
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
224
|
-
"Authorization error for getFields (temp token), refreshing token:"
|
|
225
|
-
);
|
|
226
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
227
|
-
"[getFields] Retrying with token: token_2..."
|
|
228
|
-
);
|
|
229
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
230
|
-
"[getFields] Retrying with token: token_3..."
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
consoleSpy.mockRestore();
|
|
234
|
-
});
|
|
235
|
-
});
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
// tests/vitest/unit/auth/retryUserToken401.test.ts
|
|
2
|
-
|
|
3
|
-
import { describe, it, expect, vi } from "vitest";
|
|
4
|
-
import {
|
|
5
|
-
createClient,
|
|
6
|
-
mockFetch,
|
|
7
|
-
QB_REALM,
|
|
8
|
-
QB_TABLE_ID_1,
|
|
9
|
-
} from "@tests/setup.ts";
|
|
10
|
-
|
|
11
|
-
describe("QuickbaseClient Unit - User Token Retry on 401", () => {
|
|
12
|
-
it("retries with the same user token on 401 and succeeds", async () => {
|
|
13
|
-
const userToken = "b9f3pk_q4jd_0_b4qu5eebyvuix3xs57ysd7zn3";
|
|
14
|
-
mockFetch
|
|
15
|
-
.mockResolvedValueOnce(
|
|
16
|
-
new Response(JSON.stringify({ message: "Unauthorized" }), {
|
|
17
|
-
status: 401,
|
|
18
|
-
})
|
|
19
|
-
)
|
|
20
|
-
.mockResolvedValueOnce(
|
|
21
|
-
new Response(JSON.stringify([{ id: 1, label: "Field1" }]), {
|
|
22
|
-
status: 200,
|
|
23
|
-
})
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const consoleSpy = vi.spyOn(console, "log");
|
|
27
|
-
const client = createClient(mockFetch, {
|
|
28
|
-
realm: QB_REALM,
|
|
29
|
-
userToken,
|
|
30
|
-
useTempTokens: false,
|
|
31
|
-
debug: true,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const result = await client.getFields({ tableId: QB_TABLE_ID_1 });
|
|
35
|
-
|
|
36
|
-
expect(result).toEqual([{ id: 1, label: "Field1" }]);
|
|
37
|
-
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
38
|
-
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
39
|
-
1,
|
|
40
|
-
`https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
|
|
41
|
-
expect.objectContaining({
|
|
42
|
-
method: "GET",
|
|
43
|
-
headers: expect.objectContaining({
|
|
44
|
-
Authorization: `QB-USER-TOKEN ${userToken}`,
|
|
45
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
46
|
-
"Content-Type": "application/json",
|
|
47
|
-
}),
|
|
48
|
-
})
|
|
49
|
-
);
|
|
50
|
-
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
51
|
-
2,
|
|
52
|
-
`https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
|
|
53
|
-
expect.objectContaining({
|
|
54
|
-
method: "GET",
|
|
55
|
-
headers: expect.objectContaining({
|
|
56
|
-
Authorization: `QB-USER-TOKEN ${userToken}`,
|
|
57
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
58
|
-
"Content-Type": "application/json",
|
|
59
|
-
}),
|
|
60
|
-
})
|
|
61
|
-
);
|
|
62
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
63
|
-
`Retrying getFields with existing user token: b9f3pk_q4j...`
|
|
64
|
-
);
|
|
65
|
-
expect(consoleSpy).toHaveBeenCalledWith(
|
|
66
|
-
"[getFields] Retrying with token: b9f3pk_q4j..."
|
|
67
|
-
);
|
|
68
|
-
});
|
|
69
|
-
});
|