quickbase-js 0.1.0-beta.10

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.
Files changed (165) hide show
  1. package/CHANGELOG.md +211 -0
  2. package/LICENSE +21 -0
  3. package/README.md +179 -0
  4. package/api-examples.md +49 -0
  5. package/build-common.js +131 -0
  6. package/build-esm.js +71 -0
  7. package/build-umd.js +69 -0
  8. package/dist/esm/quickbase.d.ts +8081 -0
  9. package/dist/esm/quickbase.js +7928 -0
  10. package/dist/esm/quickbase.js.map +1 -0
  11. package/dist/esm/quickbase.min.js +2 -0
  12. package/dist/esm/quickbase.min.js.map +1 -0
  13. package/dist/umd/quickbase.umd.js +8504 -0
  14. package/dist/umd/quickbase.umd.js.map +1 -0
  15. package/dist/umd/quickbase.umd.min.js +2 -0
  16. package/dist/umd/quickbase.umd.min.js.map +1 -0
  17. package/docs/.vitepress/cache/deps/@theme_index.js +275 -0
  18. package/docs/.vitepress/cache/deps/@theme_index.js.map +7 -0
  19. package/docs/.vitepress/cache/deps/_metadata.json +40 -0
  20. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js +12542 -0
  21. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js.map +7 -0
  22. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js +9719 -0
  23. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js.map +7 -0
  24. package/docs/.vitepress/cache/deps/package.json +3 -0
  25. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4504 -0
  26. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  27. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
  28. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  29. package/docs/.vitepress/cache/deps/vue.js +343 -0
  30. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  31. package/docs/.vitepress/config.mts +26 -0
  32. package/docs/api-examples.md +49 -0
  33. package/docs/index.md +59 -0
  34. package/docs/markdown-examples.md +85 -0
  35. package/docs-data/api-docs.json +4143 -0
  36. package/examples/quicbase-jsCDN.html +36 -0
  37. package/examples/react.js +48 -0
  38. package/examples/vanillajs.html +47 -0
  39. package/index.md +25 -0
  40. package/markdown-examples.md +85 -0
  41. package/open-api/fix-spec-main.ts +133 -0
  42. package/open-api/generate-unified-interface.ts +104 -0
  43. package/open-api/generateDocsJson.ts +24 -0
  44. package/open-api/output/quickbase-fixed.json +11191 -0
  45. package/open-api/post-process-generated.ts +226 -0
  46. package/open-api/regenerate-client.ts +117 -0
  47. package/open-api/schema/enhance-general.ts +111 -0
  48. package/open-api/schema/fix-arrays.ts +68 -0
  49. package/open-api/schema/tags/apps.ts +90 -0
  50. package/open-api/schema/tags/fields.ts +471 -0
  51. package/open-api/schema/tags/groups.ts +51 -0
  52. package/open-api/schema/tags/index.ts +66 -0
  53. package/open-api/schema/tags/records.ts +115 -0
  54. package/open-api/schema/tags/tables.ts +211 -0
  55. package/open-api/schema/tags/users.ts +47 -0
  56. package/open-api/types/spec.ts +42 -0
  57. package/open-api/utils/common.ts +29 -0
  58. package/open-api/utils/generateJsDoc.ts +74 -0
  59. package/open-api/utils/infer-schema.ts +40 -0
  60. package/open-api/utils/naming.ts +10 -0
  61. package/open-api/utils/sharedUtils.ts +352 -0
  62. package/package.json +96 -0
  63. package/playwright.config.ts +37 -0
  64. package/rollup.config.js +114 -0
  65. package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
  66. package/specs/split/apps.json +1315 -0
  67. package/specs/split/auth.json +191 -0
  68. package/specs/split/fields.json +3869 -0
  69. package/specs/split/records.json +754 -0
  70. package/specs/split/reports.json +619 -0
  71. package/specs/split/tables.json +1605 -0
  72. package/src/RateLimitError.ts +12 -0
  73. package/src/ThrottleBucket.ts +41 -0
  74. package/src/authorizationStrategy.ts +283 -0
  75. package/src/generated-unified/QuickbaseClient.ts +1135 -0
  76. package/src/generated-unified/missing-types-report.json +3 -0
  77. package/src/index.ts +1 -0
  78. package/src/invokeMethod.ts +245 -0
  79. package/src/quickbaseClient.ts +194 -0
  80. package/src/rateLimiter.ts +25 -0
  81. package/src/tokenCache.ts +35 -0
  82. package/src/utils.ts +50 -0
  83. package/tests/playwright/qb/auth/cacheSwitching.test.ts +159 -0
  84. package/tests/playwright/qb/auth/fetchTempToken401.test.ts +219 -0
  85. package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +162 -0
  86. package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +153 -0
  87. package/tests/playwright/qb/auth/getTempToken.test.ts +152 -0
  88. package/tests/playwright/qb/auth/tempTokenPrefetch.test.ts +179 -0
  89. package/tests/playwright/qb/auth/tempTokenRenewal.test.ts +133 -0
  90. package/tests/setup.ts +53 -0
  91. package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
  92. package/tests/vitest/qb/apps/createApp.test.ts +64 -0
  93. package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
  94. package/tests/vitest/qb/apps/getApp.test.ts +44 -0
  95. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  96. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  97. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +60 -0
  98. package/tests/vitest/qb/auth/retryUserToken401.test.ts +73 -0
  99. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +65 -0
  100. package/tests/vitest/qb/fields/createField.test.ts +77 -0
  101. package/tests/vitest/qb/fields/deleteFields.test.ts +83 -0
  102. package/tests/vitest/qb/fields/getField.test.ts +85 -0
  103. package/tests/vitest/qb/fields/getFieldUsage.test.ts +82 -0
  104. package/tests/vitest/qb/fields/getFields.test.ts +232 -0
  105. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
  106. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  107. package/tests/vitest/qb/formulas/runFormula.test.ts +96 -0
  108. package/tests/vitest/qb/index.ts +29 -0
  109. package/tests/vitest/qb/records/deleteRecords.test.ts +110 -0
  110. package/tests/vitest/qb/records/runQuery.test.ts +146 -0
  111. package/tests/vitest/qb/records/upsert.test.ts +102 -0
  112. package/tests/vitest/qb/tables/createRelationship.test.ts +102 -0
  113. package/tests/vitest/qb/tables/createTable.test.ts +55 -0
  114. package/tests/vitest/qb/tables/deleteRelationship.test.ts +143 -0
  115. package/tests/vitest/qb/tables/deleteTable.test.ts +50 -0
  116. package/tests/vitest/qb/tables/getAppTables.test.ts +46 -0
  117. package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
  118. package/tests/vitest/qb/tables/getTable.test.ts +26 -0
  119. package/tests/vitest/qb/tables/updateRelationship.test.ts +250 -0
  120. package/tests/vitest/qb/tables/updateTable.test.ts +48 -0
  121. package/tests/vitest/unit/ThrottleBucket.test.ts +95 -0
  122. package/tests/vitest/unit/apps/copyApp.test.ts +581 -0
  123. package/tests/vitest/unit/apps/createApp.test.ts +186 -0
  124. package/tests/vitest/unit/apps/deleteApp.test.ts +216 -0
  125. package/tests/vitest/unit/apps/getApp.test.ts +128 -0
  126. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  127. package/tests/vitest/unit/apps/updateApp.test.ts +412 -0
  128. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +63 -0
  129. package/tests/vitest/unit/auth/getTempToken.test.ts +119 -0
  130. package/tests/vitest/unit/auth/retryTempToken401.test.ts +95 -0
  131. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +206 -0
  132. package/tests/vitest/unit/auth/retryUserToken401.test.ts +69 -0
  133. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +325 -0
  134. package/tests/vitest/unit/fields/createField.test.ts +451 -0
  135. package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
  136. package/tests/vitest/unit/fields/getField.test.ts +189 -0
  137. package/tests/vitest/unit/fields/getFieldUsage.test.ts +180 -0
  138. package/tests/vitest/unit/fields/getFields.test.ts +122 -0
  139. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
  140. package/tests/vitest/unit/fields/updateField.test.ts +285 -0
  141. package/tests/vitest/unit/formulas/runFormula.test.ts +205 -0
  142. package/tests/vitest/unit/index.ts +34 -0
  143. package/tests/vitest/unit/quickbaseClientHttpMethods.test.ts +139 -0
  144. package/tests/vitest/unit/rateLimit.test.ts +191 -0
  145. package/tests/vitest/unit/records/deleteRecords.test.ts +112 -0
  146. package/tests/vitest/unit/records/runQuery.test.ts +102 -0
  147. package/tests/vitest/unit/records/upsert.test.ts +234 -0
  148. package/tests/vitest/unit/tables/createRelationship.test.ts +212 -0
  149. package/tests/vitest/unit/tables/createTable.test.ts +70 -0
  150. package/tests/vitest/unit/tables/deleteRelationship.test.ts +97 -0
  151. package/tests/vitest/unit/tables/deleteTable.test.ts +77 -0
  152. package/tests/vitest/unit/tables/getAppTables.test.ts +85 -0
  153. package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
  154. package/tests/vitest/unit/tables/getTable.test.ts +233 -0
  155. package/tests/vitest/unit/tables/updateRelationship.test.ts +307 -0
  156. package/tests/vitest/unit/tables/updateTable.test.ts +80 -0
  157. package/tests/vitest/unit/tokenLifespan.test.ts +204 -0
  158. package/tests/vitest/unit/umd.test.ts +8 -0
  159. package/tools/log-gen.ts +251 -0
  160. package/tools/log-gen.yaml +4777 -0
  161. package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
  162. package/tsconfig.build.json +17 -0
  163. package/tsconfig.json +32 -0
  164. package/versionrc.json +9 -0
  165. package/vitest.config.ts +27 -0
