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,451 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import {
3
+ createClient,
4
+ mockFetch,
5
+ QB_TABLE_ID_1,
6
+ QB_REALM,
7
+ QB_USER_TOKEN,
8
+ } from "@tests/setup.ts";
9
+ import { CreateFieldRequest } from "@/generated/models";
10
+
11
+ describe("QuickbaseClient Unit - createField", () => {
12
+ let client: ReturnType<typeof createClient>;
13
+
14
+ beforeEach(() => {
15
+ mockFetch.mockClear();
16
+ });
17
+
18
+ it("initializes without errors and has createField method", () => {
19
+ client = createClient(mockFetch, { debug: true });
20
+ expect(client).toBeDefined();
21
+ expect(typeof client.createField).toBe("function");
22
+ });
23
+
24
+ it("creates field successfully with user token", async () => {
25
+ client = createClient(mockFetch, { debug: true });
26
+
27
+ const request: CreateFieldRequest = {
28
+ label: "NewField",
29
+ fieldType: "text",
30
+ };
31
+
32
+ const mockResponse = {
33
+ id: 101,
34
+ label: "NewField",
35
+ fieldType: "text",
36
+ appearsByDefault: true,
37
+ audited: false,
38
+ bold: false,
39
+ doesDataCopy: false,
40
+ fieldHelp: "",
41
+ findEnabled: true,
42
+ mode: "",
43
+ noWrap: false,
44
+ permissions: [],
45
+ properties: {
46
+ allowHTML: false,
47
+ allowMentions: false,
48
+ allowNewChoices: false,
49
+ appendOnly: false,
50
+ carryChoices: true,
51
+ defaultValue: "",
52
+ foreignKey: false,
53
+ formula: "",
54
+ maxLength: 0,
55
+ numLines: 1,
56
+ primaryKey: false,
57
+ sortAsGiven: false,
58
+ },
59
+ required: false,
60
+ unique: false,
61
+ };
62
+
63
+ mockFetch.mockResolvedValueOnce({
64
+ ok: true,
65
+ status: 200,
66
+ json: () => Promise.resolve(mockResponse),
67
+ });
68
+
69
+ const result = await client.createField({
70
+ tableId: QB_TABLE_ID_1,
71
+ body: request,
72
+ });
73
+
74
+ expect(result).toEqual(mockResponse);
75
+ expect(mockFetch).toHaveBeenCalledWith(
76
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
77
+ {
78
+ method: "POST",
79
+ body: JSON.stringify(request),
80
+ headers: {
81
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
82
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
83
+ "Content-Type": "application/json",
84
+ },
85
+ credentials: "omit",
86
+ }
87
+ );
88
+ });
89
+
90
+ it("creates field successfully with temp token", async () => {
91
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
92
+
93
+ const request: CreateFieldRequest = {
94
+ label: "TempField",
95
+ fieldType: "numeric",
96
+ addToForms: false,
97
+ };
98
+
99
+ const mockResponse = {
100
+ id: 102,
101
+ label: "TempField",
102
+ fieldType: "numeric",
103
+ appearsByDefault: false,
104
+ audited: false,
105
+ bold: false,
106
+ doesDataCopy: false,
107
+ fieldHelp: "",
108
+ findEnabled: true,
109
+ mode: "",
110
+ noWrap: false,
111
+ permissions: [],
112
+ properties: {
113
+ allowHTML: false,
114
+ allowMentions: false,
115
+ allowNewChoices: false,
116
+ appendOnly: false,
117
+ carryChoices: true,
118
+ defaultValue: "",
119
+ foreignKey: false,
120
+ formula: "",
121
+ maxLength: 0,
122
+ numLines: 1,
123
+ primaryKey: false,
124
+ sortAsGiven: false,
125
+ },
126
+ required: false,
127
+ unique: false,
128
+ };
129
+
130
+ mockFetch
131
+ .mockResolvedValueOnce({
132
+ ok: true,
133
+ status: 200,
134
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
135
+ })
136
+ .mockResolvedValueOnce({
137
+ ok: true,
138
+ status: 200,
139
+ json: () => Promise.resolve(mockResponse),
140
+ });
141
+
142
+ const result = await client.createField({
143
+ tableId: QB_TABLE_ID_1,
144
+ body: request,
145
+ });
146
+
147
+ expect(result).toEqual(mockResponse);
148
+ expect(mockFetch).toHaveBeenCalledTimes(2);
149
+ expect(mockFetch).toHaveBeenNthCalledWith(
150
+ 1,
151
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
152
+ expect.any(Object)
153
+ );
154
+ expect(mockFetch).toHaveBeenNthCalledWith(
155
+ 2,
156
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
157
+ {
158
+ method: "POST",
159
+ body: JSON.stringify(request),
160
+ headers: {
161
+ Authorization: "QB-TEMP-TOKEN temp_token",
162
+ "Content-Type": "application/json",
163
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
164
+ },
165
+ credentials: "omit",
166
+ }
167
+ );
168
+ });
169
+
170
+ it("retries successfully after 401 with temp token", async () => {
171
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
172
+
173
+ const request: CreateFieldRequest = {
174
+ label: "RetryField",
175
+ fieldType: "checkbox",
176
+ };
177
+
178
+ const mockResponse = {
179
+ id: 102,
180
+ label: "RetryField",
181
+ fieldType: "checkbox",
182
+ appearsByDefault: true,
183
+ audited: false,
184
+ bold: false,
185
+ doesDataCopy: false,
186
+ fieldHelp: "",
187
+ findEnabled: true,
188
+ mode: "",
189
+ noWrap: false,
190
+ permissions: [],
191
+ properties: {
192
+ allowHTML: false,
193
+ allowMentions: false,
194
+ allowNewChoices: false,
195
+ appendOnly: false,
196
+ carryChoices: true,
197
+ defaultValue: "",
198
+ foreignKey: false,
199
+ formula: "",
200
+ maxLength: 0,
201
+ numLines: 1,
202
+ primaryKey: false,
203
+ sortAsGiven: false,
204
+ },
205
+ required: false,
206
+ unique: false,
207
+ };
208
+
209
+ mockFetch
210
+ .mockResolvedValueOnce({
211
+ ok: true,
212
+ status: 200,
213
+ json: () =>
214
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
215
+ })
216
+ .mockResolvedValueOnce({
217
+ ok: false,
218
+ status: 401,
219
+ json: () => Promise.resolve({ message: "Unauthorized" }),
220
+ text: () => Promise.resolve("Unauthorized"),
221
+ })
222
+ .mockResolvedValueOnce({
223
+ ok: true,
224
+ status: 200,
225
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
226
+ })
227
+ .mockResolvedValueOnce({
228
+ ok: true,
229
+ status: 200,
230
+ json: () => Promise.resolve(mockResponse),
231
+ });
232
+
233
+ const consoleSpy = vi.spyOn(console, "log");
234
+ const result = await client.createField({
235
+ tableId: QB_TABLE_ID_1,
236
+ body: request,
237
+ });
238
+
239
+ expect(result).toEqual(mockResponse);
240
+ expect(mockFetch).toHaveBeenCalledTimes(4);
241
+ expect(mockFetch).toHaveBeenNthCalledWith(
242
+ 1,
243
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
244
+ expect.any(Object)
245
+ );
246
+ expect(mockFetch).toHaveBeenNthCalledWith(
247
+ 2,
248
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
249
+ {
250
+ method: "POST",
251
+ body: JSON.stringify(request),
252
+ headers: {
253
+ Authorization: "QB-TEMP-TOKEN initial_token",
254
+ "Content-Type": "application/json",
255
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
256
+ },
257
+ credentials: "omit",
258
+ }
259
+ );
260
+ expect(mockFetch).toHaveBeenNthCalledWith(
261
+ 3,
262
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
263
+ expect.any(Object)
264
+ );
265
+ expect(mockFetch).toHaveBeenNthCalledWith(
266
+ 4,
267
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
268
+ {
269
+ method: "POST",
270
+ body: JSON.stringify(request),
271
+ headers: {
272
+ Authorization: "QB-TEMP-TOKEN new_token",
273
+ "Content-Type": "application/json",
274
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
275
+ },
276
+ credentials: "omit",
277
+ }
278
+ );
279
+ expect(consoleSpy).toHaveBeenCalledWith(
280
+ "Authorization error for createField (temp token), refreshing token:"
281
+ );
282
+ expect(consoleSpy).toHaveBeenCalledWith(
283
+ "[createField] Retrying with token: new_token..."
284
+ );
285
+ consoleSpy.mockRestore();
286
+ });
287
+
288
+ it("handles 400 error for invalid request", async () => {
289
+ client = createClient(mockFetch, { debug: true });
290
+
291
+ const request: CreateFieldRequest = {
292
+ label: "", // Invalid: empty label
293
+ fieldType: "text",
294
+ };
295
+
296
+ mockFetch.mockResolvedValueOnce({
297
+ ok: false,
298
+ status: 400,
299
+ json: () => Promise.resolve({ message: "Label is required" }),
300
+ text: () => Promise.resolve("Label is required"),
301
+ });
302
+
303
+ await expect(
304
+ client.createField({ tableId: QB_TABLE_ID_1, body: request })
305
+ ).rejects.toThrow("API Error: Label is required (Status: 400)");
306
+
307
+ expect(mockFetch).toHaveBeenCalledWith(
308
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
309
+ {
310
+ method: "POST",
311
+ body: JSON.stringify(request),
312
+ headers: {
313
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
314
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
315
+ "Content-Type": "application/json",
316
+ },
317
+ credentials: "omit",
318
+ }
319
+ );
320
+ });
321
+
322
+ it("handles 401 with failed temp token retry", async () => {
323
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
324
+
325
+ const request: CreateFieldRequest = {
326
+ label: "FailField",
327
+ fieldType: "text",
328
+ };
329
+
330
+ mockFetch
331
+ .mockResolvedValueOnce({
332
+ ok: true,
333
+ status: 200,
334
+ json: () =>
335
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
336
+ })
337
+ .mockResolvedValueOnce({
338
+ ok: false,
339
+ status: 401,
340
+ json: () => Promise.resolve({ message: "Unauthorized" }),
341
+ text: () => Promise.resolve("Unauthorized"),
342
+ })
343
+ .mockResolvedValueOnce({
344
+ ok: true,
345
+ status: 200,
346
+ json: () => Promise.resolve({ temporaryAuthorization: "token_2" }),
347
+ })
348
+ .mockResolvedValueOnce({
349
+ ok: false,
350
+ status: 401,
351
+ json: () => Promise.resolve({ message: "Unauthorized again" }),
352
+ text: () => Promise.resolve("Unauthorized again"),
353
+ })
354
+ .mockResolvedValueOnce({
355
+ ok: true,
356
+ status: 200,
357
+ json: () => Promise.resolve({ temporaryAuthorization: "token_3" }),
358
+ })
359
+ .mockResolvedValueOnce({
360
+ ok: false,
361
+ status: 401,
362
+ json: () => Promise.resolve({ message: "Unauthorized again" }),
363
+ text: () => Promise.resolve("Unauthorized again"),
364
+ })
365
+ .mockResolvedValueOnce({
366
+ ok: true,
367
+ status: 200,
368
+ json: () => Promise.resolve({ temporaryAuthorization: "token_4" }),
369
+ })
370
+ .mockResolvedValueOnce({
371
+ ok: false,
372
+ status: 401,
373
+ json: () => Promise.resolve({ message: "Unauthorized again" }),
374
+ text: () => Promise.resolve("Unauthorized again"),
375
+ });
376
+
377
+ const consoleSpy = vi.spyOn(console, "log");
378
+
379
+ await expect(
380
+ client.createField({ tableId: QB_TABLE_ID_1, body: request })
381
+ ).rejects.toThrow("API Error: Unauthorized again (Status: 401)");
382
+
383
+ expect(mockFetch).toHaveBeenCalledTimes(8);
384
+ expect(mockFetch).toHaveBeenNthCalledWith(
385
+ 1,
386
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
387
+ expect.any(Object)
388
+ );
389
+ expect(mockFetch).toHaveBeenNthCalledWith(
390
+ 2,
391
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
392
+ expect.objectContaining({
393
+ method: "POST",
394
+ headers: expect.objectContaining({
395
+ Authorization: "QB-TEMP-TOKEN initial_token",
396
+ }),
397
+ })
398
+ );
399
+ expect(mockFetch).toHaveBeenNthCalledWith(
400
+ 3,
401
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
402
+ expect.any(Object)
403
+ );
404
+ expect(mockFetch).toHaveBeenNthCalledWith(
405
+ 4,
406
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
407
+ expect.objectContaining({
408
+ method: "POST",
409
+ headers: expect.objectContaining({
410
+ Authorization: "QB-TEMP-TOKEN token_2",
411
+ }),
412
+ })
413
+ );
414
+ expect(mockFetch).toHaveBeenNthCalledWith(
415
+ 5,
416
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
417
+ expect.any(Object)
418
+ );
419
+ expect(mockFetch).toHaveBeenNthCalledWith(
420
+ 6,
421
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
422
+ expect.objectContaining({
423
+ method: "POST",
424
+ headers: expect.objectContaining({
425
+ Authorization: "QB-TEMP-TOKEN token_3",
426
+ }),
427
+ })
428
+ );
429
+ expect(mockFetch).toHaveBeenNthCalledWith(
430
+ 7,
431
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
432
+ expect.any(Object)
433
+ );
434
+ expect(mockFetch).toHaveBeenNthCalledWith(
435
+ 8,
436
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
437
+ expect.objectContaining({
438
+ method: "POST",
439
+ headers: expect.objectContaining({
440
+ Authorization: "QB-TEMP-TOKEN token_4",
441
+ }),
442
+ })
443
+ );
444
+
445
+ expect(consoleSpy).toHaveBeenCalledWith(
446
+ "Authorization error for createField (temp token), refreshing token:"
447
+ );
448
+
449
+ consoleSpy.mockRestore();
450
+ });
451
+ });
@@ -0,0 +1,141 @@
1
+ // tests/vitest/unit/fields/deleteFields.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
+ } from "@tests/setup.ts";
10
+ import {
11
+ DeleteFieldsRequest,
12
+ DeleteFields200Response,
13
+ } from "@/generated/models";
14
+
15
+ describe("QuickbaseClient Unit - deleteFields", () => {
16
+ let client: ReturnType<typeof createClient>;
17
+
18
+ beforeEach(() => {
19
+ mockFetch.mockClear();
20
+ });
21
+
22
+ it("initializes without errors and has deleteFields method", () => {
23
+ client = createClient(mockFetch, { debug: true });
24
+ expect(client).toBeDefined();
25
+ expect(typeof client.deleteFields).toBe("function");
26
+ });
27
+
28
+ it("deletes fields successfully with user token", async () => {
29
+ client = createClient(mockFetch, { debug: true });
30
+
31
+ const request: DeleteFieldsRequest = {
32
+ fieldIds: [6, 7, 8],
33
+ };
34
+
35
+ const mockResponse: DeleteFields200Response = {
36
+ deletedFieldIds: [6, 7, 8],
37
+ errors: [],
38
+ };
39
+
40
+ mockFetch.mockResolvedValueOnce({
41
+ ok: true,
42
+ status: 200,
43
+ json: () => Promise.resolve(mockResponse),
44
+ });
45
+
46
+ const result = await client.deleteFields({
47
+ tableId: QB_TABLE_ID_1,
48
+ body: request,
49
+ });
50
+
51
+ expect(result).toEqual(mockResponse);
52
+ expect(mockFetch).toHaveBeenCalledWith(
53
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
54
+ expect.objectContaining({
55
+ method: "DELETE",
56
+ headers: expect.objectContaining({
57
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
58
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
59
+ "Content-Type": "application/json",
60
+ }),
61
+ body: JSON.stringify(request),
62
+ credentials: "omit",
63
+ })
64
+ );
65
+ });
66
+
67
+ it("handles partial success with errors", async () => {
68
+ client = createClient(mockFetch, { debug: true });
69
+
70
+ const request: DeleteFieldsRequest = {
71
+ fieldIds: [6, 7, 8],
72
+ };
73
+
74
+ const mockResponse: DeleteFields200Response = {
75
+ deletedFieldIds: [6, 8],
76
+ errors: ["Error found with fid: 7"],
77
+ };
78
+
79
+ mockFetch.mockResolvedValueOnce({
80
+ ok: true,
81
+ status: 200,
82
+ json: () => Promise.resolve(mockResponse),
83
+ });
84
+
85
+ const result = await client.deleteFields({
86
+ tableId: QB_TABLE_ID_1,
87
+ body: request,
88
+ });
89
+
90
+ expect(result).toEqual(mockResponse);
91
+ expect(mockFetch).toHaveBeenCalledWith(
92
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
93
+ expect.objectContaining({
94
+ method: "DELETE",
95
+ headers: expect.objectContaining({
96
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
97
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
98
+ "Content-Type": "application/json",
99
+ }),
100
+ body: JSON.stringify(request),
101
+ credentials: "omit",
102
+ })
103
+ );
104
+ });
105
+
106
+ it("handles 400 error for invalid request", async () => {
107
+ client = createClient(mockFetch, { debug: true });
108
+
109
+ const request: DeleteFieldsRequest = {
110
+ fieldIds: [], // Invalid: empty fieldIds array
111
+ };
112
+
113
+ mockFetch.mockResolvedValueOnce({
114
+ ok: false,
115
+ status: 400,
116
+ json: () =>
117
+ Promise.resolve({
118
+ message: "Invalid input",
119
+ description: "Field IDs list cannot be empty.",
120
+ }),
121
+ });
122
+
123
+ await expect(
124
+ client.deleteFields({ tableId: QB_TABLE_ID_1, body: request })
125
+ ).rejects.toThrow("API Error: Invalid input (Status: 400)");
126
+
127
+ expect(mockFetch).toHaveBeenCalledWith(
128
+ `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
129
+ expect.objectContaining({
130
+ method: "DELETE",
131
+ headers: expect.objectContaining({
132
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
133
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
134
+ "Content-Type": "application/json",
135
+ }),
136
+ body: JSON.stringify(request),
137
+ credentials: "omit",
138
+ })
139
+ );
140
+ });
141
+ });