quickbase-js 1.1.0-beta.2 → 1.1.0-beta.4

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 (59) hide show
  1. package/CHANGELOG.md +53 -22
  2. package/README.md +61 -5
  3. package/dist/quickbase.d.ts +1711 -613
  4. package/dist/quickbase.js +1469 -468
  5. package/dist/quickbase.js.map +1 -1
  6. package/examples/react.js +12 -12
  7. package/examples/vanillajs.html +12 -12
  8. package/log-gen.yaml +3558 -528
  9. package/package.json +12 -14
  10. package/playwright-results.txt/.last-run.json +4 -0
  11. package/playwright.config.ts +1 -1
  12. package/src/code-generation/definitions/apps.ts +274 -0
  13. package/src/code-generation/definitions/auth.ts +10 -0
  14. package/src/code-generation/definitions/fields.ts +751 -0
  15. package/src/code-generation/definitions/index.ts +18 -0
  16. package/src/code-generation/definitions/records.ts +269 -0
  17. package/src/code-generation/definitions/relationships.ts +90 -0
  18. package/src/code-generation/definitions/reports.ts +10 -0
  19. package/src/code-generation/definitions/tables.ts +112 -0
  20. package/src/code-generation/fix-spec-main.ts +2 -2
  21. package/src/code-generation/output/quickbase-fixed.json +1724 -3044
  22. package/src/code-generation/paths/apps.ts +100 -0
  23. package/src/code-generation/paths/auth.ts +25 -0
  24. package/src/code-generation/paths/fields.ts +207 -0
  25. package/src/code-generation/paths/index.ts +17 -0
  26. package/src/code-generation/paths/records.ts +68 -0
  27. package/src/code-generation/paths/relationships.ts +32 -0
  28. package/src/code-generation/paths/reports.ts +34 -0
  29. package/src/code-generation/paths/tables.ts +162 -0
  30. package/src/generated-unified/QuickbaseClient.ts +10 -8
  31. package/src/log-gen.ts +6 -2
  32. package/src/quickbaseClient.ts +35 -41
  33. package/tests/setup.ts +5 -1
  34. package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
  35. package/tests/vitest/qb/apps/createApp.test.ts +64 -0
  36. package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
  37. package/tests/vitest/qb/fields/createField.test.ts +78 -0
  38. package/tests/vitest/qb/fields/deleteFields.test.ts +89 -0
  39. package/tests/vitest/qb/fields/getField.test.ts +85 -0
  40. package/tests/vitest/qb/fields/getFieldUsage.test.ts +174 -0
  41. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
  42. package/tests/vitest/qb/index.ts +25 -1
  43. package/tests/vitest/qb/records/deleteRecords.test.ts +42 -10
  44. package/tests/vitest/qb/records/runQuery.test.ts +81 -17
  45. package/tests/vitest/qb/records/upsert.test.ts +33 -3
  46. package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
  47. package/tests/vitest/unit/apps/copyApp.test.ts +282 -0
  48. package/tests/vitest/unit/apps/createApp.test.ts +125 -0
  49. package/tests/vitest/unit/apps/deleteApp.test.ts +215 -0
  50. package/tests/vitest/unit/fields/createField.test.ts +391 -0
  51. package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
  52. package/tests/vitest/unit/fields/getField.test.ts +189 -0
  53. package/tests/vitest/unit/fields/getFieldUsage.test.ts +147 -0
  54. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
  55. package/tests/vitest/unit/index.ts +30 -1
  56. package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
  57. package/src/code-generation/fix-spec-definitions.ts +0 -543
  58. package/src/code-generation/fix-spec-paths.ts +0 -355
  59. package/test-artifacts/test-results.json +0 -829
