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.
Files changed (52) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +46 -130
  3. package/dist/esm/quickbase.d.ts +822 -119
  4. package/dist/esm/quickbase.js +373 -236
  5. package/dist/esm/quickbase.js.map +1 -1
  6. package/dist/esm/quickbase.min.js +1 -1
  7. package/dist/esm/quickbase.min.js.map +1 -1
  8. package/dist/umd/quickbase.umd.js +373 -236
  9. package/dist/umd/quickbase.umd.js.map +1 -1
  10. package/dist/umd/quickbase.umd.min.js +1 -1
  11. package/dist/umd/quickbase.umd.min.js.map +1 -1
  12. package/open-api/generate-unified-interface.ts +97 -17
  13. package/open-api/utils/generateJsDoc.ts +96 -0
  14. package/package.json +3 -1
  15. package/src/ThrottleBucket.ts +17 -12
  16. package/src/authorizationStrategy.ts +246 -0
  17. package/src/generated-unified/QuickbaseClient.ts +809 -119
  18. package/src/invokeMethod.ts +150 -265
  19. package/src/quickbaseClient.ts +85 -58
  20. package/src/rateLimiter.ts +25 -0
  21. package/src/tokenCache.ts +10 -26
  22. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +60 -0
  23. package/tests/vitest/qb/auth/retryUserToken401.test.ts +59 -42
  24. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +65 -0
  25. package/tests/vitest/qb/fields/getFieldUsage.test.ts +58 -150
  26. package/tests/vitest/qb/formulas/runFormula.test.ts +96 -0
  27. package/tests/vitest/qb/tables/createRelationship.test.ts +102 -0
  28. package/tests/vitest/qb/tables/deleteRelationship.test.ts +143 -0
  29. package/tests/vitest/qb/tables/updateRelationship.test.ts +118 -145
  30. package/tests/vitest/unit/ThrottleBucket.test.ts +95 -0
  31. package/tests/vitest/unit/apps/copyApp.test.ts +133 -40
  32. package/tests/vitest/unit/apps/updateApp.test.ts +42 -11
  33. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +3 -2
  34. package/tests/vitest/unit/auth/getTempToken.test.ts +42 -18
  35. package/tests/vitest/unit/auth/retryTempToken401.test.ts +46 -64
  36. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +235 -0
  37. package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -43
  38. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +325 -0
  39. package/tests/vitest/unit/fields/createField.test.ts +180 -116
  40. package/tests/vitest/unit/fields/getFieldUsage.test.ts +17 -3
  41. package/tests/vitest/unit/fields/updateField.test.ts +12 -7
  42. package/tests/vitest/unit/formulas/runFormula.test.ts +205 -0
  43. package/tests/vitest/unit/index.ts +2 -1
  44. package/tests/vitest/unit/inferHttpMethod.test.ts +131 -0
  45. package/tests/vitest/unit/rateLimit.test.ts +4 -2
  46. package/tests/vitest/unit/tables/createRelationship.test.ts +212 -0
  47. package/tests/vitest/unit/tables/deleteRelationship.test.ts +97 -0
  48. package/tests/vitest/unit/tables/updateRelationship.test.ts +12 -10
  49. package/tests/vitest/unit/tokenLifespan.test.ts +149 -83
  50. package/tools/log-gen.ts +14 -5
  51. package/tools/log-gen.yaml +710 -268
  52. package/tests/vitest/unit/auth/retryTempTokenTwo401.test.ts +0 -81
@@ -1,166 +1,72 @@
1
- // tests/vitest/qb/fields/getFieldUsage.test.ts
2
- import { describe, it, expect, afterAll } from "vitest";
3
- import {
4
- createClient,
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 - getFieldUsage", () => {
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
- // Validate response structure (unwrap the array)
91
- expect(responseArray).toBeDefined();
92
- expect(responseArray).toHaveLength(1); // Expect a single-item array
93
- const response = responseArray[0];
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
- expect(response.usage).toBeDefined();
103
- expect(typeof response.usage).toBe("object");
104
- expect(response.usage).toHaveProperty("actions");
105
- expect(response.usage.actions).toHaveProperty("count");
106
- expect(typeof response.usage.actions.count).toBe("number");
107
- expect(response.usage).toHaveProperty("appHomePages");
108
- expect(response.usage).toHaveProperty("dashboards");
109
- expect(response.usage).toHaveProperty("defaultReports");
110
- expect(response.usage).toHaveProperty("exactForms");
111
- expect(response.usage).toHaveProperty("fields");
112
- expect(response.usage).toHaveProperty("forms");
113
- expect(response.usage).toHaveProperty("notifications");
114
- expect(response.usage).toHaveProperty("personalReports");
115
- expect(response.usage).toHaveProperty("pipelines");
116
- expect(response.usage).toHaveProperty("relationships");
117
- expect(response.usage).toHaveProperty("reminders");
118
- expect(response.usage).toHaveProperty("reports");
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
- // Since it’s a new field, expect minimal usage
125
- expect(response.usage.forms.count).toBeGreaterThanOrEqual(0); // May be 1 if addToForms worked
126
- },
127
- { timeout: 30000 } // 30-second timeout for create + get + delete
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
- it("fails with non-existent field ID", { timeout: 15000 }, async () => {
131
- const nonExistentFieldId = 999999;
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("Attempting to fetch usage for non-existent field:", {
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
- fieldId: nonExistentFieldId,
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
- `Confirmed 404 for non-existent field ID ${nonExistentFieldId}`
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", { timeout: 15000 }, async () => {
64
+ it("fails with invalid table ID", async () => {
154
65
  const invalidTableId = "invalid_dbid";
155
- const fieldId = 6;
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.getFieldUsage({
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(`Confirmed 401 for invalid table ID ${invalidTableId}`);
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
+ );