quickbase-js 0.1.0-beta.7 → 0.1.0-beta.8

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 (60) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/esm/quickbase.d.ts +589 -125
  3. package/dist/esm/quickbase.js +484 -145
  4. package/dist/esm/quickbase.js.map +1 -1
  5. package/dist/esm/quickbase.min.js +1 -1
  6. package/dist/esm/quickbase.min.js.map +1 -1
  7. package/dist/umd/quickbase.umd.js +492 -144
  8. package/dist/umd/quickbase.umd.js.map +1 -1
  9. package/dist/umd/quickbase.umd.min.js +1 -1
  10. package/dist/umd/quickbase.umd.min.js.map +1 -1
  11. package/open-api/fix-spec-main.ts +69 -68
  12. package/open-api/generate-override-report.ts +214 -0
  13. package/open-api/generate-unified-interface.ts +73 -94
  14. package/open-api/output/override-report.html +11 -0
  15. package/open-api/output/override-report.json +14395 -58
  16. package/open-api/output/quickbase-fixed.json +583 -2
  17. package/open-api/schema/tags/fields.ts +331 -16
  18. package/open-api/schema/tags/index.ts +2 -4
  19. package/open-api/types/spec.ts +2 -1
  20. package/package.json +31 -33
  21. package/src/RateLimitError.ts +12 -0
  22. package/src/ThrottleBucket.ts +36 -0
  23. package/src/generated-unified/QuickbaseClient.ts +119 -119
  24. package/src/invokeMethod.ts +357 -0
  25. package/src/quickbaseClient.ts +51 -209
  26. package/tests/setup.ts +2 -0
  27. package/tests/vitest/qb/apps/getApp.test.ts +6 -2
  28. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  29. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  30. package/tests/vitest/qb/auth/retryUserToken401.test.ts +56 -0
  31. package/tests/vitest/qb/fields/createField.test.ts +1 -2
  32. package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
  33. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  34. package/tests/vitest/qb/index.ts +1 -0
  35. package/tests/vitest/qb/tables/updateRelationship.test.ts +277 -0
  36. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  37. package/tests/vitest/unit/apps/updateApp.test.ts +381 -0
  38. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +6 -26
  39. package/tests/vitest/unit/auth/{retryOn401.test.ts → retryTempToken401.test.ts} +3 -6
  40. package/tests/vitest/unit/auth/{two401s.test.ts → retryTempTokenTwo401.test.ts} +4 -9
  41. package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -0
  42. package/tests/vitest/unit/fields/createField.test.ts +8 -12
  43. package/tests/vitest/unit/fields/getFieldUsage.test.ts +33 -14
  44. package/tests/vitest/unit/fields/updateField.test.ts +280 -0
  45. package/tests/vitest/unit/rateLimit.test.ts +189 -0
  46. package/tests/vitest/unit/tables/updateRelationship.test.ts +305 -0
  47. package/tests/vitest/unit/tokenLifespan.test.ts +138 -0
  48. package/tools/log-gen.ts +8 -4
  49. package/tools/log-gen.yaml +3004 -1848
  50. package/tsconfig.json +5 -2
  51. package/vitest.config.ts +2 -2
  52. package/notes.txt +0 -158
  53. package/tools/generate-types.ts +0 -29
  54. package/tools/openapi-schema-validator.ts +0 -38
  55. package/tools/spec-analyzer.ts +0 -163
  56. package/tools/split-spec.ts +0 -148
  57. package/tools/test-import.ts +0 -3
  58. package/tools/validate-spec.ts +0 -40
  59. /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
  60. /package/tests/playwright/qb/auth/{tokenRenewal.test.ts → tempTokenRenewal.test.ts} +0 -0
@@ -1,8 +1,7 @@
1
- // tests/vitest/unit/auth/two401s.test.ts
2
1
  import { describe, it, expect, beforeEach, vi } from "vitest";
3
2
  import { createClient, mockFetch } from "@tests/setup.ts";
4
3
 
