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,285 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import {
3
+ createClient,
4
+ mockFetch,
5
+ QB_APP_ID,
6
+ QB_REALM,
7
+ QB_USER_TOKEN,
8
+ QB_TABLE_ID_1,
9
+ } from "@tests/setup.ts";
10
+ import { UpdateFieldRequest, UpdateField200Response } from "@/generated/models";
11
+
12
+ describe("QuickbaseClient Unit - updateField", () => {
13
+ let client: ReturnType<typeof createClient>;
14
+
15
+ beforeEach(() => {
16
+ mockFetch.mockClear();
17
+ });
18
+
19
+ it("initializes and has updateField method", () => {
20
+ client = createClient(mockFetch, { debug: true });
21
+ expect(client).toBeDefined();
22
+ expect(typeof client.updateField).toBe("function");
23
+ });
24
+
25
+ it("updates field successfully with user token", async () => {
26
+ client = createClient(mockFetch, {
27
+ realm: QB_REALM,
28
+ userToken: QB_USER_TOKEN,
29
+ debug: true,
30
+ });
31
+
32
+ const tableId = QB_TABLE_ID_1;
33
+ const fieldId = 123;
34
+
35
+ const request: UpdateFieldRequest = {
36
+ label: "Field1",
37
+ // fieldType omitted as it’s not allowed in updates
38
+ noWrap: false,
39
+ bold: false,
40
+ required: true,
41
+ appearsByDefault: false,
42
+ findEnabled: false,
43
+ unique: true,
44
+ fieldHelp: "field help",
45
+ addToForms: true,
46
+ properties: {
47
+ numLines: 1,
48
+ maxLength: 50,
49
+ appendOnly: true,
50
+ sortAsGiven: false,
51
+ allowMentions: false,
52
+ comments: "Updated field settings",
53
+ doesTotal: false,
54
+ defaultValue: "Initial text",
55
+ choices: ["Option1", "Option2"],
56
+ },
57
+ permissions: [
58
+ { role: "Viewer", permissionType: "View", roleId: 10 },
59
+ { role: "Participant", permissionType: "None", roleId: 11 },
60
+ { role: "Administrator", permissionType: "Modify", roleId: 12 },
61
+ ],
62
+ };
63
+
64
+ const mockResponse: UpdateField200Response = {
65
+ id: fieldId,
66
+ label: "Field1",
67
+ fieldType: "text",
68
+ mode: "",
69
+ noWrap: false,
70
+ bold: false,
71
+ required: true,
72
+ appearsByDefault: false,
73
+ findEnabled: false,
74
+ unique: true,
75
+ doesDataCopy: false,
76
+ fieldHelp: "field help",
77
+ audited: false,
78
+ properties: {
79
+ numLines: 1,
80
+ maxLength: 50,
81
+ appendOnly: true,
82
+ sortAsGiven: false,
83
+ allowMentions: false,
84
+ comments: "Updated field settings",
85
+ doesTotal: false,
86
+ defaultValue: "Initial text",
87
+ choices: ["Option1", "Option2"],
88
+ allowHTML: false,
89
+ carryChoices: true,
90
+ allowNewChoices: false,
91
+ formula: "",
92
+ },
93
+ permissions: [
94
+ { permissionType: "View", role: "Viewer", roleId: 10 },
95
+ { permissionType: "None", role: "Participant", roleId: 11 },
96
+ { permissionType: "Modify", role: "Administrator", roleId: 12 },
97
+ ],
98
+ };
99
+
100
+ mockFetch.mockResolvedValueOnce({
101
+ ok: true,
102
+ status: 200,
103
+ json: () => Promise.resolve(mockResponse),
104
+ });
105
+
106
+ const result = await client.updateField({
107
+ fieldId,
108
+ tableId,
109
+ body: request,
110
+ });
111
+
112
+ expect(result).toEqual(mockResponse);
113
+ expect(mockFetch).toHaveBeenCalledWith(
114
+ `https://api.quickbase.com/v1/fields/${fieldId}?tableId=${tableId}`,
115
+ expect.objectContaining({
116
+ method: "POST",
117
+ headers: expect.objectContaining({
118
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
119
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
120
+ "Content-Type": "application/json",
121
+ }),
122
+ body: expect.any(String),
123
+ credentials: "omit",
124
+ })
125
+ );
126
+ const [_, callOptions] = mockFetch.mock.calls[0];
127
+ expect(JSON.parse(callOptions.body)).toEqual(request);
128
+ });
129
+
130
+ it("updates field successfully with temp token", async () => {
131
+ client = createClient(mockFetch, {
132
+ realm: QB_REALM,
133
+ useTempTokens: true,
134
+ debug: true,
135
+ });
136
+
137
+ const tableId = QB_TABLE_ID_1;
138
+ const fieldId = 123;
139
+
140
+ const request: UpdateFieldRequest = {
141
+ label: "Field1",
142
+ // fieldType omitted as it’s not allowed in updates
143
+ required: true,
144
+ unique: true,
145
+ properties: {
146
+ numLines: 1,
147
+ maxLength: 0,
148
+ choices: ["Yes", "No"],
149
+ allowNewChoices: true,
150
+ defaultValue: "No",
151
+ },
152
+ };
153
+
154
+ const mockResponse: UpdateField200Response = {
155
+ id: fieldId,
156
+ label: "Field1",
157
+ fieldType: "text-multiple-choice",
158
+ mode: "",
159
+ noWrap: false,
160
+ bold: false,
161
+ required: true,
162
+ appearsByDefault: false,
163
+ findEnabled: false,
164
+ unique: true,
165
+ doesDataCopy: false,
166
+ fieldHelp: "",
167
+ audited: false,
168
+ properties: {
169
+ numLines: 1,
170
+ maxLength: 0,
171
+ choices: ["Yes", "No"],
172
+ allowNewChoices: true,
173
+ defaultValue: "No",
174
+ appendOnly: false,
175
+ allowHTML: false,
176
+ allowMentions: false,
177
+ sortAsGiven: false,
178
+ carryChoices: true,
179
+ formula: "",
180
+ },
181
+ permissions: [
182
+ { permissionType: "View", role: "Viewer", roleId: 10 },
183
+ { permissionType: "None", role: "Participant", roleId: 11 },
184
+ { permissionType: "Modify", role: "Administrator", roleId: 12 },
185
+ ],
186
+ };
187
+
188
+ mockFetch
189
+ .mockResolvedValueOnce({
190
+ ok: true,
191
+ status: 200,
192
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
193
+ })
194
+ .mockResolvedValueOnce({
195
+ ok: true,
196
+ status: 200,
197
+ json: () => Promise.resolve(mockResponse),
198
+ });
199
+
200
+ const result = await client.updateField({
201
+ fieldId,
202
+ tableId,
203
+ body: request,
204
+ });
205
+
206
+ expect(result).toEqual(mockResponse);
207
+ expect(mockFetch).toHaveBeenCalledTimes(2);
208
+ expect(mockFetch).toHaveBeenNthCalledWith(
209
+ 1,
210
+ `https://api.quickbase.com/v1/auth/temporary/${tableId}`,
211
+ expect.objectContaining({
212
+ method: "GET",
213
+ headers: expect.objectContaining({
214
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
215
+ "Content-Type": "application/json",
216
+ }),
217
+ credentials: "include",
218
+ })
219
+ );
220
+ expect(mockFetch).toHaveBeenNthCalledWith(
221
+ 2,
222
+ `https://api.quickbase.com/v1/fields/${fieldId}?tableId=${tableId}`,
223
+ expect.objectContaining({
224
+ method: "POST",
225
+ headers: expect.objectContaining({
226
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
227
+ Authorization: "QB-TEMP-TOKEN temp_token",
228
+ "Content-Type": "application/json",
229
+ }),
230
+ body: expect.any(String),
231
+ credentials: "omit",
232
+ })
233
+ );
234
+ const [_, callOptions] = mockFetch.mock.calls[1];
235
+ expect(JSON.parse(callOptions.body)).toEqual(request);
236
+ });
237
+
238
+ it("handles 400 error for invalid field update", async () => {
239
+ client = createClient(mockFetch, {
240
+ realm: QB_REALM,
241
+ userToken: QB_USER_TOKEN,
242
+ debug: true,
243
+ });
244
+
245
+ const tableId = QB_TABLE_ID_1;
246
+ const fieldId = 999;
247
+
248
+ const request: UpdateFieldRequest = {
249
+ label: "InvalidField",
250
+ // fieldType omitted as it’s not allowed in updates
251
+ required: true,
252
+ properties: {
253
+ numLines: 1,
254
+ decimalPlaces: 2,
255
+ currencySymbol: "$",
256
+ },
257
+ };
258
+
259
+ mockFetch.mockResolvedValueOnce({
260
+ ok: false,
261
+ status: 400,
262
+ json: () => Promise.resolve({ message: "Invalid field ID" }),
263
+ });
264
+
265
+ await expect(
266
+ client.updateField({ fieldId, tableId, body: request })
267
+ ).rejects.toThrow("API Error: Invalid field ID (Status: 400)");
268
+
269
+ expect(mockFetch).toHaveBeenCalledWith(
270
+ `https://api.quickbase.com/v1/fields/${fieldId}?tableId=${tableId}`,
271
+ expect.objectContaining({
272
+ method: "POST",
273
+ headers: expect.objectContaining({
274
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
275
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
276
+ "Content-Type": "application/json",
277
+ }),
278
+ body: expect.any(String),
279
+ credentials: "omit",
280
+ })
281
+ );
282
+ const [_, callOptions] = mockFetch.mock.calls[0];
283
+ expect(JSON.parse(callOptions.body)).toEqual(request);
284
+ });
285
+ });
@@ -0,0 +1,205 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import {
3
+ createClient,
4
+ mockFetch,
5
+ QB_REALM,
6
+ QB_USER_TOKEN,
7
+ QB_TABLE_ID_1,
8
+ } from "@tests/setup.ts";
9
+ import { RunFormulaRequest, RunFormula200Response } from "@/generated/models";
10
+
11
+ describe("QuickbaseClient Unit - runFormula", () => {
12
+ let client: ReturnType<typeof createClient>;
13
+
14
+ beforeEach(() => {
15
+ mockFetch.mockClear();
16
+ });
17
+
18
+ it("initializes without errors and has runFormula method", () => {
19
+ client = createClient(mockFetch, { debug: true });
20
+ expect(client).toBeDefined();
21
+ expect(typeof client.runFormula).toBe("function");
22
+ });
23
+
24
+ it("calls runFormula successfully with user token", async () => {
25
+ client = createClient(mockFetch, { debug: true });
26
+
27
+ const request: RunFormulaRequest = {
28
+ from: QB_TABLE_ID_1,
29
+ formula: "Sum([NumericField],20)",
30
+ rid: 1,
31
+ };
32
+
33
+ const mockResponse: RunFormula200Response = {
34
+ result: "30",
35
+ };
36
+
37
+ mockFetch.mockResolvedValueOnce({
38
+ ok: true,
39
+ status: 200,
40
+ json: () => Promise.resolve(mockResponse),
41
+ });
42
+
43
+ const result = await client.runFormula({ body: request });
44
+
45
+ expect(result).toEqual(mockResponse);
46
+ expect(mockFetch).toHaveBeenCalledWith(
47
+ "https://api.quickbase.com/v1/formula/run",
48
+ expect.objectContaining({
49
+ method: "POST",
50
+ headers: expect.objectContaining({
51
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
52
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
53
+ "Content-Type": "application/json",
54
+ }),
55
+ // Check body as parsed object to ignore order
56
+ body: expect.any(String), // First ensure it’s a string
57
+ })
58
+ );
59
+
60
+ // Additional check for body content
61
+ const calls = mockFetch.mock.calls;
62
+ expect(calls.length).toBe(1);
63
+ const fetchBody = calls[0][1].body as string;
64
+ expect(JSON.parse(fetchBody)).toEqual(request);
65
+
66
+ expect(result.result).toBe("30");
67
+ });
68
+
69
+ it("handles missing rid for formulas not requiring it", async () => {
70
+ client = createClient(mockFetch, { debug: true });
71
+
72
+ const request: RunFormulaRequest = {
73
+ from: QB_TABLE_ID_1,
74
+ formula: "User()",
75
+ // rid omitted intentionally
76
+ };
77
+
78
+ const mockResponse: RunFormula200Response = {
79
+ result: "user@example.com",
80
+ };
81
+
82
+ mockFetch.mockResolvedValueOnce({
83
+ ok: true,
84
+ status: 200,
85
+ json: () => Promise.resolve(mockResponse),
86
+ });
87
+
88
+ const result = await client.runFormula({ body: request });
89
+
90
+ expect(result).toEqual(mockResponse);
91
+ expect(mockFetch).toHaveBeenCalledWith(
92
+ "https://api.quickbase.com/v1/formula/run",
93
+ expect.objectContaining({
94
+ method: "POST",
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: expect.any(String),
101
+ })
102
+ );
103
+
104
+ const calls = mockFetch.mock.calls;
105
+ expect(calls.length).toBe(1);
106
+ const fetchBody = calls[0][1].body as string;
107
+ expect(JSON.parse(fetchBody)).toEqual(request);
108
+
109
+ expect(result.result).toBe("user@example.com");
110
+ });
111
+
112
+ it("handles API error for invalid formula", async () => {
113
+ client = createClient(mockFetch, { debug: true });
114
+
115
+ const request: RunFormulaRequest = {
116
+ from: QB_TABLE_ID_1,
117
+ formula: "InvalidFormula()",
118
+ rid: 1,
119
+ };
120
+
121
+ mockFetch.mockResolvedValueOnce({
122
+ ok: false,
123
+ status: 400,
124
+ json: () => Promise.resolve({ message: "Invalid formula syntax" }),
125
+ });
126
+
127
+ await expect(client.runFormula({ body: request })).rejects.toThrow(
128
+ "API Error: Invalid formula syntax (Status: 400)"
129
+ );
130
+
131
+ expect(mockFetch).toHaveBeenCalledWith(
132
+ "https://api.quickbase.com/v1/formula/run",
133
+ expect.objectContaining({
134
+ method: "POST",
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
+ body: expect.any(String),
141
+ })
142
+ );
143
+
144
+ const calls = mockFetch.mock.calls;
145
+ expect(calls.length).toBe(1);
146
+ const fetchBody = calls[0][1].body as string;
147
+ expect(JSON.parse(fetchBody)).toEqual(request);
148
+ });
149
+
150
+ it("handles temp token with retry on 401", async () => {
151
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
152
+
153
+ const request: RunFormulaRequest = {
154
+ from: QB_TABLE_ID_1,
155
+ formula: "Sum([NumericField],20)",
156
+ rid: 1,
157
+ };
158
+
159
+ const mockResponse: RunFormula200Response = {
160
+ result: "30",
161
+ };
162
+
163
+ mockFetch
164
+ .mockResolvedValueOnce({
165
+ ok: true,
166
+ status: 200,
167
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
168
+ })
169
+ .mockResolvedValueOnce({
170
+ ok: false,
171
+ status: 401,
172
+ json: () => Promise.resolve({ message: "Unauthorized" }),
173
+ })
174
+ .mockResolvedValueOnce({
175
+ ok: true,
176
+ status: 200,
177
+ json: () =>
178
+ Promise.resolve({ temporaryAuthorization: "new_temp_token" }),
179
+ })
180
+ .mockResolvedValueOnce({
181
+ ok: true,
182
+ status: 200,
183
+ json: () => Promise.resolve(mockResponse),
184
+ });
185
+
186
+ const result = await client.runFormula({ body: request });
187
+
188
+ expect(result).toEqual(mockResponse);
189
+ expect(mockFetch).toHaveBeenCalledTimes(4);
190
+ expect(mockFetch).toHaveBeenNthCalledWith(
191
+ 1,
192
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
193
+ expect.any(Object)
194
+ );
195
+ expect(mockFetch).toHaveBeenNthCalledWith(
196
+ 4,
197
+ "https://api.quickbase.com/v1/formula/run",
198
+ expect.objectContaining({
199
+ headers: expect.objectContaining({
200
+ Authorization: "QB-TEMP-TOKEN new_temp_token",
201
+ }),
202
+ })
203
+ );
204
+ });
205
+ });
@@ -0,0 +1,34 @@
1
+ // tests/vitest/unit/index.ts
2
+ // Apps
3
+ export * from "./apps/copyApp.test.ts";
4
+ export * from "./apps/createApp.test.ts";
5
+ export * from "./apps/deleteApp.test.ts";
6
+ export * from "./apps/getApp.test.ts";
7
+
8
+ // Auth
9
+ export * from "./auth/fetchTempToken401.test.ts";
10
+ export * from "./auth/getTempToken.test.ts";
11
+ export * from "./auth/retryOn401.test.ts";
12
+ export * from "./auth/retryTempTokenTwo401.test.ts";
13
+
14
+ // Fields
15
+ export * from "./fields/getFields.test.ts";
16
+ export * from "./fields/getField.test.ts";
17
+ export * from "./fields/createField.test.ts";
18
+ export * from "./fields/deleteFields.test.ts";
19
+ export * from "./fields/getFieldsUsage.test.ts";
20
+ export * from "./fields/getFieldUsage.test.ts";
21
+
22
+ // Records
23
+ export * from "./records/deleteRecords.test.ts";
24
+ export * from "./records/runQuery.test.ts";
25
+ export * from "./records/upsert.test.ts";
26
+
27
+ // Tables
28
+ export * from "./tables/createTable.test.ts";
29
+ export * from "./tables/deleteTable.test.ts";
30
+ export * from "./tables/getAppTables.test.ts";
31
+ export * from "./tables/getRelationships.test.ts";
32
+ export * from "./tables/getTable.test.ts";
33
+ export * from "./tables/updateTable.test.ts";
34
+ export * from "./tables/createRelationship.test.ts";
@@ -0,0 +1,139 @@
1
+ // tests/vitest/unit/quickbaseClient.test.ts
2
+
3
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
4
+ import { quickbase } from "../../../src/quickbaseClient";
5
+ import { mockFetch, QB_APP_ID, QB_REALM } from "../../setup.ts";
6
+ import {
7
+ UpdateAppRequest,
8
+ UpdateApp200Response,
9
+ } from "../../../src/generated/models";
10
+ import { ChangesetSolution200Response } from "../../../src/generated/models";
11
+
12
+ describe("QuickbaseClient Unit - HTTP Method Extraction", () => {
13
+ let clientTemp: ReturnType<typeof quickbase>;
14
+ let clientUser: ReturnType<typeof quickbase>;
15
+
16
+ beforeEach(() => {
17
+ mockFetch.mockClear();
18
+ vi.useFakeTimers();
19
+
20
+ clientTemp = quickbase({
21
+ realm: QB_REALM,
22
+ userToken: "b9f3pk_q4jd_0_b4qu5eebyvuix3xs57ysd7zn3",
23
+ debug: false,
24
+ fetchApi: mockFetch,
25
+ throttle: { rate: 10, burst: 10 },
26
+ useTempTokens: true,
27
+ tempTokenLifespan: 500,
28
+ });
29
+
30
+ clientUser = quickbase({
31
+ realm: QB_REALM,
32
+ userToken: "b9f3pk_q4jd_0_b4qu5eebyvuix3xs57ysd7zn3",
33
+ debug: false,
34
+ fetchApi: mockFetch,
35
+ throttle: { rate: 10, burst: 10 },
36
+ useTempTokens: false,
37
+ });
38
+ });
39
+
40
+ afterEach(() => {
41
+ vi.useRealTimers();
42
+ mockFetch.mockReset();
43
+ });
44
+
45
+ const mockUpdateAppResponse: UpdateApp200Response = {
46
+ id: QB_APP_ID,
47
+ name: "Test App",
48
+ description: "",
49
+ created: new Date("2020-01-01T00:00:00Z"),
50
+ updated: new Date("2020-01-01T00:00:00Z"),
51
+ dateFormat: "MM-DD-YYYY",
52
+ timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
53
+ hasEveryoneOnTheInternet: false,
54
+ ancestorId: undefined,
55
+ variables: [],
56
+ securityProperties: {},
57
+ memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
58
+ dataClassification: "None",
59
+ };
60
+
61
+ const mockChangesetResponse: ChangesetSolution200Response = [{ changes: [] }];
62
+
63
+ const setupMockFetch = () => {
64
+ mockFetch.mockReset();
65
+ mockFetch
66
+ .mockResolvedValueOnce({
67
+ ok: true,
68
+ status: 200,
69
+ json: () => Promise.resolve({ temporaryAuthorization: "token1" }),
70
+ })
71
+ .mockResolvedValueOnce({
72
+ ok: true,
73
+ status: 200,
74
+ json: () => Promise.resolve(mockUpdateAppResponse),
75
+ });
76
+ };
77
+
78
+ const setupMockFetchForChangeset = () => {
79
+ mockFetch.mockReset();
80
+ mockFetch.mockResolvedValueOnce({
81
+ ok: true,
82
+ status: 200,
83
+ json: () => Promise.resolve(mockChangesetResponse),
84
+ });
85
+ };
86
+
87
+ it("uses POST for updateApp", async () => {
88
+ setupMockFetch();
89
+ await clientTemp.updateApp({
90
+ appId: QB_APP_ID,
91
+ body: { name: "Test App" },
92
+ });
93
+ const apiCall = mockFetch.mock.calls.find((call) =>
94
+ call[0].includes("/apps/")
95
+ )![1];
96
+ expect(apiCall.method).toBe("POST");
97
+ });
98
+
99
+ it("uses POST for runReport", async () => {
100
+ setupMockFetch();
101
+ await clientTemp.runReport({
102
+ tableId: QB_APP_ID,
103
+ reportId: "report1",
104
+ body: { skip: 0, top: 10 },
105
+ });
106
+ const apiCall = mockFetch.mock.calls.find((call) =>
107
+ call[0].includes("/reports/")
108
+ )![1];
109
+ expect(apiCall.method).toBe("POST");
110
+ });
111
+
112
+ it("uses PUT for changesetSolution", async () => {
113
+ setupMockFetchForChangeset();
114
+ await clientUser.changesetSolution({
115
+ solutionId: "solution1",
116
+ body: "name: Test Solution\nversion: 1.0",
117
+ });
118
+ const apiCall = mockFetch.mock.calls[0][1];
119
+ expect(apiCall.method).toBe("PUT");
120
+ });
121
+
122
+ it("uses GET for getTable", async () => {
123
+ setupMockFetch();
124
+ await clientTemp.getTable({ appId: QB_APP_ID, tableId: "table1" });
125
+ const apiCall = mockFetch.mock.calls.find((call) =>
126
+ call[0].includes("/tables/")
127
+ )![1];
128
+ expect(apiCall.method).toBe("GET");
129
+ });
130
+
131
+ it("uses DELETE for deleteTable", async () => {
132
+ setupMockFetch();
133
+ await clientTemp.deleteTable({ appId: QB_APP_ID, tableId: "table1" });
134
+ const apiCall = mockFetch.mock.calls.find((call) =>
135
+ call[0].includes("/tables/")
136
+ )![1];
137
+ expect(apiCall.method).toBe("DELETE");
138
+ });
139
+ });