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.
- package/CHANGELOG.md +53 -22
- package/README.md +61 -5
- package/dist/quickbase.d.ts +1711 -613
- package/dist/quickbase.js +1469 -468
- package/dist/quickbase.js.map +1 -1
- package/examples/react.js +12 -12
- package/examples/vanillajs.html +12 -12
- package/log-gen.yaml +3558 -528
- package/package.json +12 -14
- package/playwright-results.txt/.last-run.json +4 -0
- package/playwright.config.ts +1 -1
- package/src/code-generation/definitions/apps.ts +274 -0
- package/src/code-generation/definitions/auth.ts +10 -0
- package/src/code-generation/definitions/fields.ts +751 -0
- package/src/code-generation/definitions/index.ts +18 -0
- package/src/code-generation/definitions/records.ts +269 -0
- package/src/code-generation/definitions/relationships.ts +90 -0
- package/src/code-generation/definitions/reports.ts +10 -0
- package/src/code-generation/definitions/tables.ts +112 -0
- package/src/code-generation/fix-spec-main.ts +2 -2
- package/src/code-generation/output/quickbase-fixed.json +1724 -3044
- package/src/code-generation/paths/apps.ts +100 -0
- package/src/code-generation/paths/auth.ts +25 -0
- package/src/code-generation/paths/fields.ts +207 -0
- package/src/code-generation/paths/index.ts +17 -0
- package/src/code-generation/paths/records.ts +68 -0
- package/src/code-generation/paths/relationships.ts +32 -0
- package/src/code-generation/paths/reports.ts +34 -0
- package/src/code-generation/paths/tables.ts +162 -0
- package/src/generated-unified/QuickbaseClient.ts +10 -8
- package/src/log-gen.ts +6 -2
- package/src/quickbaseClient.ts +35 -41
- package/tests/setup.ts +5 -1
- package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
- package/tests/vitest/qb/apps/createApp.test.ts +64 -0
- package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
- package/tests/vitest/qb/fields/createField.test.ts +78 -0
- package/tests/vitest/qb/fields/deleteFields.test.ts +89 -0
- package/tests/vitest/qb/fields/getField.test.ts +85 -0
- package/tests/vitest/qb/fields/getFieldUsage.test.ts +174 -0
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
- package/tests/vitest/qb/index.ts +25 -1
- package/tests/vitest/qb/records/deleteRecords.test.ts +42 -10
- package/tests/vitest/qb/records/runQuery.test.ts +81 -17
- package/tests/vitest/qb/records/upsert.test.ts +33 -3
- package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
- package/tests/vitest/unit/apps/copyApp.test.ts +282 -0
- package/tests/vitest/unit/apps/createApp.test.ts +125 -0
- package/tests/vitest/unit/apps/deleteApp.test.ts +215 -0
- package/tests/vitest/unit/fields/createField.test.ts +391 -0
- package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
- package/tests/vitest/unit/fields/getField.test.ts +189 -0
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +147 -0
- package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
- package/tests/vitest/unit/index.ts +30 -1
- package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
- package/src/code-generation/fix-spec-definitions.ts +0 -543
- package/src/code-generation/fix-spec-paths.ts +0 -355
- 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
|
+
});
|