5
- describe("QuickbaseClient - Two 401s in a Row", () => {
4
+ describe("QuickbaseClient - Two Temp Token 401s in a Row", () => {
6
5
  let client: ReturnType<typeof createClient>;
7
6
 
8
7
  beforeEach(() => {
@@ -28,7 +27,7 @@ describe("QuickbaseClient - Two 401s in a Row", () => {
28
27
  return Promise.resolve({
29
28
  ok: false,
30
29
  status: 401,
31
- text: () => Promise.resolve("Unauthorized"),
30
+ json: () => Promise.resolve({ message: "Unauthorized" }),
32
31
  });
33
32
  }
34
33
  if (url.includes("auth/temporary") && callCount === 3) {
@@ -70,15 +69,11 @@ describe("QuickbaseClient - Two 401s in a Row", () => {
70
69
  ]);
71
70
 
72
71
  expect(consoleSpy).toHaveBeenCalledWith(
73
- "Authorization error for getFields, refreshing token:",
72
+ "Authorization error for getFields (temp token), refreshing token:",
74
73
  expect.any(String)
75
74
  );
76
75
  expect(consoleSpy).toHaveBeenCalledWith(
77
- "Retrying getFields with new token"
78
- );
79
- expect(consoleSpy).toHaveBeenCalledWith(
80
- "Error response body for getFields:",
81
- { message: "Unauthorized again" }
76
+ "Retrying getFields with temp token"
82
77
  );
83
78
 
84
79
  consoleSpy.mockRestore();
@@ -0,0 +1,56 @@
1
+ import { describe, it, vi, expect, beforeEach } from "vitest";
2
+ import { quickbase, QuickbaseClient } from "../../../../src/quickbaseClient";
3
+ import { ResponseError } from "../../../../src/generated/runtime";
4
+
5
+ describe("QuickbaseClient Integration - User Token Retry on 401", () => {
6
+ let client: QuickbaseClient;
7
+ const mockFetch = vi.fn();
8
+
9
+ beforeEach(() => {
10
+ const config = {
11
+ realm: process.env.QB_REALM || "builderprogram-dbradford6815",
12
+ userToken: "mock-user-token",
13
+ debug: true,
14
+ fetchApi: mockFetch,
15
+ };
16
+ console.log("[quickbaseTest] Config:", config);
17
+ client = quickbase(config);
18
+ });
19
+
20
+ it("retries on transient 401 with user token and succeeds", async () => {
21
+ const consoleSpy = vi.spyOn(console, "log");
22
+
23
+ mockFetch
24
+ .mockImplementationOnce(() => {
25
+ const response = new Response(null, {
26
+ status: 401,
27
+ statusText: "Unauthorized",
28
+ });
29
+ console.log("[mockFetch] Throwing 401 ResponseError");
30
+ return Promise.reject(new ResponseError(response));
31
+ })
32
+ .mockResolvedValueOnce({
33
+ ok: true,
34
+ status: 200,
35
+ json: async () => [{ id: 1, label: "Mock Field", fieldType: "text" }],
36
+ });
37
+
38
+ const fields = await client.getFields({ tableId: "mock-table-id" });
39
+
40
+ expect(fields).toBeInstanceOf(Array);
41
+ expect(fields.length).toBeGreaterThan(0);
42
+ expect(fields[0]).toHaveProperty("id");
43
+ expect(fields[0]).toHaveProperty("label");
44
+
45
+ expect(consoleSpy).toHaveBeenCalledWith(
46
+ "Authorization error for getFields (user token), retrying with same token:",
47
+ "" // Matches empty ResponseError message from mock
48
+ );
49
+ expect(consoleSpy).toHaveBeenCalledWith(
50
+ "Retrying getFields with user token"
51
+ );
52
+ expect(mockFetch).toHaveBeenCalledTimes(2);
53
+
54
+ consoleSpy.mockRestore();
55
+ });
56
+ });
@@ -1,4 +1,3 @@
1
- // tests/vitest/unit/fields/createField.test.ts
2
1
  import { describe, it, expect, beforeEach, vi } from "vitest";
3
2
  import {
4
3
  createClient,
@@ -83,9 +82,9 @@ describe("QuickbaseClient Unit - createField", () => {
83
82
 
84
83
  expect(result).toEqual(mockResponse);
85
84
  const callArgs = mockFetch.mock.calls[0];
86
- console.log("Raw fetch call (user token):", callArgs); // Debug raw call
85
+ console.log("Raw fetch call (user token):", callArgs);
87
86
  const receivedBody = JSON.parse(callArgs[1].body as string);
88
- expect(receivedBody).toEqual(request); // Expect full body
87
+ expect(receivedBody).toEqual(request);
89
88
  expect(mockFetch).toHaveBeenCalledWith(
90
89
  `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
91
90
  expect.objectContaining({
@@ -172,7 +171,7 @@ describe("QuickbaseClient Unit - createField", () => {
172
171
  })
173
172
  );
174
173
  const callArgs = mockFetch.mock.calls[1];
175
- console.log("Raw fetch call (temp token):", callArgs); // Debug raw call
174
+ console.log("Raw fetch call (temp token):", callArgs);
176
175
  expect(mockFetch).toHaveBeenNthCalledWith(
177
176
  2,
178
177
  `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
@@ -287,11 +286,11 @@ describe("QuickbaseClient Unit - createField", () => {
287
286
  })
288
287
  );
289
288
  expect(consoleSpy).toHaveBeenCalledWith(
290
- "Authorization error for createField, refreshing token:",
289
+ "Authorization error for createField (temp token), refreshing token:",
291
290
  expect.any(String)
292
291
  );
293
292
  expect(consoleSpy).toHaveBeenCalledWith(
294
- "Retrying createField with new token"
293
+ "Retrying createField with temp token"
295
294
  );
296
295
  consoleSpy.mockRestore();
297
296
  });
@@ -351,16 +350,13 @@ describe("QuickbaseClient Unit - createField", () => {
351
350
  .mockResolvedValueOnce({
352
351
  ok: false,
353
352
  status: 401,
354
- json: () =>
355
- Promise.resolve({ message: "Unauthorized in fetchTempToken" }),
353
+ json: () => Promise.resolve({ message: "Temp token fetch failed" }),
356
354
  });
357
355
 
358
356
  const consoleSpy = vi.spyOn(console, "log");
359
357
  await expect(
360
358
  client.createField({ tableId: QB_TABLE_ID_1, body: request })
361
- ).rejects.toThrow(
362
- "API Error: Unauthorized in fetchTempToken (Status: 401)"
363
- );
359
+ ).rejects.toThrow("API Error: Temp token fetch failed (Status: 401)");
364
360
 
365
361
  expect(mockFetch).toHaveBeenCalledTimes(3);
366
362
  expect(mockFetch).toHaveBeenNthCalledWith(
@@ -383,7 +379,7 @@ describe("QuickbaseClient Unit - createField", () => {
383
379
  expect.any(Object)
384
380
  );
385
381
  expect(consoleSpy).toHaveBeenCalledWith(
386
- "Authorization error for createField, refreshing token:",
382
+ "Authorization error for createField (temp token), refreshing token:",
387
383
  expect.any(String)
388
384
  );
389
385
  consoleSpy.mockRestore();
@@ -1,4 +1,3 @@
1
- // tests/vitest/unit/fields/getFieldUsage.test.ts
2
1
  import { describe, it, expect, beforeEach } from "vitest";
3
2
  import {
4
3
  createClient,
@@ -27,7 +26,7 @@ describe("QuickbaseClient Unit - getFieldUsage", () => {
27
26
  field: {
28
27
  id: 6,
29
28
  name: "name",
30
- type: "text", // Match QuickBase's lowercase response
29
+ type: "text",
31
30
  },
32
31
  usage: {
33
32
  actions: { count: 0 },
@@ -50,7 +49,6 @@ describe("QuickbaseClient Unit - getFieldUsage", () => {
50
49
  },
51
50
  };
52
51
 
53
- // Mock the response as an array to match the updated API behavior
54
52
  mockFetch.mockResolvedValueOnce(
55
53
  new Response(JSON.stringify([mockResponse]), {
56
54
  status: 200,
@@ -64,7 +62,6 @@ describe("QuickbaseClient Unit - getFieldUsage", () => {
64
62
  tableId: QB_TABLE_ID_1,
65
63
  });
66
64
 
67
- // Expect an array and unwrap the first item
68
65
  expect(result).toBeInstanceOf(Array);
69
66
  expect(result).toHaveLength(1);
70
67
  const fieldUsage = result[0];
@@ -115,14 +112,23 @@ describe("QuickbaseClient Unit - getFieldUsage", () => {
115
112
  );
116
113
  });
117
114
 
118
- it("handles 401 error and does not retry with user token", async () => {
119
- mockFetch.mockResolvedValueOnce(
120
- new Response(JSON.stringify({ message: "Unauthorized" }), {
121
- status: 401,
122
- statusText: "Unauthorized",
123
- headers: { "Content-Type": "application/json" },
124
- })
125
- );
115
+ it("handles persistent 401 error for field usage retrieval", async () => {
116
+ // Simulate a persistent 401 error (initial and retry attempts fail)
117
+ mockFetch
118
+ .mockResolvedValueOnce(
119
+ new Response(JSON.stringify({ message: "Unauthorized" }), {
120
+ status: 401,
121
+ statusText: "Unauthorized",
122
+ headers: { "Content-Type": "application/json" },
123
+ })
124
+ )
125
+ .mockResolvedValueOnce(
126
+ new Response(JSON.stringify({ message: "Unauthorized" }), {
127
+ status: 401,
128
+ statusText: "Unauthorized",
129
+ headers: { "Content-Type": "application/json" },
130
+ })
131
+ );
126
132
 
127
133
  await expect(
128
134
  client.getFieldUsage({
@@ -131,8 +137,21 @@ describe("QuickbaseClient Unit - getFieldUsage", () => {
131
137
  })
132
138
  ).rejects.toThrow("API Error: Unauthorized (Status: 401)");
133
139
 
134
- expect(mockFetch).toHaveBeenCalledTimes(1); // No retry with user token
135
- expect(mockFetch).toHaveBeenCalledWith(
140
+ expect(mockFetch).toHaveBeenCalledTimes(2); // Initial call + 1 retry
141
+ expect(mockFetch).toHaveBeenNthCalledWith(
142
+ 1,
143
+ `https://api.quickbase.com/v1/fields/usage/6?tableId=${QB_TABLE_ID_1}`,
144
+ expect.objectContaining({
145
+ method: "GET",
146
+ headers: expect.objectContaining({
147
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
148
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
149
+ "Content-Type": "application/json",
150
+ }),
151
+ })
152
+ );
153
+ expect(mockFetch).toHaveBeenNthCalledWith(
154
+ 2,
136
155
  `https://api.quickbase.com/v1/fields/usage/6?tableId=${QB_TABLE_ID_1}`,
137
156
  expect.objectContaining({
138
157
  method: "GET",
@@ -0,0 +1,280 @@
1
+ // tests/vitest/unit/fields/updateField.test.ts
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import {
4
+ createClient,
5
+ mockFetch,
6
+ QB_APP_ID,
7
+ QB_REALM,
8
+ QB_USER_TOKEN,
9
+ QB_TABLE_ID_1,
10
+ } from "@tests/setup.ts";
11
+ import { UpdateFieldRequest, UpdateField200Response } from "@/generated/models";
12
+
13
+ describe("QuickbaseClient Unit - updateField", () => {
14
+ let client: ReturnType<typeof createClient>;
15
+
16
+ beforeEach(() => {
17
+ mockFetch.mockClear();
18
+ });
19
+
20
+ it("initializes and has updateField method", () => {
21
+ client = createClient(mockFetch, { debug: true });
22
+ expect(client).toBeDefined();
23
+ expect(typeof client.updateField).toBe("function");
24
+ });
25
+
26
+ it("updates field successfully with user token", async () => {
27
+ client = createClient(mockFetch, {
28
+ realm: QB_REALM,
29
+ userToken: QB_USER_TOKEN,
30
+ debug: true,
31
+ });
32
+
33
+ const tableId = QB_TABLE_ID_1;
34
+ const fieldId = 123;
35
+
36
+ const request: UpdateFieldRequest = {
37
+ label: "Field1",
38
+ // fieldType omitted as it’s not allowed in updates
39
+ noWrap: false,
40
+ bold: false,
41
+ required: true,
42
+ appearsByDefault: false,
43
+ findEnabled: false,
44
+ unique: true,
45
+ fieldHelp: "field help",
46
+ addToForms: true,
47
+ properties: {
48
+ numLines: 1,
49
+ maxLength: 50,
50
+ appendOnly: true,
51
+ sortAsGiven: false,
52
+ allowMentions: false,
53
+ comments: "Updated field settings",
54
+ doesTotal: false,
55
+ defaultValue: "Initial text",
56
+ choices: ["Option1", "Option2"], // Still valid for properties
57
+ },
58
+ permissions: [
59
+ { role: "Viewer", permissionType: "View", roleId: 10 },
60
+ { role: "Participant", permissionType: "None", roleId: 11 },
61
+ { role: "Administrator", permissionType: "Modify", roleId: 12 },
62
+ ],
63
+ };
64
+
65
+ const mockResponse: UpdateField200Response = {
66
+ id: fieldId,
67
+ label: "Field1",
68
+ fieldType: "text", // Returned by API, not updated
69
+ mode: "",
70
+ noWrap: false,
71
+ bold: false,
72
+ required: true,
73
+ appearsByDefault: false,
74
+ findEnabled: false,
75
+ unique: true,
76
+ doesDataCopy: false,
77
+ fieldHelp: "field help",
78
+ audited: false,
79
+ properties: {
80
+ numLines: 1,
81
+ maxLength: 50,
82
+ appendOnly: true,
83
+ sortAsGiven: false,
84
+ allowMentions: false,
85
+ comments: "Updated field settings",
86
+ doesTotal: false,
87
+ defaultValue: "Initial text",
88
+ choices: ["Option1", "Option2"],
89
+ allowHTML: false,
90
+ carryChoices: true,
91
+ allowNewChoices: false,
92
+ formula: "",
93
+ },
94
+ permissions: [
95
+ { permissionType: "View", role: "Viewer", roleId: 10 },
96
+ { permissionType: "None", role: "Participant", roleId: 11 },
97
+ { permissionType: "Modify", role: "Administrator", roleId: 12 },
98
+ ],
99
+ };
100
+
101
+ mockFetch.mockResolvedValueOnce({
102
+ ok: true,
103
+ status: 200,
104
+ json: () => Promise.resolve(mockResponse),
105
+ });
106
+
107
+ const result = await client.updateField({
108
+ fieldId,
109
+ tableId,
110
+ body: request,
111
+ });
112
+
113
+ expect(result).toEqual(mockResponse);
114
+ expect(mockFetch).toHaveBeenCalledWith(
115
+ `https://api.quickbase.com/v1/fields/${fieldId}?tableId=${tableId}`,
116
+ expect.objectContaining({
117
+ method: "POST",
118
+ headers: expect.objectContaining({
119
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
120
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
121
+ "Content-Type": "application/json",
122
+ }),
123
+ body: JSON.stringify(request),
124
+ credentials: "omit",
125
+ })
126
+ );
127
+ });
128
+
129
+ it("updates field successfully with temp token", async () => {
130
+ client = createClient(mockFetch, {
131
+ realm: QB_REALM,
132
+ useTempTokens: true,
133
+ debug: true,
134
+ });
135
+
136
+ const tableId = QB_TABLE_ID_1;
137
+ const fieldId = 123;
138
+
139
+ const request: UpdateFieldRequest = {
140
+ label: "Field1",
141
+ // fieldType omitted as it’s not allowed in updates
142
+ required: true,
143
+ unique: true,
144
+ properties: {
145
+ numLines: 1,
146
+ maxLength: 0,
147
+ choices: ["Yes", "No"],
148
+ allowNewChoices: true,
149
+ defaultValue: "No",
150
+ },
151
+ };
152
+
153
+ const mockResponse: UpdateField200Response = {
154
+ id: fieldId,
155
+ label: "Field1",
156
+ fieldType: "text-multiple-choice", // Returned by API, not updated
157
+ mode: "",
158
+ noWrap: false,
159
+ bold: false,
160
+ required: true,
161
+ appearsByDefault: false,
162
+ findEnabled: false,
163
+ unique: true,
164
+ doesDataCopy: false,
165
+ fieldHelp: "",
166
+ audited: false,
167
+ properties: {
168
+ numLines: 1,
169
+ maxLength: 0,
170
+ choices: ["Yes", "No"],
171
+ allowNewChoices: true,
172
+ defaultValue: "No",
173
+ appendOnly: false,
174
+ allowHTML: false,
175
+ allowMentions: false,
176
+ sortAsGiven: false,
177
+ carryChoices: true,
178
+ formula: "",
179
+ },
180
+ permissions: [
181
+ { permissionType: "View", role: "Viewer", roleId: 10 },
182
+ { permissionType: "None", role: "Participant", roleId: 11 },
183
+ { permissionType: "Modify", role: "Administrator", roleId: 12 },
184
+ ],
185
+ };
186
+
187
+ mockFetch
188
+ .mockResolvedValueOnce({
189
+ ok: true,
190
+ status: 200,
191
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
192
+ })
193
+ .mockResolvedValueOnce({
194
+ ok: true,
195
+ status: 200,
196
+ json: () => Promise.resolve(mockResponse),
197
+ });
198
+
199
+ const result = await client.updateField({
200
+ fieldId,
201
+ tableId,
202
+ body: request,
203
+ });
204
+
205
+ expect(result).toEqual(mockResponse);
206
+ expect(mockFetch).toHaveBeenCalledTimes(2);
207
+ expect(mockFetch).toHaveBeenNthCalledWith(
208
+ 1,
209
+ `https://api.quickbase.com/v1/auth/temporary/${tableId}`,
210
+ expect.objectContaining({
211
+ method: "GET",
212
+ headers: expect.objectContaining({
213
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
214
+ "Content-Type": "application/json",
215
+ }),
216
+ credentials: "include",
217
+ })
218
+ );
219
+ expect(mockFetch).toHaveBeenNthCalledWith(
220
+ 2,
221
+ `https://api.quickbase.com/v1/fields/${fieldId}?tableId=${tableId}`,
222
+ expect.objectContaining({
223
+ method: "POST",
224
+ headers: expect.objectContaining({
225
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
226
+ Authorization: "QB-TEMP-TOKEN temp_token",
227
+ "Content-Type": "application/json",
228
+ }),
229
+ body: JSON.stringify(request),
230
+ credentials: "omit",
231
+ })
232
+ );
233
+ });
234
+
235
+ it("handles 400 error for invalid field update", async () => {
236
+ client = createClient(mockFetch, {
237
+ realm: QB_REALM,
238
+ userToken: QB_USER_TOKEN,
239
+ debug: true,
240
+ });
241
+
242
+ const tableId = QB_TABLE_ID_1;
243
+ const fieldId = 999;
244
+
245
+ const request: UpdateFieldRequest = {
246
+ label: "InvalidField",
247
+ // fieldType omitted as it’s not allowed in updates
248
+ required: true,
249
+ properties: {
250
+ numLines: 1,
251
+ decimalPlaces: 2,
252
+ currencySymbol: "$",
253
+ },
254
+ };
255
+
256
+ mockFetch.mockResolvedValueOnce({
257
+ ok: false,
258
+ status: 400,
259
+ json: () => Promise.resolve({ message: "Invalid field ID" }),
260
+ });
261
+
262
+ await expect(
263
+ client.updateField({ fieldId, tableId, body: request })
264
+ ).rejects.toThrow("API Error: Invalid field ID (Status: 400)");
265
+
266
+ expect(mockFetch).toHaveBeenCalledWith(
267
+ `https://api.quickbase.com/v1/fields/${fieldId}?tableId=${tableId}`,
268
+ expect.objectContaining({
269
+ method: "POST",
270
+ headers: expect.objectContaining({
271
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
272
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
273
+ "Content-Type": "application/json",
274
+ }),
275
+ body: JSON.stringify(request),
276
+ credentials: "omit",
277
+ })
278
+ );
279
+ });
280
+ });