quickbase-js 1.1.0-beta.2 → 1.1.0-beta.3
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 +30 -22
- package/dist/quickbase.d.ts +424 -85
- package/dist/quickbase.js +552 -116
- package/dist/quickbase.js.map +1 -1
- package/log-gen.yaml +1136 -1969
- package/package.json +1 -1
- package/src/code-generation/fix-spec-definitions.ts +269 -3
- package/src/code-generation/fix-spec-paths.ts +84 -0
- package/src/code-generation/output/quickbase-fixed.json +380 -358
- package/src/generated-unified/QuickbaseClient.ts +5 -4
- package/src/log-gen.ts +5 -3
- 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/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/tables/getRelationships.test.ts +259 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
// Generated on 2025-03-
|
|
2
|
-
import { App, CreateApp200Response, CreateAppRequest, DeleteRecords200Response, DeleteRecordsRequest, DeleteTableResponse, Field, GetTempTokenDBID200Response, ReportRunResponse, RunQueryRequest, RunQueryResponse, Table, UpdateTableRequest, Upsert200Response, Upsert207Response, UpsertRequest } from "../generated/models";
|
|
1
|
+
// Generated on 2025-03-11T21:50:08.483Z
|
|
2
|
+
import { App, CopyApp200Response, CopyAppRequest, CreateApp200Response, CreateAppRequest, DeleteApp200Response, DeleteAppRequest, DeleteRecords200Response, DeleteRecordsRequest, DeleteTableResponse, Field, GetRelationships200Response, GetTempTokenDBID200Response, ReportRunResponse, RunQueryRequest, RunQueryResponse, Table, UpdateTableRequest, Upsert200Response, Upsert207Response, UpsertRequest } from "../generated/models";
|
|
3
3
|
|
|
4
4
|
export interface QuickbaseClient {
|
|
5
5
|
createApp: (params: { body: CreateAppRequest }) => Promise<CreateApp200Response>;
|
|
6
6
|
getApp: (params: { appId: string }) => Promise<App>;
|
|
7
|
+
deleteApp: (params: { appId: string; body: DeleteAppRequest }) => Promise<DeleteApp200Response>;
|
|
7
8
|
getAppEvents: (params: { appId: string }) => Promise<any[]>;
|
|
8
|
-
copyApp: (params: { appId: string;
|
|
9
|
+
copyApp: (params: { appId: string; body: CopyAppRequest }) => Promise<CopyApp200Response>;
|
|
9
10
|
getAppTables: (params: { appId: string }) => Promise<Table[]>;
|
|
10
11
|
createTable: (params: { appId: string; body: any }) => Promise<Table>;
|
|
11
12
|
getTable: (params: { tableId: string; appId: string }) => Promise<Table>;
|
|
12
13
|
updateTable: (params: { tableId: string; appId: string; body: UpdateTableRequest }) => Promise<Table>;
|
|
13
14
|
deleteTable: (params: { tableId: string; appId: string }) => Promise<DeleteTableResponse>;
|
|
14
|
-
getRelationships: (params: { skip?: number
|
|
15
|
+
getRelationships: (params: { tableId: string; skip?: number }) => Promise<GetRelationships200Response>;
|
|
15
16
|
createRelationship: (params: { tableId: string; generated?: any }) => Promise<{ [key: string]: any }>;
|
|
16
17
|
updateRelationship: (params: { tableId: string; relationshipId: any; generated?: any }) => Promise<{ [key: string]: any }>;
|
|
17
18
|
deleteRelationship: (params: { tableId: string; relationshipId: any }) => Promise<{ [key: string]: any }>;
|
package/src/log-gen.ts
CHANGED
|
@@ -17,7 +17,9 @@ const includeFolders: string[] = [
|
|
|
17
17
|
|
|
18
18
|
// prettier-ignore
|
|
19
19
|
const includeRecursiveFolders: string[] = [
|
|
20
|
-
"tests",
|
|
20
|
+
// "tests",
|
|
21
|
+
"tests/vitest/unit",
|
|
22
|
+
// "tests/vitest/qb"
|
|
21
23
|
];
|
|
22
24
|
|
|
23
25
|
// prettier-ignore
|
|
@@ -28,8 +30,8 @@ const includeFiles: string[] = [
|
|
|
28
30
|
// "vitest.config.ts",
|
|
29
31
|
// "src/utils.ts",
|
|
30
32
|
// "src/tokenCache.ts",
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
"src/generated-unified/QuickbaseClient.ts",
|
|
34
|
+
"src/quickbaseClient.ts",
|
|
33
35
|
];
|
|
34
36
|
|
|
35
37
|
const projectGoals: string[] = [
|
package/src/quickbaseClient.ts
CHANGED
|
@@ -79,15 +79,15 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
79
79
|
|
|
80
80
|
const tokenCache = new TokenCache();
|
|
81
81
|
|
|
82
|
-
const
|
|
82
|
+
const baseHeaders: HTTPHeaders = {
|
|
83
83
|
"QB-Realm-Hostname": `${realm}.quickbase.com`,
|
|
84
84
|
"Content-Type": "application/json",
|
|
85
85
|
};
|
|
86
86
|
|
|
87
87
|
if (initialTempToken) {
|
|
88
|
-
|
|
88
|
+
baseHeaders["Authorization"] = `QB-TEMP-TOKEN ${initialTempToken}`;
|
|
89
89
|
} else if (userToken && !useTempTokens) {
|
|
90
|
-
|
|
90
|
+
baseHeaders["Authorization"] = `QB-USER-TOKEN ${userToken}`;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
const defaultFetch: typeof fetch | undefined =
|
|
@@ -96,7 +96,7 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
96
96
|
: undefined;
|
|
97
97
|
const configuration = new Configuration({
|
|
98
98
|
basePath: baseUrl,
|
|
99
|
-
headers: { ...
|
|
99
|
+
headers: { ...baseHeaders },
|
|
100
100
|
fetchApi: fetchApi || defaultFetch,
|
|
101
101
|
credentials: useTempTokens ? "include" : "omit",
|
|
102
102
|
});
|
|
@@ -162,7 +162,7 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
162
162
|
`https://api.quickbase.com/v1/auth/temporary/${dbid}`,
|
|
163
163
|
{
|
|
164
164
|
method: "GET",
|
|
165
|
-
headers: { ...
|
|
165
|
+
headers: { ...baseHeaders },
|
|
166
166
|
credentials: "include",
|
|
167
167
|
}
|
|
168
168
|
);
|
|
@@ -194,23 +194,24 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
194
194
|
return token;
|
|
195
195
|
};
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
async function invokeMethod<K extends keyof QuickbaseClient>(
|
|
198
198
|
methodName: K,
|
|
199
199
|
params: Parameters<QuickbaseClient[K]>[0] & Partial<TempTokenParams>,
|
|
200
200
|
retryCount: number = 0
|
|
201
|
-
): Promise<ReturnType<QuickbaseClient[K]>>
|
|
201
|
+
): Promise<ReturnType<QuickbaseClient[K]>> {
|
|
202
202
|
const methodInfo = methodMap[methodName];
|
|
203
203
|
if (!methodInfo) {
|
|
204
204
|
throw new Error(`Method ${methodName} not found`);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
const
|
|
207
|
+
const requestParameters =
|
|
208
208
|
methodInfo.paramMap.length === 1 &&
|
|
209
209
|
methodInfo.paramMap[0] === "requestParameters"
|
|
210
|
-
?
|
|
211
|
-
:
|
|
210
|
+
? { requestParameters: params }
|
|
211
|
+
: params;
|
|
212
|
+
let requestOptions: RequestInit = {};
|
|
212
213
|
|
|
213
|
-
|
|
214
|
+
const selectedToken =
|
|
214
215
|
initialTempToken || (userToken && !useTempTokens ? userToken : undefined);
|
|
215
216
|
|
|
216
217
|
if (methodName === "getTempTokenDBID" && useTempTokens) {
|
|
@@ -223,43 +224,38 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
223
224
|
}
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
|
|
227
|
+
let authorizationToken = selectedToken;
|
|
228
|
+
if (useTempTokens && !authorizationToken) {
|
|
227
229
|
const dbid = extractDbid(
|
|
228
230
|
params,
|
|
229
231
|
`No dbid found in params for ${methodName} to fetch temp token`
|
|
230
232
|
);
|
|
231
233
|
const cachedToken = tokenCache.get(dbid);
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
"Temporary tokens require a browser environment or a custom fetchApi with browser-like session support"
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
token = await fetchTempToken(dbid);
|
|
241
|
-
if (methodName === "getTempTokenDBID") {
|
|
242
|
-
return { temporaryAuthorization: token } as ReturnType<
|
|
243
|
-
QuickbaseClient[K]
|
|
244
|
-
>;
|
|
245
|
-
}
|
|
234
|
+
authorizationToken = cachedToken || (await fetchTempToken(dbid));
|
|
235
|
+
if (methodName === "getTempTokenDBID") {
|
|
236
|
+
return { temporaryAuthorization: authorizationToken } as ReturnType<
|
|
237
|
+
QuickbaseClient[K]
|
|
238
|
+
>;
|
|
246
239
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
240
|
+
requestOptions.headers = {
|
|
241
|
+
...baseHeaders,
|
|
242
|
+
Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
|
|
243
|
+
};
|
|
244
|
+
} else if (authorizationToken) {
|
|
245
|
+
requestOptions.headers = {
|
|
246
|
+
...baseHeaders,
|
|
247
|
+
Authorization: `QB-USER-TOKEN ${authorizationToken}`,
|
|
252
248
|
};
|
|
253
249
|
}
|
|
254
250
|
|
|
255
251
|
try {
|
|
256
|
-
|
|
257
|
-
return response;
|
|
252
|
+
return await methodInfo.method(requestParameters, requestOptions);
|
|
258
253
|
} catch (error) {
|
|
259
254
|
if (
|
|
260
255
|
error instanceof ResponseError &&
|
|
261
256
|
error.response.status === 401 &&
|
|
262
|
-
retryCount < 1
|
|
257
|
+
retryCount < 1 &&
|
|
258
|
+
useTempTokens
|
|
263
259
|
) {
|
|
264
260
|
if (debug) {
|
|
265
261
|
console.log(
|
|
@@ -271,12 +267,10 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
271
267
|
params,
|
|
272
268
|
`No dbid to refresh token after authorization error`
|
|
273
269
|
);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
Authorization: `QB-TEMP-TOKEN ${token}`,
|
|
279
|
-
},
|
|
270
|
+
authorizationToken = await fetchTempToken(dbid);
|
|
271
|
+
requestOptions.headers = {
|
|
272
|
+
...baseHeaders,
|
|
273
|
+
Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
|
|
280
274
|
};
|
|
281
275
|
if (debug) {
|
|
282
276
|
console.log(`Retrying ${methodName} with new token`);
|
|
@@ -300,7 +294,7 @@ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
|
300
294
|
}
|
|
301
295
|
throw error;
|
|
302
296
|
}
|
|
303
|
-
}
|
|
297
|
+
}
|
|
304
298
|
|
|
305
299
|
const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, {
|
|
306
300
|
get: (_, prop: string): ((params: any) => Promise<any>) | undefined => {
|
package/tests/setup.ts
CHANGED
|
@@ -13,13 +13,17 @@ export const QB_REALM = process.env.QB_REALM || "test-realm";
|
|
|
13
13
|
export const QB_USER_TOKEN =
|
|
14
14
|
process.env.QB_USER_TOKEN || "user-token-1234567890";
|
|
15
15
|
export const QB_APP_ID = process.env.QB_APP_ID || "app-id-1234567890";
|
|
16
|
-
export const QB_TABLE_ID_1 =
|
|
16
|
+
export const QB_TABLE_ID_1 =
|
|
17
|
+
process.env.QB_TABLE_ID_1 || "table-id-1-1234567890";
|
|
18
|
+
export const QB_TABLE_ID_2 =
|
|
19
|
+
process.env.QB_TABLE_ID_2 || "table-id-2-1234567890";
|
|
17
20
|
|
|
18
21
|
// Mock env variables for consistency, using .env values if available
|
|
19
22
|
vi.stubEnv("QB_REALM", QB_REALM);
|
|
20
23
|
vi.stubEnv("QB_USER_TOKEN", QB_USER_TOKEN);
|
|
21
24
|
vi.stubEnv("QB_APP_ID", QB_APP_ID);
|
|
22
25
|
vi.stubEnv("QB_TABLE_ID_1", QB_TABLE_ID_1);
|
|
26
|
+
vi.stubEnv("QB_TABLE_ID_2", QB_TABLE_ID_2);
|
|
23
27
|
|
|
24
28
|
export const createClient = (
|
|
25
29
|
fetchApi?: any,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// tests/vitest/qb/apps/copyApp.test.ts
|
|
2
|
+
import { test, expect } from "vitest";
|
|
3
|
+
import { quickbase } from "../../../../src/quickbaseClient";
|
|
4
|
+
|
|
5
|
+
test(
|
|
6
|
+
"QuickbaseClient Integration - copyApp > creates a new copied app in QuickBase",
|
|
7
|
+
{ timeout: 30000 }, // Increased to 30 seconds
|
|
8
|
+
async () => {
|
|
9
|
+
const config = {
|
|
10
|
+
realm: process.env.QB_REALM || "",
|
|
11
|
+
userToken: process.env.QB_USER_TOKEN || "",
|
|
12
|
+
debug: true,
|
|
13
|
+
};
|
|
14
|
+
const client = quickbase(config);
|
|
15
|
+
const appId = process.env.QB_APP_ID || ""; // The source app ID to copy from
|
|
16
|
+
|
|
17
|
+
const copyAppName = `CopiedApp_${Date.now()}`; // Unique name to avoid conflicts
|
|
18
|
+
const copyBody = {
|
|
19
|
+
name: copyAppName,
|
|
20
|
+
description: "Test copied app",
|
|
21
|
+
properties: {
|
|
22
|
+
keepData: false,
|
|
23
|
+
excludeFiles: true,
|
|
24
|
+
usersAndRoles: false,
|
|
25
|
+
assignUserToken: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
console.log("Config used:", {
|
|
30
|
+
...config,
|
|
31
|
+
userToken: config.userToken ? "[REDACTED]" : "", // Avoid logging sensitive token
|
|
32
|
+
});
|
|
33
|
+
console.log("Copying app with:", copyBody);
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
let response;
|
|
36
|
+
try {
|
|
37
|
+
response = await client.copyApp({ appId, body: copyBody });
|
|
38
|
+
const duration = Date.now() - startTime;
|
|
39
|
+
console.log(`copyApp completed in ${duration}ms`);
|
|
40
|
+
console.log("Real API response:", response);
|
|
41
|
+
|
|
42
|
+
expect(response).toBeDefined();
|
|
43
|
+
expect(response.id).toBeDefined();
|
|
44
|
+
expect(response.name).toBe(copyAppName);
|
|
45
|
+
expect(response.description).toBe(copyBody.description);
|
|
46
|
+
expect(response.created).toBeInstanceOf(Date);
|
|
47
|
+
expect(response.updated).toBeInstanceOf(Date);
|
|
48
|
+
expect(response.dateFormat).toMatch(/^\w{2}-\w{2}-\w{4}$/); // e.g., "MM-DD-YYYY"
|
|
49
|
+
expect(typeof response.timeZone).toBe("string"); // Simplified check
|
|
50
|
+
expect(typeof response.hasEveryoneOnTheInternet).toBe("boolean");
|
|
51
|
+
expect(response.ancestorId).toBe(appId); // Should match the source app ID
|
|
52
|
+
expect(
|
|
53
|
+
response.dataClassification === undefined ||
|
|
54
|
+
typeof response.dataClassification === "string"
|
|
55
|
+
).toBe(true); // Allow undefined or string
|
|
56
|
+
expect(
|
|
57
|
+
response.variables === undefined || Array.isArray(response.variables)
|
|
58
|
+
).toBe(true); // Allow undefined or array
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const duration = Date.now() - startTime;
|
|
61
|
+
console.error(`copyApp failed after ${duration}ms:`, error);
|
|
62
|
+
throw error; // Re-throw to fail the test
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Cleanup: Delete the copied app using deleteApp
|
|
66
|
+
const deleteStartTime = Date.now();
|
|
67
|
+
const deleteResponse = await client.deleteApp({
|
|
68
|
+
appId: response.id,
|
|
69
|
+
body: { name: copyAppName },
|
|
70
|
+
});
|
|
71
|
+
const deleteDuration = Date.now() - deleteStartTime;
|
|
72
|
+
console.log(`deleteApp completed in ${deleteDuration}ms`);
|
|
73
|
+
console.log("Cleanup - Deleted app:", deleteResponse);
|
|
74
|
+
expect(deleteResponse.deletedAppId).toBe(response.id);
|
|
75
|
+
}
|
|
76
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// tests/vitest/qb/apps/createApp.test.ts
|
|
2
|
+
import { test, expect } from "vitest";
|
|
3
|
+
import { quickbase } from "../../../../src/quickbaseClient";
|
|
4
|
+
|
|
5
|
+
test(
|
|
6
|
+
"QuickbaseClient Integration - createApp > creates a new app in QuickBase",
|
|
7
|
+
{ timeout: 30000 },
|
|
8
|
+
async () => {
|
|
9
|
+
const config = {
|
|
10
|
+
realm: process.env.QB_REALM || "",
|
|
11
|
+
userToken: process.env.QB_USER_TOKEN || "",
|
|
12
|
+
debug: true,
|
|
13
|
+
useTempTokens: false,
|
|
14
|
+
};
|
|
15
|
+
const client = quickbase(config);
|
|
16
|
+
|
|
17
|
+
const appName = `TestApp_${Date.now()}`;
|
|
18
|
+
const createBody = {
|
|
19
|
+
name: appName,
|
|
20
|
+
description: "Test app creation",
|
|
21
|
+
assignToken: true,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
console.log("Config used:", {
|
|
25
|
+
...config,
|
|
26
|
+
userToken: config.userToken ? "[REDACTED]" : "",
|
|
27
|
+
});
|
|
28
|
+
console.log("Creating app with:", createBody);
|
|
29
|
+
const createStartTime = Date.now();
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await client.createApp({ body: createBody });
|
|
33
|
+
const createDuration = Date.now() - createStartTime;
|
|
34
|
+
console.log(`createApp completed in ${createDuration}ms`);
|
|
35
|
+
console.log("Real API response:", response);
|
|
36
|
+
|
|
37
|
+
expect(response).toBeDefined();
|
|
38
|
+
expect(response.id).toBeDefined();
|
|
39
|
+
expect(response.name).toBe(appName);
|
|
40
|
+
expect(response.description).toBe(createBody.description);
|
|
41
|
+
expect(response.created).toBeInstanceOf(Date);
|
|
42
|
+
expect(response.updated).toBeInstanceOf(Date);
|
|
43
|
+
expect(response.dateFormat).toMatch(/^\w{2}-\w{2}-\w{4}$/);
|
|
44
|
+
expect(typeof response.timeZone).toBe("string");
|
|
45
|
+
expect(typeof response.hasEveryoneOnTheInternet).toBe("boolean");
|
|
46
|
+
expect(
|
|
47
|
+
response.dataClassification === undefined ||
|
|
48
|
+
typeof response.dataClassification === "string"
|
|
49
|
+
).toBe(true);
|
|
50
|
+
expect(
|
|
51
|
+
response.variables === undefined || Array.isArray(response.variables)
|
|
52
|
+
).toBe(true);
|
|
53
|
+
|
|
54
|
+
// Cleanup: Delete the app after assertions
|
|
55
|
+
const deleteBody = { name: appName };
|
|
56
|
+
await client.deleteApp({ appId: response.id, body: deleteBody });
|
|
57
|
+
console.log("Cleanup: App deleted:", response.id);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
const createDuration = Date.now() - createStartTime;
|
|
60
|
+
console.error(`createApp failed after ${createDuration}ms:`, error);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// tests/vitest/qb/apps/deleteApp.test.ts
|
|
2
|
+
import { test, expect } from "vitest";
|
|
3
|
+
import { quickbase } from "../../../../src/quickbaseClient";
|
|
4
|
+
|
|
5
|
+
test(
|
|
6
|
+
"QuickbaseClient Integration - deleteApp > deletes an app in QuickBase",
|
|
7
|
+
{ timeout: 30000 },
|
|
8
|
+
async () => {
|
|
9
|
+
const config = {
|
|
10
|
+
realm: process.env.QB_REALM || "",
|
|
11
|
+
userToken: process.env.QB_USER_TOKEN || "",
|
|
12
|
+
debug: true,
|
|
13
|
+
useTempTokens: false,
|
|
14
|
+
};
|
|
15
|
+
const client = quickbase(config);
|
|
16
|
+
|
|
17
|
+
const appName = `TempApp_${Date.now()}`;
|
|
18
|
+
const createBody = {
|
|
19
|
+
name: appName,
|
|
20
|
+
description: "Temporary app for deleteApp test",
|
|
21
|
+
assignToken: true, // Match createApp.test.ts
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
console.log("Config used:", {
|
|
25
|
+
...config,
|
|
26
|
+
userToken: config.userToken ? "[REDACTED]" : "",
|
|
27
|
+
});
|
|
28
|
+
console.log("Creating temporary app with:", createBody);
|
|
29
|
+
const createStartTime = Date.now();
|
|
30
|
+
let createResponse;
|
|
31
|
+
try {
|
|
32
|
+
createResponse = await client.createApp({ body: createBody });
|
|
33
|
+
const createDuration = Date.now() - createStartTime;
|
|
34
|
+
console.log(`createApp completed in ${createDuration}ms`);
|
|
35
|
+
console.log("Created app response:", createResponse);
|
|
36
|
+
|
|
37
|
+
expect(createResponse).toBeDefined();
|
|
38
|
+
expect(createResponse.id).toBeDefined();
|
|
39
|
+
expect(createResponse.name).toBe(appName);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
const createDuration = Date.now() - createStartTime;
|
|
42
|
+
console.error(`createApp failed after ${createDuration}ms:`, error);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const deleteBody = {
|
|
47
|
+
name: appName,
|
|
48
|
+
};
|
|
49
|
+
console.log("Deleting app with:", deleteBody);
|
|
50
|
+
const deleteStartTime = Date.now();
|
|
51
|
+
try {
|
|
52
|
+
const deleteResponse = await client.deleteApp({
|
|
53
|
+
appId: createResponse.id,
|
|
54
|
+
body: deleteBody,
|
|
55
|
+
});
|
|
56
|
+
const deleteDuration = Date.now() - deleteStartTime;
|
|
57
|
+
console.log(`deleteApp completed in ${deleteDuration}ms`);
|
|
58
|
+
console.log("Delete app response:", deleteResponse);
|
|
59
|
+
|
|
60
|
+
expect(deleteResponse).toBeDefined();
|
|
61
|
+
expect(deleteResponse.deletedAppId).toBe(createResponse.id);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const deleteDuration = Date.now() - deleteStartTime;
|
|
64
|
+
console.error(`deleteApp failed after ${deleteDuration}ms:`, error);
|
|
65
|
+
if (error instanceof Error && "response" in error) {
|
|
66
|
+
try {
|
|
67
|
+
const errorBody = await (error as any).response.json();
|
|
68
|
+
console.error("Full deleteApp error response:", errorBody);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error("Could not parse error response:", e);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
);
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// tests/vitest/qb/records/deleteRecords.test.ts
|
|
2
2
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
3
3
|
import { quickbase } from "@/quickbaseClient.ts";
|
|
4
|
+
import {
|
|
5
|
+
Upsert200Response,
|
|
6
|
+
Upsert207Response,
|
|
7
|
+
} from "/home/drew/Projects/quickbase-js/src/generated/models";
|
|
4
8
|
|
|
5
9
|
const TABLE_ID = "buwai2zws";
|
|
6
10
|
const UNIQUE_FIELD_ID = 3; // Record ID field
|
|
@@ -40,16 +44,44 @@ describe("QuickbaseClient - DeleteRecords Integration", () => {
|
|
|
40
44
|
test(
|
|
41
45
|
"deletes a newly upserted record",
|
|
42
46
|
async () => {
|
|
43
|
-
const upsertResponse
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
const upsertResponse: Upsert200Response | Upsert207Response =
|
|
48
|
+
await qb.upsert({
|
|
49
|
+
body: {
|
|
50
|
+
to: TABLE_ID,
|
|
51
|
+
data: [{ [MARKER_FIELD_ID]: { value: "test-marker-delete" } }],
|
|
52
|
+
fieldsToReturn: [UNIQUE_FIELD_ID], // Return record ID
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Check metadata and createdRecordIds
|
|
57
|
+
expect(
|
|
58
|
+
upsertResponse.metadata,
|
|
59
|
+
"Expected metadata to be defined"
|
|
60
|
+
).toBeDefined();
|
|
61
|
+
expect(
|
|
62
|
+
upsertResponse.metadata?.createdRecordIds,
|
|
63
|
+
"Expected createdRecordIds to be defined"
|
|
64
|
+
).toBeDefined();
|
|
65
|
+
|
|
66
|
+
const createdRecordIds = upsertResponse.metadata?.createdRecordIds;
|
|
67
|
+
if (!createdRecordIds || createdRecordIds.length === 0) {
|
|
68
|
+
throw new Error("Expected at least one created record ID");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const recordId = createdRecordIds[0];
|
|
72
|
+
expect(recordId, "Expected recordId to be defined").toBeDefined();
|
|
73
|
+
|
|
74
|
+
// Check data
|
|
75
|
+
expect(upsertResponse.data, "Expected data to be defined").toBeDefined();
|
|
76
|
+
if (!upsertResponse.data || upsertResponse.data.length === 0) {
|
|
77
|
+
throw new Error("Expected at least one data entry");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const recordData = upsertResponse.data[0];
|
|
81
|
+
expect(
|
|
82
|
+
recordData[UNIQUE_FIELD_ID]?.value,
|
|
83
|
+
"Expected record ID in data to match created record ID"
|
|
84
|
+
).toBe(recordId);
|
|
53
85
|
|
|
54
86
|
const deleteResponse = await qb.deleteRecords({
|
|
55
87
|
body: { from: TABLE_ID, where: `{${UNIQUE_FIELD_ID}.EX.${recordId}}` },
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
// tests/vitest/qb/records/runQuery.test.ts
|
|
2
2
|
import { describe, it, expect } from "vitest";
|
|
3
|
-
import { createClient, QB_TABLE_ID_1 } from "../../../setup.ts";
|
|
4
|
-
import {
|
|
3
|
+
import { createClient, QB_TABLE_ID_1 } from "../../../setup.ts";
|
|
4
|
+
import {
|
|
5
|
+
RunQueryRequest,
|
|
6
|
+
RunQueryResponse,
|
|
7
|
+
Upsert200Response,
|
|
8
|
+
Upsert207Response,
|
|
9
|
+
} from "@/generated/models";
|
|
10
|
+
|
|
11
|
+
// Define a type for field data with a value property (matches generated Record type)
|
|
12
|
+
interface FieldData {
|
|
13
|
+
value: any; // Adjust type based on your needs (e.g., string | number)
|
|
14
|
+
}
|
|
5
15
|
|
|
6
16
|
describe("QuickbaseClient Integration - runQuery", () => {
|
|
7
17
|
let createdRecordId: number;
|
|
@@ -12,18 +22,39 @@ describe("QuickbaseClient Integration - runQuery", () => {
|
|
|
12
22
|
const client = createClient();
|
|
13
23
|
|
|
14
24
|
// Upsert a test record to query later
|
|
15
|
-
const upsertResponse
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const upsertResponse: Upsert200Response | Upsert207Response =
|
|
26
|
+
await client.upsert({
|
|
27
|
+
body: {
|
|
28
|
+
to: QB_TABLE_ID_1,
|
|
29
|
+
data: [
|
|
30
|
+
{
|
|
31
|
+
"6": { value: "Test Task " + Date.now() }, // Assuming field 6 is a text field
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
fieldsToReturn: [3, 6], // Record ID# and the text field
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Check metadata and createdRecordIds with null safety
|
|
39
|
+
expect(
|
|
40
|
+
upsertResponse.metadata,
|
|
41
|
+
"Expected metadata to be defined"
|
|
42
|
+
).toBeDefined();
|
|
43
|
+
expect(
|
|
44
|
+
upsertResponse.metadata?.createdRecordIds,
|
|
45
|
+
"Expected createdRecordIds to be defined"
|
|
46
|
+
).toBeDefined();
|
|
47
|
+
|
|
48
|
+
const createdRecordIds = upsertResponse.metadata?.createdRecordIds;
|
|
49
|
+
if (!createdRecordIds || createdRecordIds.length === 0) {
|
|
50
|
+
throw new Error("Expected at least one created record ID from upsert");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
createdRecordId = createdRecordIds[0];
|
|
54
|
+
expect(
|
|
55
|
+
createdRecordId,
|
|
56
|
+
"Expected createdRecordId to be defined"
|
|
57
|
+
).toBeDefined();
|
|
27
58
|
console.log("Created test record with ID:", createdRecordId);
|
|
28
59
|
|
|
29
60
|
// Query the record
|
|
@@ -46,10 +77,35 @@ describe("QuickbaseClient Integration - runQuery", () => {
|
|
|
46
77
|
});
|
|
47
78
|
console.log("Real API response:", response);
|
|
48
79
|
|
|
49
|
-
// Assertions
|
|
80
|
+
// Assertions with safety checks and type assertion
|
|
81
|
+
expect(
|
|
82
|
+
response.data,
|
|
83
|
+
"Expected response.data to be defined"
|
|
84
|
+
).toBeDefined();
|
|
85
|
+
if (!response.data || response.data.length === 0) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"Expected response.data to contain at least one record"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
50
91
|
expect(response.data).toHaveLength(1);
|
|
51
|
-
|
|
52
|
-
expect(
|
|
92
|
+
const record = response.data[0] as { [key: string]: FieldData };
|
|
93
|
+
expect(
|
|
94
|
+
record["3"]?.value,
|
|
95
|
+
"Expected field 3 to match createdRecordId"
|
|
96
|
+
).toBe(createdRecordId);
|
|
97
|
+
expect(
|
|
98
|
+
record["6"]?.value,
|
|
99
|
+
"Expected field 6 to contain 'Test Task'"
|
|
100
|
+
).toContain("Test Task");
|
|
101
|
+
|
|
102
|
+
expect(
|
|
103
|
+
response.fields,
|
|
104
|
+
"Expected response.fields to be defined"
|
|
105
|
+
).toBeDefined();
|
|
106
|
+
if (!response.fields) {
|
|
107
|
+
throw new Error("Expected response.fields to contain field metadata");
|
|
108
|
+
}
|
|
53
109
|
expect(response.fields).toContainEqual({
|
|
54
110
|
id: 3,
|
|
55
111
|
label: expect.any(String),
|
|
@@ -60,6 +116,14 @@ describe("QuickbaseClient Integration - runQuery", () => {
|
|
|
60
116
|
label: expect.any(String),
|
|
61
117
|
type: expect.any(String),
|
|
62
118
|
});
|
|
119
|
+
|
|
120
|
+
expect(
|
|
121
|
+
response.metadata,
|
|
122
|
+
"Expected response.metadata to be defined"
|
|
123
|
+
).toBeDefined();
|
|
124
|
+
if (!response.metadata) {
|
|
125
|
+
throw new Error("Expected response.metadata to be present");
|
|
126
|
+
}
|
|
63
127
|
expect(response.metadata).toMatchObject({
|
|
64
128
|
numFields: 2,
|
|
65
129
|
numRecords: 1,
|