@@ -0,0 +1,282 @@
1
+ // tests/vitest/unit/apps/copyApp.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
+ } from "@tests/setup.ts";
10
+ import { CopyAppRequest, CopyApp200Response } from "@/generated/models";
11
+
12
+ describe("QuickbaseClient Unit - copyApp", () => {
13
+ let client: ReturnType<typeof createClient>;
14
+
15
+ beforeEach(() => {
16
+ mockFetch.mockClear();
17
+ });
18
+
19
+ it("initializes without errors", () => {
20
+ client = createClient(mockFetch, { debug: true });
21
+ expect(client).toBeDefined();
22
+ });
23
+
24
+ it("has copyApp method", () => {
25
+ client = createClient(mockFetch, { debug: true });
26
+ expect(typeof client.copyApp).toBe("function");
27
+ });
28
+
29
+ it("calls copyApp successfully with user token", async () => {
30
+ client = createClient(mockFetch, { debug: true });
31
+
32
+ const request: CopyAppRequest = {
33
+ name: "Copied App",
34
+ description: "A copy of the original app",
35
+ properties: {
36
+ keepData: false,
37
+ excludeFiles: true,
38
+ usersAndRoles: false,
39
+ assignUserToken: true,
40
+ },
41
+ };
42
+
43
+ const mockResponse: CopyApp200Response = {
44
+ id: "bpqe82s1",
45
+ name: "Copied App",
46
+ description: "A copy of the original app",
47
+ created: new Date("2025-03-11T10:00:00Z"), // Changed to Date object
48
+ updated: new Date("2025-03-11T10:00:00Z"), // Changed to Date object
49
+ dateFormat: "MM-DD-YYYY",
50
+ timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
51
+ hasEveryoneOnTheInternet: false,
52
+ ancestorId: QB_APP_ID,
53
+ dataClassification: "None",
54
+ variables: [],
55
+ };
56
+
57
+ mockFetch.mockResolvedValueOnce({
58
+ ok: true,
59
+ status: 200,
60
+ json: () =>
61
+ Promise.resolve({
62
+ ...mockResponse,
63
+ created: "2025-03-11T10:00:00Z", // API returns string
64
+ updated: "2025-03-11T10:00:00Z", // API returns string
65
+ }),
66
+ });
67
+
68
+ const result = await client.copyApp({ appId: QB_APP_ID, body: request });
69
+
70
+ expect(result).toEqual(mockResponse);
71
+ expect(mockFetch).toHaveBeenCalledWith(
72
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
73
+ expect.objectContaining({
74
+ method: "POST",
75
+ headers: expect.objectContaining({
76
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
77
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
78
+ "Content-Type": "application/json",
79
+ }),
80
+ body: JSON.stringify(request),
81
+ })
82
+ );
83
+ });
84
+
85
+ it("calls copyApp successfully with temp token", async () => {
86
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
87
+
88
+ const request: CopyAppRequest = {
89
+ name: "Copied App with Temp Token",
90
+ description: "A copy using temp token",
91
+ properties: {
92
+ keepData: true,
93
+ excludeFiles: false,
94
+ usersAndRoles: true,
95
+ assignUserToken: false,
96
+ },
97
+ };
98
+
99
+ const mockResponse: CopyApp200Response = {
100
+ id: "bpqe82s2",
101
+ name: "Copied App with Temp Token",
102
+ description: "A copy using temp token",
103
+ created: new Date("2025-03-11T11:00:00Z"), // Changed to Date object
104
+ updated: new Date("2025-03-11T11:00:00Z"), // Changed to Date object
105
+ dateFormat: "MM-DD-YYYY",
106
+ timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
107
+ hasEveryoneOnTheInternet: true,
108
+ ancestorId: QB_APP_ID,
109
+ dataClassification: "None",
110
+ variables: [],
111
+ };
112
+
113
+ mockFetch
114
+ .mockResolvedValueOnce({
115
+ ok: true,
116
+ status: 200,
117
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
118
+ })
119
+ .mockResolvedValueOnce({
120
+ ok: true,
121
+ status: 200,
122
+ json: () =>
123
+ Promise.resolve({
124
+ ...mockResponse,
125
+ created: "2025-03-11T11:00:00Z", // API returns string
126
+ updated: "2025-03-11T11:00:00Z", // API returns string
127
+ }),
128
+ });
129
+
130
+ const result = await client.copyApp({ appId: QB_APP_ID, body: request });
131
+
132
+ expect(result).toEqual(mockResponse);
133
+ expect(mockFetch).toHaveBeenCalledTimes(2);
134
+ expect(mockFetch).toHaveBeenNthCalledWith(
135
+ 1,
136
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
137
+ expect.objectContaining({
138
+ method: "GET",
139
+ headers: expect.objectContaining({
140
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
141
+ "Content-Type": "application/json",
142
+ }),
143
+ credentials: "include",
144
+ })
145
+ );
146
+ expect(mockFetch).toHaveBeenNthCalledWith(
147
+ 2,
148
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
149
+ expect.objectContaining({
150
+ method: "POST",
151
+ headers: expect.objectContaining({
152
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
153
+ Authorization: "QB-TEMP-TOKEN temp_token",
154
+ "Content-Type": "application/json",
155
+ }),
156
+ body: JSON.stringify(request),
157
+ })
158
+ );
159
+ });
160
+
161
+ it("retries successfully after 401 with temp token", async () => {
162
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
163
+
164
+ const request: CopyAppRequest = {
165
+ name: "Retry Copied App",
166
+ description: "Retry after 401",
167
+ properties: {
168
+ keepData: false,
169
+ excludeFiles: true,
170
+ usersAndRoles: false,
171
+ assignUserToken: true,
172
+ },
173
+ };
174
+
175
+ const mockResponse: CopyApp200Response = {
176
+ id: "bpqe82s3",
177
+ name: "Retry Copied App",
178
+ description: "Retry after 401",
179
+ created: new Date("2025-03-11T12:00:00Z"), // Changed to Date object
180
+ updated: new Date("2025-03-11T12:00:00Z"), // Changed to Date object
181
+ dateFormat: "MM-DD-YYYY",
182
+ timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
183
+ hasEveryoneOnTheInternet: false,
184
+ ancestorId: QB_APP_ID,
185
+ dataClassification: "None",
186
+ variables: [],
187
+ };
188
+
189
+ mockFetch
190
+ .mockResolvedValueOnce({
191
+ ok: true,
192
+ status: 200,
193
+ json: () =>
194
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
195
+ })
196
+ .mockResolvedValueOnce({
197
+ ok: false,
198
+ status: 401,
199
+ json: () => Promise.resolve({ message: "Unauthorized" }),
200
+ })
201
+ .mockResolvedValueOnce({
202
+ ok: true,
203
+ status: 200,
204
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
205
+ })
206
+ .mockResolvedValueOnce({
207
+ ok: true,
208
+ status: 200,
209
+ json: () =>
210
+ Promise.resolve({
211
+ ...mockResponse,
212
+ created: "2025-03-11T12:00:00Z", // API returns string
213
+ updated: "2025-03-11T12:00:00Z", // API returns string
214
+ }),
215
+ });
216
+
217
+ const result = await client.copyApp({ appId: QB_APP_ID, body: request });
218
+
219
+ expect(result).toEqual(mockResponse);
220
+ expect(mockFetch).toHaveBeenCalledTimes(4);
221
+ expect(mockFetch).toHaveBeenNthCalledWith(
222
+ 1,
223
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
224
+ expect.any(Object)
225
+ );
226
+ expect(mockFetch).toHaveBeenNthCalledWith(
227
+ 2,
228
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
229
+ expect.objectContaining({
230
+ headers: expect.objectContaining({
231
+ Authorization: "QB-TEMP-TOKEN initial_token",
232
+ }),
233
+ })
234
+ );
235
+ expect(mockFetch).toHaveBeenNthCalledWith(
236
+ 3,
237
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
238
+ expect.any(Object)
239
+ );
240
+ expect(mockFetch).toHaveBeenNthCalledWith(
241
+ 4,
242
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
243
+ expect.objectContaining({
244
+ headers: expect.objectContaining({
245
+ Authorization: "QB-TEMP-TOKEN new_token",
246
+ }),
247
+ })
248
+ );
249
+ });
250
+
251
+ it("handles 400 error for invalid request", async () => {
252
+ client = createClient(mockFetch, { debug: true });
253
+
254
+ const request: CopyAppRequest = {
255
+ name: "", // Invalid: name is required
256
+ description: "Invalid request",
257
+ };
258
+
259
+ mockFetch.mockResolvedValueOnce({
260
+ ok: false,
261
+ status: 400,
262
+ json: () => Promise.resolve({ message: "Name is required" }),
263
+ });
264
+
265
+ await expect(
266
+ client.copyApp({ appId: QB_APP_ID, body: request })
267
+ ).rejects.toThrow("API Error: Name is required (Status: 400)");
268
+
269
+ expect(mockFetch).toHaveBeenCalledWith(
270
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
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: JSON.stringify(request),
279
+ })
280
+ );
281
+ });
282
+ });
@@ -0,0 +1,125 @@
1
+ // tests/vitest/unit/apps/createApp.test.ts
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import {
4
+ createClient,
5
+ mockFetch,
6
+ QB_REALM,
7
+ QB_USER_TOKEN,
8
+ } from "@tests/setup.ts";
9
+ import { CreateAppRequest, CreateApp200Response } from "@/generated/models";
10
+
11
+ describe("QuickbaseClient Unit - createApp", () => {
12
+ let client: ReturnType<typeof createClient>;
13
+
14
+ beforeEach(() => {
15
+ mockFetch.mockClear();
16
+ });
17
+
18
+ it("initializes without errors", () => {
19
+ client = createClient(mockFetch, { debug: true });
20
+ expect(client).toBeDefined();
21
+ });
22
+
23
+ it("has createApp method", () => {
24
+ client = createClient(mockFetch, { debug: true });
25
+ expect(typeof client.createApp).toBe("function");
26
+ });
27
+
28
+ it("calls createApp successfully with user token", async () => {
29
+ client = createClient(mockFetch, { debug: true });
30
+
31
+ const request: CreateAppRequest = {
32
+ name: "New App",
33
+ description: "A new app for testing",
34
+ assignToken: true,
35
+ };
36
+
37
+ const mockResponse: CreateApp200Response = {
38
+ id: "new-app-id",
39
+ name: "New App",
40
+ description: "A new app for testing",
41
+ created: new Date("2025-03-11T10:00:00Z"),
42
+ updated: new Date("2025-03-11T10:00:00Z"),
43
+ dateFormat: "MM-DD-YYYY",
44
+ timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
45
+ hasEveryoneOnTheInternet: false,
46
+ variables: [],
47
+ dataClassification: "None",
48
+ memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
49
+ securityProperties: {
50
+ allowClone: false,
51
+ allowExport: true,
52
+ enableAppTokens: true,
53
+ hideFromPublic: false,
54
+ mustBeRealmApproved: false,
55
+ useIPFilter: false,
56
+ },
57
+ };
58
+
59
+ mockFetch.mockResolvedValueOnce({
60
+ ok: true,
61
+ status: 200,
62
+ json: () =>
63
+ Promise.resolve({
64
+ ...mockResponse,
65
+ created: "2025-03-11T10:00:00Z", // API returns string
66
+ updated: "2025-03-11T10:00:00Z", // API returns string
67
+ }),
68
+ });
69
+
70
+ const result = await client.createApp({ body: request });
71
+
72
+ expect(result).toEqual(mockResponse);
73
+ expect(mockFetch).toHaveBeenCalledWith(
74
+ `https://api.quickbase.com/v1/apps`,
75
+ expect.objectContaining({
76
+ method: "POST",
77
+ headers: expect.objectContaining({
78
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
79
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
80
+ "Content-Type": "application/json",
81
+ }),
82
+ body: expect.stringContaining('"name":"New App"'),
83
+ credentials: "omit", // Added to match actual behavior
84
+ })
85
+ );
86
+ // Additional check to ensure body contains all properties regardless of order
87
+ const callArgs = mockFetch.mock.calls[0];
88
+ const body = JSON.parse(callArgs[1].body as string);
89
+ expect(body).toEqual(request);
90
+ });
91
+
92
+ it("handles 400 error for invalid request", async () => {
93
+ client = createClient(mockFetch, { debug: true });
94
+
95
+ const request: CreateAppRequest = {
96
+ name: "", // Invalid: name is required
97
+ };
98
+
99
+ mockFetch.mockImplementationOnce(() =>
100
+ Promise.resolve({
101
+ ok: false,
102
+ status: 400,
103
+ json: () => Promise.resolve({ message: "Name is required" }),
104
+ })
105
+ );
106
+
107
+ await expect(client.createApp({ body: request })).rejects.toThrow(
108
+ "API Error: Name is required (Status: 400)"
109
+ );
110
+
111
+ expect(mockFetch).toHaveBeenCalledWith(
112
+ `https://api.quickbase.com/v1/apps`,
113
+ expect.objectContaining({
114
+ method: "POST",
115
+ headers: expect.objectContaining({
116
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
117
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
118
+ "Content-Type": "application/json",
119
+ }),
120
+ body: JSON.stringify(request),
121
+ credentials: "omit", // Added to match actual behavior
122
+ })
123
+ );
124
+ });
125
+ });
@@ -0,0 +1,215 @@
1
+ // tests/vitest/unit/apps/deleteApp.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
+ } from "@tests/setup.ts";
10
+ import { DeleteAppRequest, DeleteApp200Response } from "@/generated/models";
11
+
12
+ describe("QuickbaseClient Unit - deleteApp", () => {
13
+ let client: ReturnType<typeof createClient>;
14
+
15
+ beforeEach(() => {
16
+ mockFetch.mockClear();
17
+ });
18
+
19
+ it("initializes without errors", () => {
20
+ client = createClient(mockFetch, { debug: true });
21
+ expect(client).toBeDefined();
22
+ });
23
+
24
+ it("has deleteApp method", () => {
25
+ client = createClient(mockFetch, { debug: true });
26
+ expect(typeof client.deleteApp).toBe("function");
27
+ });
28
+
29
+ it("calls deleteApp successfully with user token", async () => {
30
+ client = createClient(mockFetch, { debug: true });
31
+
32
+ const request: DeleteAppRequest = {
33
+ name: "App to Delete",
34
+ };
35
+
36
+ const mockResponse: DeleteApp200Response = {
37
+ deletedAppId: QB_APP_ID,
38
+ };
39
+
40
+ mockFetch.mockResolvedValueOnce({
41
+ ok: true,
42
+ status: 200,
43
+ json: () => Promise.resolve(mockResponse),
44
+ });
45
+
46
+ const result = await client.deleteApp({ appId: QB_APP_ID, body: request });
47
+
48
+ expect(result).toEqual(mockResponse);
49
+ expect(mockFetch).toHaveBeenCalledWith(
50
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
51
+ expect.objectContaining({
52
+ method: "DELETE",
53
+ headers: expect.objectContaining({
54
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
55
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
56
+ "Content-Type": "application/json",
57
+ }),
58
+ body: JSON.stringify(request),
59
+ })
60
+ );
61
+ });
62
+
63
+ it("calls deleteApp successfully with temp token", async () => {
64
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
65
+
66
+ const request: DeleteAppRequest = {
67
+ name: "App to Delete with Temp Token",
68
+ };
69
+
70
+ const mockResponse: DeleteApp200Response = {
71
+ deletedAppId: QB_APP_ID,
72
+ };
73
+
74
+ mockFetch
75
+ .mockResolvedValueOnce({
76
+ ok: true,
77
+ status: 200,
78
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
79
+ })
80
+ .mockResolvedValueOnce({
81
+ ok: true,
82
+ status: 200,
83
+ json: () => Promise.resolve(mockResponse),
84
+ });
85
+
86
+ const result = await client.deleteApp({ appId: QB_APP_ID, body: request });
87
+
88
+ expect(result).toEqual(mockResponse);
89
+ expect(mockFetch).toHaveBeenCalledTimes(2);
90
+ expect(mockFetch).toHaveBeenNthCalledWith(
91
+ 1,
92
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
93
+ expect.objectContaining({
94
+ method: "GET",
95
+ headers: expect.objectContaining({
96
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
97
+ "Content-Type": "application/json",
98
+ }),
99
+ credentials: "include",
100
+ })
101
+ );
102
+ expect(mockFetch).toHaveBeenNthCalledWith(
103
+ 2,
104
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
105
+ expect.objectContaining({
106
+ method: "DELETE",
107
+ headers: expect.objectContaining({
108
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
109
+ Authorization: "QB-TEMP-TOKEN temp_token",
110
+ "Content-Type": "application/json",
111
+ }),
112
+ body: JSON.stringify(request),
113
+ })
114
+ );
115
+ });
116
+
117
+ it("retries successfully after 401 with temp token", async () => {
118
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
119
+
120
+ const request: DeleteAppRequest = {
121
+ name: "Retry Delete App",
122
+ };
123
+
124
+ const mockResponse: DeleteApp200Response = {
125
+ deletedAppId: QB_APP_ID,
126
+ };
127
+
128
+ mockFetch
129
+ .mockResolvedValueOnce({
130
+ ok: true,
131
+ status: 200,
132
+ json: () =>
133
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
134
+ })
135
+ .mockResolvedValueOnce({
136
+ ok: false,
137
+ status: 401,
138
+ json: () => Promise.resolve({ message: "Unauthorized" }),
139
+ })
140
+ .mockResolvedValueOnce({
141
+ ok: true,
142
+ status: 200,
143
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
144
+ })
145
+ .mockResolvedValueOnce({
146
+ ok: true,
147
+ status: 200,
148
+ json: () => Promise.resolve(mockResponse),
149
+ });
150
+
151
+ const result = await client.deleteApp({ appId: QB_APP_ID, body: request });
152
+
153
+ expect(result).toEqual(mockResponse);
154
+ expect(mockFetch).toHaveBeenCalledTimes(4);
155
+ expect(mockFetch).toHaveBeenNthCalledWith(
156
+ 1,
157
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
158
+ expect.any(Object)
159
+ );
160
+ expect(mockFetch).toHaveBeenNthCalledWith(
161
+ 2,
162
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
163
+ expect.objectContaining({
164
+ headers: expect.objectContaining({
165
+ Authorization: "QB-TEMP-TOKEN initial_token",
166
+ }),
167
+ })
168
+ );
169
+ expect(mockFetch).toHaveBeenNthCalledWith(
170
+ 3,
171
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
172
+ expect.any(Object)
173
+ );
174
+ expect(mockFetch).toHaveBeenNthCalledWith(
175
+ 4,
176
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
177
+ expect.objectContaining({
178
+ headers: expect.objectContaining({
179
+ Authorization: "QB-TEMP-TOKEN new_token",
180
+ }),
181
+ })
182
+ );
183
+ });
184
+
185
+ it("handles 400 error for invalid request", async () => {
186
+ client = createClient(mockFetch, { debug: true });
187
+
188
+ const request: DeleteAppRequest = {
189
+ name: "Wrong Name", // Assuming this doesn’t match the app’s actual name
190
+ };
191
+
192
+ mockFetch.mockResolvedValueOnce({
193
+ ok: false,
194
+ status: 400,
195
+ json: () => Promise.resolve({ message: "Invalid application name" }),
196
+ });
197
+
198
+ await expect(
199
+ client.deleteApp({ appId: QB_APP_ID, body: request })
200
+ ).rejects.toThrow("API Error: Invalid application name (Status: 400)");
201
+
202
+ expect(mockFetch).toHaveBeenCalledWith(
203
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
204
+ expect.objectContaining({
205
+ method: "DELETE",
206
+ headers: expect.objectContaining({
207
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
208
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
209
+ "Content-Type": "application/json",
210
+ }),
211
+ body: JSON.stringify(request),
212
+ })
213
+ );
214
+ });
215
+ });