quickbase-js 0.1.0-beta.8 → 0.1.0-beta.9
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 +29 -0
- package/README.md +46 -130
- package/dist/esm/quickbase.d.ts +822 -119
- package/dist/esm/quickbase.js +373 -236
- package/dist/esm/quickbase.js.map +1 -1
- package/dist/esm/quickbase.min.js +1 -1
- package/dist/esm/quickbase.min.js.map +1 -1
- package/dist/umd/quickbase.umd.js +373 -236
- package/dist/umd/quickbase.umd.js.map +1 -1
- package/dist/umd/quickbase.umd.min.js +1 -1
- package/dist/umd/quickbase.umd.min.js.map +1 -1
- package/open-api/generate-unified-interface.ts +97 -17
- package/open-api/utils/generateJsDoc.ts +96 -0
- package/package.json +3 -1
- package/src/ThrottleBucket.ts +17 -12
- package/src/authorizationStrategy.ts +246 -0
- package/src/generated-unified/QuickbaseClient.ts +809 -119
- package/src/invokeMethod.ts +150 -265
- package/src/quickbaseClient.ts +85 -58
- package/src/rateLimiter.ts +25 -0
- package/src/tokenCache.ts +10 -26
- package/tests/vitest/qb/auth/mockQuickbaseServer.ts +60 -0
- package/tests/vitest/qb/auth/retryUserToken401.test.ts +59 -42
- package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +65 -0
- package/tests/vitest/qb/fields/getFieldUsage.test.ts +58 -150
- package/tests/vitest/qb/formulas/runFormula.test.ts +96 -0
- package/tests/vitest/qb/tables/createRelationship.test.ts +102 -0
- package/tests/vitest/qb/tables/deleteRelationship.test.ts +143 -0
- package/tests/vitest/qb/tables/updateRelationship.test.ts +118 -145
- package/tests/vitest/unit/ThrottleBucket.test.ts +95 -0
- package/tests/vitest/unit/apps/copyApp.test.ts +133 -40
- package/tests/vitest/unit/apps/updateApp.test.ts +42 -11
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +3 -2
- package/tests/vitest/unit/auth/getTempToken.test.ts +42 -18
- package/tests/vitest/unit/auth/retryTempToken401.test.ts +46 -64
- package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +235 -0
- package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -43
- package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +325 -0
- package/tests/vitest/unit/fields/createField.test.ts +180 -116
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +17 -3
- package/tests/vitest/unit/fields/updateField.test.ts +12 -7
- package/tests/vitest/unit/formulas/runFormula.test.ts +205 -0
- package/tests/vitest/unit/index.ts +2 -1
- package/tests/vitest/unit/inferHttpMethod.test.ts +131 -0
- package/tests/vitest/unit/rateLimit.test.ts +4 -2
- package/tests/vitest/unit/tables/createRelationship.test.ts +212 -0
- package/tests/vitest/unit/tables/deleteRelationship.test.ts +97 -0
- package/tests/vitest/unit/tables/updateRelationship.test.ts +12 -10
- package/tests/vitest/unit/tokenLifespan.test.ts +149 -83
- package/tools/log-gen.ts +14 -5
- package/tools/log-gen.yaml +710 -268
- package/tests/vitest/unit/auth/retryTempTokenTwo401.test.ts +0 -81
|
@@ -1,166 +1,72 @@
|
|
|
1
|
-
// tests/vitest/qb/fields/
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
QB_REALM,
|
|
6
|
-
QB_USER_TOKEN,
|
|
7
|
-
QB_TABLE_ID_1,
|
|
8
|
-
} from "../../../setup.ts";
|
|
1
|
+
// tests/vitest/qb/fields/getFieldsUsage.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect } from "vitest";
|
|
4
|
+
import { createClient, QB_TABLE_ID_1 } from "../../../setup.ts";
|
|
9
5
|
import { FieldUsage } from "@/generated/models";
|
|
10
6
|
|
|
11
|
-
describe("QuickbaseClient Integration -
|
|
7
|
+
describe("QuickbaseClient Integration - getFieldsUsage", () => {
|
|
12
8
|
const client = createClient();
|
|
13
|
-
const createdFieldIds: number[] = [];
|
|
14
|
-
|
|
15
|
-
afterAll(async () => {
|
|
16
|
-
if (createdFieldIds.length > 0) {
|
|
17
|
-
try {
|
|
18
|
-
const response = await client.deleteFields({
|
|
19
|
-
tableId: QB_TABLE_ID_1,
|
|
20
|
-
body: { fieldIds: createdFieldIds },
|
|
21
|
-
});
|
|
22
|
-
console.log(
|
|
23
|
-
`Cleaned up fields: ${JSON.stringify(response.deletedFieldIds)}`,
|
|
24
|
-
response.errors && response.errors.length > 0
|
|
25
|
-
? `Errors: ${response.errors}`
|
|
26
|
-
: ""
|
|
27
|
-
);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error("Cleanup failed:", error);
|
|
30
|
-
console.log(`Fields left in table: ${createdFieldIds}`);
|
|
31
|
-
}
|
|
32
|
-
} else {
|
|
33
|
-
console.log("No fields to clean up.");
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it(
|
|
38
|
-
"creates a field, retrieves its usage statistics, and deletes it",
|
|
39
|
-
async () => {
|
|
40
|
-
// Step 1: Create a field
|
|
41
|
-
const uniqueLabel = `TestField_${Date.now()}`;
|
|
42
|
-
console.log("Creating field with label:", uniqueLabel);
|
|
43
|
-
const createResponse = await client.createField({
|
|
44
|
-
tableId: QB_TABLE_ID_1,
|
|
45
|
-
body: {
|
|
46
|
-
label: uniqueLabel,
|
|
47
|
-
fieldType: "text",
|
|
48
|
-
fieldHelp: "Created for getFieldUsage integration test",
|
|
49
|
-
addToForms: true,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
expect(createResponse.id).toBeDefined();
|
|
54
|
-
const fieldId = createResponse.id;
|
|
55
|
-
createdFieldIds.push(fieldId);
|
|
56
|
-
console.log(`Created field ${uniqueLabel} with ID ${fieldId}`);
|
|
57
|
-
|
|
58
|
-
// Brief delay to ensure field is registered
|
|
59
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
60
|
-
|
|
61
|
-
// Step 2: Get field usage with raw response logging
|
|
62
|
-
console.log("Fetching field usage for:", {
|
|
63
|
-
tableId: QB_TABLE_ID_1,
|
|
64
|
-
fieldId,
|
|
65
|
-
});
|
|
66
|
-
const rawResponse = await fetch(
|
|
67
|
-
`https://api.quickbase.com/v1/fields/usage/${fieldId}?tableId=${QB_TABLE_ID_1}`,
|
|
68
|
-
{
|
|
69
|
-
method: "GET",
|
|
70
|
-
headers: {
|
|
71
|
-
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
72
|
-
Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
|
|
73
|
-
"Content-Type": "application/json",
|
|
74
|
-
},
|
|
75
|
-
}
|
|
76
|
-
);
|
|
77
|
-
const rawBody = await rawResponse.text();
|
|
78
|
-
console.log("Raw API response:", {
|
|
79
|
-
status: rawResponse.status,
|
|
80
|
-
statusText: rawResponse.statusText,
|
|
81
|
-
body: rawBody,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
const responseArray: FieldUsage[] = await client.getFieldUsage({
|
|
85
|
-
fieldId,
|
|
86
|
-
tableId: QB_TABLE_ID_1,
|
|
87
|
-
});
|
|
88
|
-
console.log("Parsed API response (array):", responseArray);
|
|
89
9
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
expect(response).toBeDefined();
|
|
96
|
-
expect(response.field).toHaveProperty("id", fieldId);
|
|
97
|
-
expect(response.field).toHaveProperty("name", uniqueLabel);
|
|
98
|
-
expect(response.field).toHaveProperty("type", "text");
|
|
99
|
-
expect(typeof response.field.name).toBe("string");
|
|
100
|
-
expect(typeof response.field.type).toBe("string");
|
|
10
|
+
it("retrieves field usage statistics for a valid table", async () => {
|
|
11
|
+
console.log("Starting retrieval of full field usage list");
|
|
12
|
+
const response = await client.getFieldsUsage({
|
|
13
|
+
tableId: QB_TABLE_ID_1,
|
|
14
|
+
});
|
|
101
15
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
expect(
|
|
106
|
-
expect(
|
|
107
|
-
expect(
|
|
108
|
-
expect(
|
|
109
|
-
expect(
|
|
110
|
-
expect(
|
|
111
|
-
expect(
|
|
112
|
-
expect(
|
|
113
|
-
expect(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
expect(response.usage).toHaveProperty("roles");
|
|
120
|
-
expect(response.usage).toHaveProperty("tableImports");
|
|
121
|
-
expect(response.usage).toHaveProperty("tableRules");
|
|
122
|
-
expect(response.usage).toHaveProperty("webhooks");
|
|
16
|
+
expect(response).toBeInstanceOf(Array);
|
|
17
|
+
expect(response.length).toBeGreaterThan(0); // Assumes the table has fields
|
|
18
|
+
response.forEach((usage: FieldUsage) => {
|
|
19
|
+
expect(usage.field).toHaveProperty("id");
|
|
20
|
+
expect(usage.field).toHaveProperty("name");
|
|
21
|
+
expect(usage.field).toHaveProperty("type");
|
|
22
|
+
expect(usage.usage).toHaveProperty("actions");
|
|
23
|
+
expect(usage.usage.actions).toHaveProperty("count");
|
|
24
|
+
expect(typeof usage.usage.actions.count).toBe("number");
|
|
25
|
+
expect(usage.usage).toHaveProperty("dashboards");
|
|
26
|
+
expect(usage.usage).toHaveProperty("forms");
|
|
27
|
+
expect(usage.usage).toHaveProperty("reports");
|
|
28
|
+
});
|
|
29
|
+
console.log(
|
|
30
|
+
`Retrieved ${response.length} field usage entries for table ${QB_TABLE_ID_1}`
|
|
31
|
+
);
|
|
32
|
+
}, 15000); // 15-second timeout
|
|
123
33
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
34
|
+
it("retrieves field usage with skip and top parameters", async () => {
|
|
35
|
+
console.log(
|
|
36
|
+
"Starting retrieval of full field usage list for skip/top test"
|
|
37
|
+
);
|
|
38
|
+
const fullResponse = await client.getFieldsUsage({
|
|
39
|
+
tableId: QB_TABLE_ID_1,
|
|
40
|
+
});
|
|
41
|
+
console.log(`Full list retrieved: ${fullResponse.length} fields`);
|
|
129
42
|
|
|
130
|
-
|
|
131
|
-
|
|
43
|
+
const totalFields = fullResponse.length;
|
|
44
|
+
if (totalFields <= 1) {
|
|
45
|
+
console.log("Skipping skip/top test: table has too few fields");
|
|
46
|
+
return; // Skip if not enough fields to test pagination
|
|
47
|
+
}
|
|
132
48
|
|
|
133
|
-
console.log("
|
|
49
|
+
console.log("Starting retrieval with skip=1 and top=1");
|
|
50
|
+
const response = await client.getFieldsUsage({
|
|
134
51
|
tableId: QB_TABLE_ID_1,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
await expect(
|
|
138
|
-
client.getFieldUsage({
|
|
139
|
-
fieldId: nonExistentFieldId,
|
|
140
|
-
tableId: QB_TABLE_ID_1,
|
|
141
|
-
})
|
|
142
|
-
).rejects.toMatchObject({
|
|
143
|
-
message: expect.stringContaining(
|
|
144
|
-
"API Error: No such field (Status: 404)"
|
|
145
|
-
),
|
|
52
|
+
skip: 1,
|
|
53
|
+
top: 1,
|
|
146
54
|
});
|
|
147
55
|
|
|
56
|
+
expect(response).toBeInstanceOf(Array);
|
|
57
|
+
expect(response.length).toBe(1); // Should return exactly 1 field
|
|
58
|
+
expect(response[0].field.id).not.toBe(fullResponse[0].field.id); // Should skip the first field
|
|
148
59
|
console.log(
|
|
149
|
-
`
|
|
60
|
+
`Retrieved ${response.length} field usage entries with skip=1 and top=1`
|
|
150
61
|
);
|
|
151
|
-
});
|
|
62
|
+
}, 15000); // 15-second timeout
|
|
152
63
|
|
|
153
|
-
it("fails with invalid table ID",
|
|
64
|
+
it("fails with invalid table ID", async () => {
|
|
154
65
|
const invalidTableId = "invalid_dbid";
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
console.log("Attempting to fetch usage with invalid table ID:", {
|
|
158
|
-
tableId: invalidTableId,
|
|
159
|
-
fieldId,
|
|
160
|
-
});
|
|
66
|
+
console.log(`Starting retrieval with invalid table ID ${invalidTableId}`);
|
|
67
|
+
const startTime = Date.now();
|
|
161
68
|
await expect(
|
|
162
|
-
client.
|
|
163
|
-
fieldId,
|
|
69
|
+
client.getFieldsUsage({
|
|
164
70
|
tableId: invalidTableId,
|
|
165
71
|
})
|
|
166
72
|
).rejects.toMatchObject({
|
|
@@ -168,7 +74,9 @@ describe("QuickbaseClient Integration - getFieldUsage", () => {
|
|
|
168
74
|
"API Error: Access denied (Status: 401)"
|
|
169
75
|
),
|
|
170
76
|
});
|
|
171
|
-
|
|
172
|
-
console.log(
|
|
173
|
-
|
|
77
|
+
const duration = Date.now() - startTime;
|
|
78
|
+
console.log(
|
|
79
|
+
`Confirmed 401 for invalid table ID ${invalidTableId} in ${duration}ms`
|
|
80
|
+
);
|
|
81
|
+
}, 30000); // Increased to 30-second timeout
|
|
174
82
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// tests/vitest/qb/formulas/runFormula.test.ts
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
createClient,
|
|
6
|
+
QB_REALM,
|
|
7
|
+
QB_USER_TOKEN,
|
|
8
|
+
QB_TABLE_ID_1,
|
|
9
|
+
} from "@tests/setup.ts";
|
|
10
|
+
import {
|
|
11
|
+
RunFormulaRequest,
|
|
12
|
+
RunFormula200Response,
|
|
13
|
+
Upsert200Response,
|
|
14
|
+
} from "@/generated/models";
|
|
15
|
+
|
|
16
|
+
describe("QuickbaseClient Integration - runFormula", () => {
|
|
17
|
+
let client: ReturnType<typeof createClient>;
|
|
18
|
+
let testRecordId: number;
|
|
19
|
+
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
// Validate environment variables
|
|
22
|
+
if (!QB_REALM) throw new Error("QB_REALM is not defined in .env");
|
|
23
|
+
if (!QB_USER_TOKEN) throw new Error("QB_USER_TOKEN is not defined in .env");
|
|
24
|
+
if (!QB_TABLE_ID_1) throw new Error("QB_TABLE_ID_1 is not defined in .env");
|
|
25
|
+
|
|
26
|
+
// Initialize client with user token only
|
|
27
|
+
client = createClient(undefined, {
|
|
28
|
+
realm: QB_REALM,
|
|
29
|
+
userToken: QB_USER_TOKEN,
|
|
30
|
+
debug: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Upsert a test record to ensure we have a valid rid
|
|
34
|
+
const upsertResponse: Upsert200Response = await client.upsert({
|
|
35
|
+
body: {
|
|
36
|
+
to: QB_TABLE_ID_1,
|
|
37
|
+
data: [
|
|
38
|
+
{
|
|
39
|
+
"6": { value: `Test Record ${Date.now()}` }, // Assuming field 6 is a text field (e.g., "Task Name")
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
fieldsToReturn: [3], // Return Record ID# (field ID 3)
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Extract the created record ID
|
|
47
|
+
testRecordId = upsertResponse.data[0]["3"].value as number;
|
|
48
|
+
console.log(`Upserted test record with ID: ${testRecordId}`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
// Clean up the test record
|
|
53
|
+
if (testRecordId) {
|
|
54
|
+
await client.deleteRecords({
|
|
55
|
+
body: {
|
|
56
|
+
from: QB_TABLE_ID_1,
|
|
57
|
+
where: `{3.EX.${testRecordId}}`, // Delete by Record ID#
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
console.log(`Cleaned up test record with ID: ${testRecordId}`);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("runs simple formula without rid and returns correct result", async () => {
|
|
65
|
+
const request: RunFormulaRequest = {
|
|
66
|
+
from: QB_TABLE_ID_1,
|
|
67
|
+
formula: "1 + 1", // Simple arithmetic to verify functionality
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const response: RunFormula200Response = await client.runFormula({
|
|
71
|
+
body: request,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
console.log("Real API response (no rid):", response);
|
|
75
|
+
|
|
76
|
+
expect(response).toHaveProperty("result");
|
|
77
|
+
expect(response.result).toBe("2"); // Expect "2" as a string
|
|
78
|
+
}, 10000);
|
|
79
|
+
|
|
80
|
+
it("runs formula with rid and field ID 3, returns correct numeric result", async () => {
|
|
81
|
+
const request: RunFormulaRequest = {
|
|
82
|
+
from: QB_TABLE_ID_1,
|
|
83
|
+
formula: "Sum([Record ID#], 20)", // Field ID 3 is "Record ID#"
|
|
84
|
+
rid: testRecordId, // Use the upserted record ID
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const response: RunFormula200Response = await client.runFormula({
|
|
88
|
+
body: request,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
console.log("Real API response (with rid):", response);
|
|
92
|
+
|
|
93
|
+
expect(response).toHaveProperty("result");
|
|
94
|
+
expect(response.result).toBe(String(testRecordId + 20)); // Dynamic result based on the record ID
|
|
95
|
+
}, 10000);
|
|
96
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// tests/vitest/qb/tables/createRelationship.test.ts
|
|
2
|
+
|
|
3
|
+
import { test, expect } from "vitest";
|
|
4
|
+
import { quickbase } from "/home/drew/Projects/quickbase-js/src/quickbaseClient";
|
|
5
|
+
import {
|
|
6
|
+
QB_REALM,
|
|
7
|
+
QB_USER_TOKEN,
|
|
8
|
+
QB_TABLE_ID_1,
|
|
9
|
+
QB_TABLE_ID_2,
|
|
10
|
+
} from "/home/drew/Projects/quickbase-js/tests/setup.ts";
|
|
11
|
+
|
|
12
|
+
test(
|
|
13
|
+
"QuickbaseClient Integration - createRelationship > creates a new relationship in QuickBase",
|
|
14
|
+
{ timeout: 60000 }, // 60 seconds for create and delete
|
|
15
|
+
async () => {
|
|
16
|
+
// Validate environment variables
|
|
17
|
+
if (!QB_REALM) throw new Error("QB_REALM is not defined in .env");
|
|
18
|
+
if (!QB_USER_TOKEN) throw new Error("QB_USER_TOKEN is not defined in .env");
|
|
19
|
+
if (!QB_TABLE_ID_1) throw new Error("QB_TABLE_ID_1 is not defined in .env");
|
|
20
|
+
if (!QB_TABLE_ID_2) throw new Error("QB_TABLE_ID_2 is not defined in .env");
|
|
21
|
+
|
|
22
|
+
// Initialize client
|
|
23
|
+
const client = quickbase({
|
|
24
|
+
realm: QB_REALM,
|
|
25
|
+
userToken: QB_USER_TOKEN,
|
|
26
|
+
debug: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Define the relationship creation request
|
|
30
|
+
const uniqueLabel = `TestRelationship_${Date.now()}`;
|
|
31
|
+
const requestBody = {
|
|
32
|
+
parentTableId: QB_TABLE_ID_1,
|
|
33
|
+
foreignKeyField: { label: uniqueLabel },
|
|
34
|
+
lookupFieldIds: [6], // Assuming field 6 exists in QB_TABLE_ID_1
|
|
35
|
+
summaryFields: [
|
|
36
|
+
{
|
|
37
|
+
summaryFid: 6, // Assuming field 6 in QB_TABLE_ID_2 is numeric
|
|
38
|
+
label: `Sum_${uniqueLabel}`,
|
|
39
|
+
accumulationType: "SUM",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
console.log("Config used:", {
|
|
45
|
+
realm: QB_REALM,
|
|
46
|
+
userToken: "[REDACTED]",
|
|
47
|
+
parentTableId: QB_TABLE_ID_1,
|
|
48
|
+
childTableId: QB_TABLE_ID_2,
|
|
49
|
+
});
|
|
50
|
+
console.log("Creating relationship with:", requestBody);
|
|
51
|
+
|
|
52
|
+
// Create the relationship
|
|
53
|
+
const response = await client.createRelationship({
|
|
54
|
+
tableId: QB_TABLE_ID_2,
|
|
55
|
+
body: requestBody,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log("Real API response:", JSON.stringify(response, null, 2));
|
|
59
|
+
|
|
60
|
+
// Assertions for creation
|
|
61
|
+
expect(response).toBeDefined();
|
|
62
|
+
expect(response.id).toBeDefined();
|
|
63
|
+
expect(response.parentTableId).toBe(QB_TABLE_ID_1);
|
|
64
|
+
expect(response.childTableId).toBe(QB_TABLE_ID_2);
|
|
65
|
+
expect(response.foreignKeyField).toBeDefined();
|
|
66
|
+
expect(response.foreignKeyField?.label).toBe(uniqueLabel);
|
|
67
|
+
expect(response.lookupFields).toHaveLength(1);
|
|
68
|
+
expect(response.lookupFields[0].id).toBeGreaterThan(0);
|
|
69
|
+
expect(response.summaryFields).toHaveLength(1);
|
|
70
|
+
expect(response.summaryFields[0].label).toBe(`Sum_${uniqueLabel}`);
|
|
71
|
+
|
|
72
|
+
// Cleanup: Delete the relationship
|
|
73
|
+
const relationshipId = response.id;
|
|
74
|
+
console.log("Deleting relationship with ID:", relationshipId);
|
|
75
|
+
const deleteResponse = await client.deleteRelationship({
|
|
76
|
+
tableId: QB_TABLE_ID_2,
|
|
77
|
+
relationshipId,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log(
|
|
81
|
+
"Delete relationship response:",
|
|
82
|
+
JSON.stringify(deleteResponse, null, 2)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Verify deletion
|
|
86
|
+
expect(deleteResponse).toBeDefined();
|
|
87
|
+
expect(deleteResponse.relationshipId).toBe(relationshipId);
|
|
88
|
+
|
|
89
|
+
// Confirm relationship is gone
|
|
90
|
+
const relationships = await client.getRelationships({
|
|
91
|
+
tableId: QB_TABLE_ID_2,
|
|
92
|
+
});
|
|
93
|
+
console.log(
|
|
94
|
+
"Relationships after deletion:",
|
|
95
|
+
JSON.stringify(relationships, null, 2)
|
|
96
|
+
);
|
|
97
|
+
const deletedRelationship = relationships.relationships.find(
|
|
98
|
+
(r) => r.id === relationshipId
|
|
99
|
+
);
|
|
100
|
+
expect(deletedRelationship).toBeUndefined(); // Should no longer exist
|
|
101
|
+
}
|
|
102
|
+
);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// tests/vitest/qb/tables/deleteRelationship.test.ts
|
|
2
|
+
|
|
3
|
+
import { test, expect } from "vitest";
|
|
4
|
+
import { quickbase } from "/home/drew/Projects/quickbase-js/src/quickbaseClient";
|
|
5
|
+
import {
|
|
6
|
+
QB_REALM,
|
|
7
|
+
QB_USER_TOKEN,
|
|
8
|
+
QB_TABLE_ID_1,
|
|
9
|
+
QB_TABLE_ID_2,
|
|
10
|
+
} from "/home/drew/Projects/quickbase-js/tests/setup.ts";
|
|
11
|
+
|
|
12
|
+
test(
|
|
13
|
+
"QuickbaseClient Integration - deleteRelationship > creates and deletes a relationship in QuickBase",
|
|
14
|
+
{ timeout: 60000 }, // 60 seconds for two API calls
|
|
15
|
+
async () => {
|
|
16
|
+
// Validate environment variables
|
|
17
|
+
if (!QB_REALM) throw new Error("QB_REALM is not defined in .env");
|
|
18
|
+
if (!QB_USER_TOKEN) throw new Error("QB_USER_TOKEN is not defined in .env");
|
|
19
|
+
if (!QB_TABLE_ID_1) throw new Error("QB_TABLE_ID_1 is not defined in .env");
|
|
20
|
+
if (!QB_TABLE_ID_2) throw new Error("QB_TABLE_ID_2 is not defined in .env");
|
|
21
|
+
|
|
22
|
+
// Initialize client
|
|
23
|
+
const client = quickbase({
|
|
24
|
+
realm: QB_REALM,
|
|
25
|
+
userToken: QB_USER_TOKEN,
|
|
26
|
+
debug: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Step 1: Create a relationship
|
|
30
|
+
const uniqueLabel = `TestRelationship_${Date.now()}`;
|
|
31
|
+
const createRequestBody = {
|
|
32
|
+
parentTableId: QB_TABLE_ID_1,
|
|
33
|
+
foreignKeyField: { label: uniqueLabel },
|
|
34
|
+
lookupFieldIds: [6], // Assuming field 6 exists in QB_TABLE_ID_1
|
|
35
|
+
summaryFields: [
|
|
36
|
+
{
|
|
37
|
+
summaryFid: 6, // Assuming field 6 in QB_TABLE_ID_2 is numeric
|
|
38
|
+
label: `Sum_${uniqueLabel}`,
|
|
39
|
+
accumulationType: "SUM",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
console.log("Config used:", {
|
|
45
|
+
realm: QB_REALM,
|
|
46
|
+
userToken: "[REDACTED]",
|
|
47
|
+
parentTableId: QB_TABLE_ID_1,
|
|
48
|
+
childTableId: QB_TABLE_ID_2,
|
|
49
|
+
});
|
|
50
|
+
console.log("Creating relationship with:", createRequestBody);
|
|
51
|
+
|
|
52
|
+
const createResponse = await client.createRelationship({
|
|
53
|
+
tableId: QB_TABLE_ID_2,
|
|
54
|
+
body: createRequestBody,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log(
|
|
58
|
+
"Created relationship response:",
|
|
59
|
+
JSON.stringify(createResponse, null, 2)
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Verify creation
|
|
63
|
+
expect(createResponse).toBeDefined();
|
|
64
|
+
expect(createResponse.id).toBeDefined();
|
|
65
|
+
expect(createResponse.parentTableId).toBe(QB_TABLE_ID_1);
|
|
66
|
+
expect(createResponse.childTableId).toBe(QB_TABLE_ID_2);
|
|
67
|
+
expect(createResponse.foreignKeyField).toBeDefined();
|
|
68
|
+
expect(createResponse.foreignKeyField?.label).toBe(uniqueLabel);
|
|
69
|
+
|
|
70
|
+
const relationshipId = createResponse.id;
|
|
71
|
+
const foreignKeyFieldId = createResponse.foreignKeyField?.id;
|
|
72
|
+
|
|
73
|
+
// Step 2: Delete the relationship
|
|
74
|
+
console.log("Deleting relationship with ID:", relationshipId);
|
|
75
|
+
const deleteResponse = await client.deleteRelationship({
|
|
76
|
+
tableId: QB_TABLE_ID_2,
|
|
77
|
+
relationshipId,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log(
|
|
81
|
+
"Delete relationship response:",
|
|
82
|
+
JSON.stringify(deleteResponse, null, 2)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Verify deletion (adjusted to match actual response)
|
|
86
|
+
expect(deleteResponse).toBeDefined();
|
|
87
|
+
expect(deleteResponse.relationshipId).toBe(relationshipId); // Updated to use "relationshipId"
|
|
88
|
+
|
|
89
|
+
// Step 3: Confirm relationship is gone (or non-functional)
|
|
90
|
+
try {
|
|
91
|
+
const relationships = await client.getRelationships({
|
|
92
|
+
tableId: QB_TABLE_ID_2,
|
|
93
|
+
});
|
|
94
|
+
console.log(
|
|
95
|
+
"Relationships after deletion:",
|
|
96
|
+
JSON.stringify(relationships, null, 2)
|
|
97
|
+
);
|
|
98
|
+
// Check if the relationship is still listed (metadata might persist)
|
|
99
|
+
const deletedRelationship = relationships.relationships.find(
|
|
100
|
+
(r) => r.id === relationshipId
|
|
101
|
+
);
|
|
102
|
+
if (deletedRelationship) {
|
|
103
|
+
console.log("Relationship metadata persists:", deletedRelationship);
|
|
104
|
+
expect(deletedRelationship.foreignKeyField).toBeUndefined(); // Should lack fields
|
|
105
|
+
}
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error("Error fetching relationships post-deletion:", error);
|
|
108
|
+
expect(error.message).toMatch(/404|not found/i); // Optional stricter check
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Cleanup remaining fields
|
|
112
|
+
const lookupFieldId = createResponse.lookupFields[0]?.id;
|
|
113
|
+
const summaryFieldId = createResponse.summaryFields[0]?.id;
|
|
114
|
+
|
|
115
|
+
if (foreignKeyFieldId) {
|
|
116
|
+
await client.deleteFields({
|
|
117
|
+
tableId: QB_TABLE_ID_2,
|
|
118
|
+
body: { fieldIds: [foreignKeyFieldId] },
|
|
119
|
+
});
|
|
120
|
+
console.log(
|
|
121
|
+
`Deleted foreign key field ${foreignKeyFieldId} from ${QB_TABLE_ID_2}`
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (lookupFieldId) {
|
|
125
|
+
await client.deleteFields({
|
|
126
|
+
tableId: QB_TABLE_ID_2,
|
|
127
|
+
body: { fieldIds: [lookupFieldId] },
|
|
128
|
+
});
|
|
129
|
+
console.log(
|
|
130
|
+
`Deleted lookup field ${lookupFieldId} from ${QB_TABLE_ID_2}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (summaryFieldId) {
|
|
134
|
+
await client.deleteFields({
|
|
135
|
+
tableId: QB_TABLE_ID_1,
|
|
136
|
+
body: { fieldIds: [summaryFieldId] },
|
|
137
|
+
});
|
|
138
|
+
console.log(
|
|
139
|
+
`Deleted summary field ${summaryFieldId} from ${QB_TABLE_ID_1}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
);
|