@@ -0,0 +1,259 @@
1
+ // tests/vitest/unit/relationships/getRelationships.test.ts
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import {
4
+ createClient,
5
+ mockFetch,
6
+ QB_REALM,
7
+ QB_USER_TOKEN,
8
+ QB_TABLE_ID_1,
9
+ QB_TABLE_ID_2,
10
+ } from "@tests/setup.ts";
11
+ import { GetRelationships200Response } from "/home/drew/Projects/quickbase-js/src/generated/models";
12
+
13
+ describe("QuickbaseClient Unit - getRelationships", () => {
14
+ let client: ReturnType<typeof createClient>;
15
+
16
+ beforeEach(() => {
17
+ mockFetch.mockClear();
18
+ client = createClient(mockFetch, { debug: true }); // Consistent with output
19
+ });
20
+
21
+ it("initializes without errors", () => {
22
+ expect(client).toBeDefined();
23
+ });
24
+
25
+ it("has getRelationships method", () => {
26
+ expect(typeof client.getRelationships).toBe("function");
27
+ });
28
+
29
+ it("sends correct GET request and handles successful response", async () => {
30
+ const mockResponse: GetRelationships200Response = {
31
+ metadata: {
32
+ numRelationships: 1,
33
+ skip: 0,
34
+ totalRelationships: 1,
35
+ },
36
+ relationships: [
37
+ {
38
+ id: 6,
39
+ parentTableId: QB_TABLE_ID_1, // Parent table
40
+ childTableId: QB_TABLE_ID_2, // Child table
41
+ foreignKeyField: {
42
+ id: 6,
43
+ label: "Related record",
44
+ type: "numeric",
45
+ },
46
+ isCrossApp: false,
47
+ lookupFields: [
48
+ {
49
+ id: 3,
50
+ label: "Name",
51
+ type: "text",
52
+ },
53
+ ],
54
+ summaryFields: [
55
+ {
56
+ id: 8,
57
+ label: "my summary field",
58
+ type: "numeric",
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ };
64
+
65
+ mockFetch.mockResolvedValueOnce({
66
+ ok: true,
67
+ status: 200,
68
+ json: () => Promise.resolve(mockResponse),
69
+ });
70
+
71
+ const result = await client.getRelationships({
72
+ tableId: QB_TABLE_ID_2,
73
+ skip: 0,
74
+ });
75
+
76
+ expect(mockFetch).toHaveBeenCalledWith(
77
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
78
+ expect.objectContaining({
79
+ method: "GET",
80
+ headers: expect.objectContaining({
81
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
82
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
83
+ "Content-Type": "application/json",
84
+ }),
85
+ })
86
+ );
87
+
88
+ expect(result).toEqual(mockResponse);
89
+ expect(result.metadata.numRelationships).toBe(1);
90
+ expect(result.relationships).toHaveLength(1);
91
+ expect(result.relationships[0].id).toBe(6);
92
+ expect(result.relationships[0].foreignKeyField.label).toBe(
93
+ "Related record"
94
+ );
95
+ });
96
+
97
+ it("handles request without skip parameter", async () => {
98
+ const mockResponse: GetRelationships200Response = {
99
+ metadata: {
100
+ numRelationships: 1,
101
+ skip: 0,
102
+ totalRelationships: 1,
103
+ },
104
+ relationships: [
105
+ {
106
+ id: 6,
107
+ parentTableId: QB_TABLE_ID_1,
108
+ childTableId: QB_TABLE_ID_2,
109
+ foreignKeyField: {
110
+ id: 6,
111
+ label: "Related record",
112
+ type: "numeric",
113
+ },
114
+ isCrossApp: false,
115
+ lookupFields: [],
116
+ summaryFields: [],
117
+ },
118
+ ],
119
+ };
120
+
121
+ mockFetch.mockResolvedValueOnce({
122
+ ok: true,
123
+ status: 200,
124
+ json: () => Promise.resolve(mockResponse),
125
+ });
126
+
127
+ const result = await client.getRelationships({
128
+ tableId: QB_TABLE_ID_2, // Corrected from QB_TABLE_ID_1
129
+ });
130
+
131
+ expect(mockFetch).toHaveBeenCalledWith(
132
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships`,
133
+ expect.objectContaining({
134
+ method: "GET",
135
+ headers: expect.objectContaining({
136
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
137
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
138
+ "Content-Type": "application/json",
139
+ }),
140
+ })
141
+ );
142
+
143
+ expect(result).toEqual(mockResponse);
144
+ });
145
+
146
+ it("handles API error response", async () => {
147
+ mockFetch.mockResolvedValueOnce({
148
+ ok: false,
149
+ status: 404,
150
+ json: () => Promise.resolve({ message: "Table not found" }),
151
+ });
152
+
153
+ await expect(
154
+ client.getRelationships({
155
+ tableId: QB_TABLE_ID_2, // Corrected from QB_TABLE_ID_1
156
+ skip: 0,
157
+ })
158
+ ).rejects.toThrow("API Error: Table not found (Status: 404)");
159
+
160
+ expect(mockFetch).toHaveBeenCalledWith(
161
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
162
+ expect.objectContaining({
163
+ method: "GET",
164
+ headers: expect.objectContaining({
165
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
166
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
167
+ "Content-Type": "application/json",
168
+ }),
169
+ })
170
+ );
171
+ });
172
+
173
+ it("retries with temp token after 401", async () => {
174
+ const mockResponse: GetRelationships200Response = {
175
+ metadata: {
176
+ numRelationships: 1,
177
+ skip: 0,
178
+ totalRelationships: 1,
179
+ },
180
+ relationships: [
181
+ {
182
+ id: 6,
183
+ parentTableId: QB_TABLE_ID_1,
184
+ childTableId: QB_TABLE_ID_2,
185
+ foreignKeyField: {
186
+ id: 6,
187
+ label: "Related record",
188
+ type: "numeric",
189
+ },
190
+ isCrossApp: false,
191
+ },
192
+ ],
193
+ };
194
+
195
+ mockFetch
196
+ .mockResolvedValueOnce({
197
+ ok: true,
198
+ status: 200,
199
+ json: () =>
200
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
201
+ })
202
+ .mockResolvedValueOnce({
203
+ ok: false,
204
+ status: 401,
205
+ json: () => Promise.resolve({ message: "Unauthorized" }),
206
+ })
207
+ .mockResolvedValueOnce({
208
+ ok: true,
209
+ status: 200,
210
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
211
+ })
212
+ .mockResolvedValueOnce({
213
+ ok: true,
214
+ status: 200,
215
+ json: () => Promise.resolve(mockResponse),
216
+ });
217
+
218
+ const tempClient = createClient(mockFetch, {
219
+ useTempTokens: true,
220
+ debug: true,
221
+ });
222
+ const result = await tempClient.getRelationships({
223
+ tableId: QB_TABLE_ID_2,
224
+ skip: 0,
225
+ });
226
+
227
+ expect(mockFetch).toHaveBeenCalledTimes(4);
228
+ expect(mockFetch).toHaveBeenNthCalledWith(
229
+ 1,
230
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_2}`,
231
+ expect.any(Object)
232
+ );
233
+ expect(mockFetch).toHaveBeenNthCalledWith(
234
+ 2,
235
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
236
+ expect.objectContaining({
237
+ headers: expect.objectContaining({
238
+ Authorization: "QB-TEMP-TOKEN initial_token",
239
+ }),
240
+ })
241
+ );
242
+ expect(mockFetch).toHaveBeenNthCalledWith(
243
+ 3,
244
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_2}`,
245
+ expect.any(Object)
246
+ );
247
+ expect(mockFetch).toHaveBeenNthCalledWith(
248
+ 4,
249
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
250
+ expect.objectContaining({
251
+ headers: expect.objectContaining({
252
+ Authorization: "QB-TEMP-TOKEN new_token",
253
+ }),
254
+ })
255
+ );
256
+
257
+ expect(result).toEqual(mockResponse);
258
+ });
259
+ });
@@ -0,0 +1,233 @@
1
+ // tests/vitest/unit/tables/getTable.test.ts
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import {
4
+ createClient,
5
+ mockFetch,
6
+ QB_APP_ID,
7
+ QB_TABLE_ID_1,
8
+ QB_REALM,
9
+ QB_USER_TOKEN,
10
+ } from "@tests/setup.ts";
11
+
12
+ describe("QuickbaseClient Unit - getTable", () => {
13
+ let client: ReturnType<typeof createClient>;
14
+
15
+ beforeEach(() => {
16
+ mockFetch.mockClear();
17
+ });
18
+
19
+ it("calls getTable successfully with user token", async () => {
20
+ client = createClient(mockFetch, { debug: true });
21
+
22
+ const mockResponse = {
23
+ id: QB_TABLE_ID_1,
24
+ name: "Root",
25
+ alias: "_DBID_ROOT",
26
+ description: "",
27
+ created: new Date("2025-02-13T18:22:33.000Z"),
28
+ updated: new Date("2025-02-13T18:22:34.000Z"),
29
+ nextRecordId: 1,
30
+ nextFieldId: 6,
31
+ defaultSortFieldId: 2,
32
+ defaultSortOrder: "DESC",
33
+ keyFieldId: 3,
34
+ singleRecordName: "Root",
35
+ pluralRecordName: "Roots",
36
+ sizeLimit: "500 MB",
37
+ spaceUsed: "0 KB",
38
+ spaceRemaining: "500 MB",
39
+ };
40
+
41
+ mockFetch.mockResolvedValueOnce({
42
+ ok: true,
43
+ status: 200,
44
+ json: () => Promise.resolve(mockResponse),
45
+ });
46
+
47
+ const response = await client.getTable({
48
+ tableId: QB_TABLE_ID_1,
49
+ appId: QB_APP_ID,
50
+ });
51
+ expect(response).toEqual(mockResponse);
52
+ expect(mockFetch).toHaveBeenCalledWith(
53
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
54
+ expect.objectContaining({
55
+ method: "GET",
56
+ headers: expect.objectContaining({
57
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
58
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
59
+ }),
60
+ })
61
+ );
62
+ });
63
+
64
+ it("calls getTable successfully with temp token", async () => {
65
+ client = createClient(mockFetch, { debug: true, useTempTokens: true });
66
+
67
+ const mockResponse = {
68
+ id: QB_TABLE_ID_1,
69
+ name: "Root",
70
+ alias: "_DBID_ROOT",
71
+ description: "",
72
+ created: new Date("2025-02-13T18:22:33.000Z"),
73
+ updated: new Date("2025-02-13T18:22:34.000Z"),
74
+ nextRecordId: 1,
75
+ nextFieldId: 6,
76
+ defaultSortFieldId: 2,
77
+ defaultSortOrder: "DESC",
78
+ keyFieldId: 3,
79
+ singleRecordName: "Root",
80
+ pluralRecordName: "Roots",
81
+ sizeLimit: "500 MB",
82
+ spaceUsed: "0 KB",
83
+ spaceRemaining: "500 MB",
84
+ };
85
+
86
+ mockFetch
87
+ .mockResolvedValueOnce({
88
+ ok: true,
89
+ status: 200,
90
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
91
+ })
92
+ .mockResolvedValueOnce({
93
+ ok: true,
94
+ status: 200,
95
+ json: () => Promise.resolve(mockResponse),
96
+ });
97
+
98
+ const response = await client.getTable({
99
+ tableId: QB_TABLE_ID_1,
100
+ appId: QB_APP_ID,
101
+ });
102
+ expect(response).toEqual(mockResponse);
103
+ expect(mockFetch).toHaveBeenCalledTimes(2);
104
+ expect(mockFetch).toHaveBeenCalledWith(
105
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
106
+ expect.any(Object)
107
+ );
108
+ expect(mockFetch).toHaveBeenCalledWith(
109
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
110
+ expect.objectContaining({
111
+ headers: expect.objectContaining({
112
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
113
+ Authorization: "QB-TEMP-TOKEN temp_token",
114
+ }),
115
+ })
116
+ );
117
+ });
118
+
119
+ it("retries successfully after 401 with temp token", async () => {
120
+ client = createClient(mockFetch, { debug: true, useTempTokens: true });
121
+
122
+ const mockResponse = {
123
+ id: QB_TABLE_ID_1,
124
+ name: "Root",
125
+ alias: "_DBID_ROOT",
126
+ description: "",
127
+ created: new Date("2025-02-13T18:22:33.000Z"),
128
+ updated: new Date("2025-02-13T18:22:34.000Z"),
129
+ nextRecordId: 1,
130
+ nextFieldId: 6,
131
+ defaultSortFieldId: 2,
132
+ defaultSortOrder: "DESC",
133
+ keyFieldId: 3,
134
+ singleRecordName: "Root",
135
+ pluralRecordName: "Roots",
136
+ sizeLimit: "500 MB",
137
+ spaceUsed: "0 KB",
138
+ spaceRemaining: "500 MB",
139
+ };
140
+
141
+ mockFetch
142
+ .mockResolvedValueOnce({
143
+ ok: true,
144
+ status: 200,
145
+ json: () =>
146
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
147
+ })
148
+ .mockResolvedValueOnce({
149
+ ok: false,
150
+ status: 401,
151
+ json: () => Promise.resolve({ message: "Unauthorized" }),
152
+ })
153
+ .mockResolvedValueOnce({
154
+ ok: true,
155
+ status: 200,
156
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
157
+ })
158
+ .mockResolvedValueOnce({
159
+ ok: true,
160
+ status: 200,
161
+ json: () => Promise.resolve(mockResponse),
162
+ });
163
+
164
+ const response = await client.getTable({
165
+ tableId: QB_TABLE_ID_1,
166
+ appId: QB_APP_ID,
167
+ });
168
+ expect(response).toEqual(mockResponse);
169
+ expect(mockFetch).toHaveBeenCalledTimes(4);
170
+ expect(mockFetch).toHaveBeenCalledWith(
171
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
172
+ expect.objectContaining({
173
+ headers: expect.objectContaining({
174
+ Authorization: "QB-TEMP-TOKEN new_token",
175
+ }),
176
+ })
177
+ );
178
+ });
179
+
180
+ it("handles 401 with failed temp token retry", async () => {
181
+ client = createClient(mockFetch, { debug: true, useTempTokens: true });
182
+
183
+ mockFetch
184
+ .mockResolvedValueOnce({
185
+ ok: true,
186
+ status: 200,
187
+ json: () =>
188
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
189
+ })
190
+ .mockResolvedValueOnce({
191
+ ok: false,
192
+ status: 401,
193
+ json: () => Promise.resolve({ message: "Unauthorized" }),
194
+ })
195
+ .mockResolvedValueOnce({
196
+ ok: false,
197
+ status: 401,
198
+ json: () =>
199
+ Promise.resolve({ message: "Unauthorized in fetchTempToken" }),
200
+ });
201
+
202
+ await expect(
203
+ client.getTable({ tableId: QB_TABLE_ID_1, appId: QB_APP_ID })
204
+ ).rejects.toThrow(
205
+ "API Error: Unauthorized in fetchTempToken (Status: 401)"
206
+ );
207
+ expect(mockFetch).toHaveBeenCalledTimes(3);
208
+ });
209
+
210
+ it("handles 404 Not Found", async () => {
211
+ client = createClient(mockFetch, { debug: true });
212
+
213
+ mockFetch.mockResolvedValueOnce({
214
+ ok: false,
215
+ status: 404,
216
+ json: () => Promise.resolve({ message: "Table not found" }),
217
+ });
218
+
219
+ await expect(
220
+ client.getTable({ tableId: QB_TABLE_ID_1, appId: QB_APP_ID })
221
+ ).rejects.toThrow("API Error: Table not found (Status: 404)");
222
+ expect(mockFetch).toHaveBeenCalledWith(
223
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
224
+ expect.objectContaining({
225
+ method: "GET",
226
+ headers: expect.objectContaining({
227
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
228
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
229
+ }),
230
+ })
231
+ );
232
+ });
233
+ });