quickbase-js 0.1.0-beta.7 → 0.1.0-beta.8

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 (60) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/esm/quickbase.d.ts +589 -125
  3. package/dist/esm/quickbase.js +484 -145
  4. package/dist/esm/quickbase.js.map +1 -1
  5. package/dist/esm/quickbase.min.js +1 -1
  6. package/dist/esm/quickbase.min.js.map +1 -1
  7. package/dist/umd/quickbase.umd.js +492 -144
  8. package/dist/umd/quickbase.umd.js.map +1 -1
  9. package/dist/umd/quickbase.umd.min.js +1 -1
  10. package/dist/umd/quickbase.umd.min.js.map +1 -1
  11. package/open-api/fix-spec-main.ts +69 -68
  12. package/open-api/generate-override-report.ts +214 -0
  13. package/open-api/generate-unified-interface.ts +73 -94
  14. package/open-api/output/override-report.html +11 -0
  15. package/open-api/output/override-report.json +14395 -58
  16. package/open-api/output/quickbase-fixed.json +583 -2
  17. package/open-api/schema/tags/fields.ts +331 -16
  18. package/open-api/schema/tags/index.ts +2 -4
  19. package/open-api/types/spec.ts +2 -1
  20. package/package.json +31 -33
  21. package/src/RateLimitError.ts +12 -0
  22. package/src/ThrottleBucket.ts +36 -0
  23. package/src/generated-unified/QuickbaseClient.ts +119 -119
  24. package/src/invokeMethod.ts +357 -0
  25. package/src/quickbaseClient.ts +51 -209
  26. package/tests/setup.ts +2 -0
  27. package/tests/vitest/qb/apps/getApp.test.ts +6 -2
  28. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  29. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  30. package/tests/vitest/qb/auth/retryUserToken401.test.ts +56 -0
  31. package/tests/vitest/qb/fields/createField.test.ts +1 -2
  32. package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
  33. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  34. package/tests/vitest/qb/index.ts +1 -0
  35. package/tests/vitest/qb/tables/updateRelationship.test.ts +277 -0
  36. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  37. package/tests/vitest/unit/apps/updateApp.test.ts +381 -0
  38. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +6 -26
  39. package/tests/vitest/unit/auth/{retryOn401.test.ts → retryTempToken401.test.ts} +3 -6
  40. package/tests/vitest/unit/auth/{two401s.test.ts → retryTempTokenTwo401.test.ts} +4 -9
  41. package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -0
  42. package/tests/vitest/unit/fields/createField.test.ts +8 -12
  43. package/tests/vitest/unit/fields/getFieldUsage.test.ts +33 -14
  44. package/tests/vitest/unit/fields/updateField.test.ts +280 -0
  45. package/tests/vitest/unit/rateLimit.test.ts +189 -0
  46. package/tests/vitest/unit/tables/updateRelationship.test.ts +305 -0
  47. package/tests/vitest/unit/tokenLifespan.test.ts +138 -0
  48. package/tools/log-gen.ts +8 -4
  49. package/tools/log-gen.yaml +3004 -1848
  50. package/tsconfig.json +5 -2
  51. package/vitest.config.ts +2 -2
  52. package/notes.txt +0 -158
  53. package/tools/generate-types.ts +0 -29
  54. package/tools/openapi-schema-validator.ts +0 -38
  55. package/tools/spec-analyzer.ts +0 -163
  56. package/tools/split-spec.ts +0 -148
  57. package/tools/test-import.ts +0 -3
  58. package/tools/validate-spec.ts +0 -40
  59. /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
  60. /package/tests/playwright/qb/auth/{tokenRenewal.test.ts → tempTokenRenewal.test.ts} +0 -0
@@ -1,3 +1,4 @@
1
+ // tests/vitest/qb/apps/getApp.test.ts
1
2
  import { test, expect } from "vitest";
2
3
  import { quickbase } from "../../../../src/quickbaseClient";
3
4
 
@@ -20,8 +21,8 @@ test(
20
21
  expect(result).toEqual({
21
22
  id: getAppId,
22
23
  name: "qb-copy",
23
- created: expect.any(Date), // Matches Date object from client
24
- updated: expect.any(Date), // Matches Date object from client
24
+ created: expect.any(Date),
25
+ updated: expect.any(Date),
25
26
  description: "",
26
27
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
27
28
  dateFormat: "MM-DD-YYYY",
@@ -35,6 +36,9 @@ test(
35
36
  mustBeRealmApproved: false,
36
37
  useIPFilter: false,
37
38
  },
39
+ ancestorId: undefined,
40
+ dataClassification: undefined,
41
+ variables: [{ name: "TestVar", value: "TestValue" }],
38
42
  });
39
43
  }
40
44
  );
@@ -0,0 +1,67 @@
1
+ import { test, expect } from "vitest";
2
+ import { quickbase } from "../../../../src/quickbaseClient";
3
+
4
+ test(
5
+ "QuickbaseClient Integration - getAppEvents > fetches real app events from QuickBase",
6
+ { timeout: 10000 },
7
+ async () => {
8
+ const config = {
9
+ realm: process.env.QB_REALM || "",
10
+ userToken: process.env.QB_USER_TOKEN || "",
11
+ debug: true,
12
+ };
13
+ const client = quickbase(config);
14
+ const appId = "buwai2zpe"; // Same app ID as getApp test
15
+
16
+ console.log("Config used:", config);
17
+ const result = await client.getAppEvents({ appId });
18
+ console.log("Real API response:", result);
19
+
20
+ // Validate the response is an array of events
21
+ expect(result).toBeInstanceOf(Array);
22
+
23
+ // Check that each event has the required properties
24
+ result.forEach((event) => {
25
+ expect(event).toHaveProperty("type");
26
+ expect([
27
+ "qb-action",
28
+ "webhook",
29
+ "email-notification",
30
+ "subscription",
31
+ "reminder",
32
+ "automation",
33
+ ]).toContain(event.type);
34
+
35
+ expect(event).toHaveProperty("owner");
36
+ expect(event.owner).toHaveProperty("email");
37
+ expect(typeof event.owner.email).toBe("string");
38
+ expect(event.owner).toHaveProperty("id");
39
+ expect(typeof event.owner.id).toBe("string");
40
+ expect(event.owner).toHaveProperty("name");
41
+ expect(typeof event.owner.name).toBe("string");
42
+ // userName is optional, so we don't enforce it
43
+
44
+ expect(event).toHaveProperty("isActive");
45
+ expect(typeof event.isActive).toBe("boolean");
46
+
47
+ expect(event).toHaveProperty("tableId");
48
+ expect(typeof event.tableId).toBe("string");
49
+
50
+ // Name is present for all types except automation
51
+ if (event.type !== "automation") {
52
+ expect(event).toHaveProperty("name");
53
+ expect(typeof event.name).toBe("string");
54
+ } else {
55
+ expect(event.name).toBeUndefined();
56
+ }
57
+
58
+ // URL is only present for automation
59
+ if (event.type === "automation") {
60
+ expect(event).toHaveProperty("url");
61
+ expect(typeof event.url).toBe("string");
62
+ } else {
63
+ expect(event.url).toBeUndefined();
64
+ }
65
+ });
66
+ }
67
+ );
@@ -0,0 +1,58 @@
1
+ import { test, expect } from "vitest";
2
+ import { quickbase } from "../../../../src/quickbaseClient";
3
+
4
+ test(
5
+ "QuickbaseClient Integration - updateApp > updates real app data in QuickBase",
6
+ { timeout: 10000 },
7
+ async () => {
8
+ const config = {
9
+ realm: process.env.QB_REALM || "",
10
+ userToken: process.env.QB_USER_TOKEN || "",
11
+ debug: true,
12
+ };
13
+ const client = quickbase(config);
14
+ const appId = "buwai2zpe"; // Same app ID as before
15
+
16
+ // Original app data (to restore later if needed)
17
+ const originalApp = await client.getApp({ appId });
18
+ console.log("Original app data:", originalApp);
19
+
20
+ // Update request
21
+ const updateRequest = {
22
+ name: "qb-copy-updated",
23
+ description: "Updated via integration test",
24
+ variables: [{ name: "TestVar", value: "TestValue" }],
25
+ };
26
+
27
+ console.log("Config used:", config);
28
+ console.log("Update request:", updateRequest);
29
+ const result = await client.updateApp({ appId, body: updateRequest });
30
+ console.log("Real API response:", result);
31
+
32
+ // Validate the response
33
+ expect(result).toHaveProperty("id", appId);
34
+ expect(result).toHaveProperty("name", "qb-copy-updated");
35
+ expect(result).toHaveProperty(
36
+ "description",
37
+ "Updated via integration test"
38
+ );
39
+ expect(result).toHaveProperty("variables");
40
+ expect(result.variables).toContainEqual({
41
+ name: "TestVar",
42
+ value: "TestValue",
43
+ });
44
+ expect(result).toHaveProperty("created");
45
+ expect(result.created).toBeInstanceOf(Date);
46
+ expect(result).toHaveProperty("updated");
47
+ expect(result.updated).toBeInstanceOf(Date);
48
+
49
+ // Optional: Restore original name to avoid permanent changes
50
+ const restoreRequest = {
51
+ name: originalApp.name,
52
+ description: originalApp.description,
53
+ variables: originalApp.variables,
54
+ };
55
+ await client.updateApp({ appId, body: restoreRequest });
56
+ console.log("Restored app to:", originalApp.name);
57
+ }
58
+ );
@@ -0,0 +1,56 @@
1
+ import { describe, it, vi, expect, beforeEach } from "vitest";
2
+ import { quickbase, QuickbaseClient } from "../../../../src/quickbaseClient";
3
+ import { ResponseError } from "../../../../src/generated/runtime";
4
+
5
+ describe("QuickbaseClient Integration - User Token Retry on 401", () => {
6
+ let client: QuickbaseClient;
7
+ const mockFetch = vi.fn();
8
+
9
+ beforeEach(() => {
10
+ const config = {
11
+ realm: process.env.QB_REALM || "builderprogram-dbradford6815",
12
+ userToken: "mock-user-token",
13
+ debug: true,
14
+ fetchApi: mockFetch,
15
+ };
16
+ console.log("[quickbaseTest] Config:", config);
17
+ client = quickbase(config);
18
+ });
19
+
20
+ it("retries on transient 401 with user token and succeeds", async () => {
21
+ const consoleSpy = vi.spyOn(console, "log");
22
+
23
+ mockFetch
24
+ .mockImplementationOnce(() => {
25
+ const response = new Response(null, {
26
+ status: 401,
27
+ statusText: "Unauthorized",
28
+ });
29
+ console.log("[mockFetch] Throwing 401 ResponseError");
30
+ return Promise.reject(new ResponseError(response));
31
+ })
32
+ .mockResolvedValueOnce({
33
+ ok: true,
34
+ status: 200,
35
+ json: async () => [{ id: 1, label: "Mock Field", fieldType: "text" }],
36
+ });
37
+
38
+ const fields = await client.getFields({ tableId: "mock-table-id" });
39
+
40
+ expect(fields).toBeInstanceOf(Array);
41
+ expect(fields.length).toBeGreaterThan(0);
42
+ expect(fields[0]).toHaveProperty("id");
43
+ expect(fields[0]).toHaveProperty("label");
44
+
45
+ expect(consoleSpy).toHaveBeenCalledWith(
46
+ "Authorization error for getFields (user token), retrying with same token:",
47
+ "" // Matches empty ResponseError message from mock
48
+ );
49
+ expect(consoleSpy).toHaveBeenCalledWith(
50
+ "Retrying getFields with user token"
51
+ );
52
+ expect(mockFetch).toHaveBeenCalledTimes(2);
53
+
54
+ consoleSpy.mockRestore();
55
+ });
56
+ });
@@ -1,4 +1,3 @@
1
- // tests/vitest/qb/fields/createField.test.ts (updated)
2
1
  import { describe, it, expect, afterAll } from "vitest";
3
2
  import { createClient, QB_TABLE_ID_1 } from "../../../setup.ts";
4
3
 
@@ -62,7 +61,7 @@ describe("QuickbaseClient Integration - createField", () => {
62
61
  },
63
62
  })
64
63
  ).rejects.toThrow("API Error: Bad Request (Status: 400)");
65
- });
64
+ }, 10000); // Increased timeout to 10 seconds
66
65
 
67
66
  it("fails with missing label", async () => {
68
67
  await expect(
@@ -4,12 +4,10 @@ import { createClient, QB_TABLE_ID_1 } from "../../../setup.ts";
4
4
 
5
5
  describe("QuickbaseClient Integration - deleteFields", () => {
6
6
  const client = createClient();
7
-
8
7
  let fieldIdsToDelete: number[] = [];
9
8
 
10
9
  beforeAll(async () => {
11
10
  try {
12
- // Create test fields to delete
13
11
  const fieldLabels = ["DeleteTest1", "DeleteTest2"];
14
12
  for (const label of fieldLabels) {
15
13
  const response = await client.createField({
@@ -28,7 +26,7 @@ describe("QuickbaseClient Integration - deleteFields", () => {
28
26
  }
29
27
  } catch (error) {
30
28
  console.error("beforeAll failed:", error);
31
- throw error; // Fail the suite if setup fails
29
+ throw error;
32
30
  }
33
31
  });
34
32
 
@@ -36,41 +34,37 @@ describe("QuickbaseClient Integration - deleteFields", () => {
36
34
  try {
37
35
  const response = await client.deleteFields({
38
36
  tableId: QB_TABLE_ID_1,
39
- body: {
40
- fieldIds: fieldIdsToDelete,
41
- },
37
+ body: { fieldIds: fieldIdsToDelete },
42
38
  });
43
-
44
39
  console.log("deleteFields response:", JSON.stringify(response, null, 2));
45
-
46
40
  expect(response).toBeDefined();
47
41
  expect(response.deletedFieldIds).toEqual(
48
42
  expect.arrayContaining(fieldIdsToDelete)
49
43
  );
50
- expect(response.errors ?? []).toHaveLength(0); // Handle undefined errors
44
+ expect(response.errors ?? []).toHaveLength(0);
51
45
  } catch (error) {
52
46
  console.error("deleteFields failed:", error);
53
- throw error; // Re-throw to fail the test with details
47
+ throw error;
54
48
  }
55
49
  });
56
50
 
57
- it("handles partial success with errors", async () => {
51
+ it("handles partial success with errors", { timeout: 10000 }, async () => {
58
52
  const newField = await client.createField({
59
53
  tableId: QB_TABLE_ID_1,
60
- body: {
61
- label: "DeleteTest3",
62
- fieldType: "text",
63
- },
54
+ body: { label: "DeleteTest3", fieldType: "text" },
64
55
  });
65
56
  console.log(`Created field DeleteTest3 with ID ${newField.id}`);
66
57
 
67
- const invalidFieldId = 999999; // Assume this ID doesn’t exist
58
+ const invalidFieldId = 999999;
68
59
  const fieldIds = [newField.id, invalidFieldId];
69
60
 
61
+ console.log("Sending deleteFields request at:", new Date());
70
62
  const response = await client.deleteFields({
71
63
  tableId: QB_TABLE_ID_1,
72
64
  body: { fieldIds },
73
65
  });
66
+ console.log("deleteFields response received at:", new Date());
67
+ console.log("Response:", JSON.stringify(response, null, 2));
74
68
 
75
69
  expect(response.deletedFieldIds).toContain(newField.id);
76
70
  expect(response.deletedFieldIds).not.toContain(invalidFieldId);
@@ -0,0 +1,93 @@
1
+ // tests/vitest/qb/fields/updateField.test.ts
2
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
3
+ import { createClient, QB_TABLE_ID_1 } from "../../../setup.ts";
4
+
5
+ describe("QuickbaseClient Integration - updateField", () => {
6
+ const client = createClient();
7
+ let fieldIdToUpdate: number;
8
+
9
+ // Setup: Create a test field to update
10
+ beforeAll(async () => {
11
+ try {
12
+ const response = await client.createField({
13
+ tableId: QB_TABLE_ID_1,
14
+ body: {
15
+ label: "UpdateTestField",
16
+ fieldType: "text",
17
+ fieldHelp: "Test field for updating",
18
+ },
19
+ });
20
+ fieldIdToUpdate = response.id;
21
+ console.log(`Created field UpdateTestField with ID ${fieldIdToUpdate}`);
22
+ } catch (error) {
23
+ console.error("beforeAll failed:", error);
24
+ throw error; // Fail the suite if setup fails
25
+ }
26
+ });
27
+
28
+ // Teardown: Delete the test field
29
+ afterAll(async () => {
30
+ try {
31
+ if (fieldIdToUpdate) {
32
+ const response = await client.deleteFields({
33
+ tableId: QB_TABLE_ID_1,
34
+ body: { fieldIds: [fieldIdToUpdate] },
35
+ });
36
+ console.log(
37
+ `Deleted field ${fieldIdToUpdate}:`,
38
+ JSON.stringify(response, null, 2)
39
+ );
40
+ }
41
+ } catch (error) {
42
+ console.error("afterAll cleanup failed:", error);
43
+ // Don’t throw here to avoid masking test results
44
+ }
45
+ });
46
+
47
+ it(
48
+ "updates a field successfully with user token",
49
+ { timeout: 10000 },
50
+ async () => {
51
+ try {
52
+ const updatedLabel = "UpdatedTestField";
53
+ console.log("Sending updateField request at:", new Date());
54
+ const response = await client.updateField({
55
+ tableId: QB_TABLE_ID_1,
56
+ fieldId: fieldIdToUpdate,
57
+ body: {
58
+ label: updatedLabel,
59
+ fieldHelp: "Updated help text",
60
+ },
61
+ });
62
+ console.log("updateField response received at:", new Date());
63
+ console.log("Response:", JSON.stringify(response, null, 2));
64
+
65
+ // Verify the update was successful
66
+ expect(response).toBeDefined();
67
+ expect(response.id).toBe(fieldIdToUpdate);
68
+ expect(response.label).toBe(updatedLabel);
69
+ expect(response.fieldHelp).toBe("Updated help text");
70
+ } catch (error) {
71
+ console.error("updateField failed:", error);
72
+ throw error; // Re-throw to fail the test with details
73
+ }
74
+ }
75
+ );
76
+
77
+ it("fails with invalid field ID", { timeout: 10000 }, async () => {
78
+ try {
79
+ await expect(
80
+ client.updateField({
81
+ tableId: QB_TABLE_ID_1,
82
+ fieldId: 999999, // Invalid field ID
83
+ body: {
84
+ label: "InvalidUpdate",
85
+ },
86
+ })
87
+ ).rejects.toThrow("API Error: Item not found (Status: 404)"); // Updated to match actual error
88
+ } catch (error) {
89
+ console.error("updateField with invalid ID failed unexpectedly:", error);
90
+ throw error;
91
+ }
92
+ });
93
+ });
@@ -26,3 +26,4 @@ export * from "./tables/getAppTables.test.ts";
26
26
  export * from "./tables/getRelationships.test.ts";
27
27
  export * from "./tables/getTable.test.ts";
28
28
  export * from "./tables/updateTable.test.ts";
29
+ export * from "./tables/updateRelationship.test.ts";
@@ -0,0 +1,277 @@
1
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
2
+ import {
3
+ createClient,
4
+ QB_REALM,
5
+ QB_USER_TOKEN,
6
+ QB_TABLE_ID_1,
7
+ QB_TABLE_ID_2,
8
+ } from "@tests/setup.ts";
9
+ import {
10
+ UpdateRelationshipRequest,
11
+ CreateFieldRequest,
12
+ } from "@/generated/models";
13
+
14
+ describe("QuickbaseClient Integration - updateRelationship", () => {
15
+ let client: ReturnType<typeof createClient>;
16
+ let relationshipId: number;
17
+ let newLookupFieldId: number | undefined;
18
+ let newChildFieldId: number | undefined;
19
+ let generatedLookupFieldId: number | undefined;
20
+ let generatedSummaryFieldId: number | undefined;
21
+
22
+ beforeAll(async () => {
23
+ client = createClient(fetch, {
24
+ realm: QB_REALM,
25
+ userToken: QB_USER_TOKEN,
26
+ debug: true,
27
+ });
28
+
29
+ if (!QB_REALM) throw new Error("QB_REALM is not defined in .env");
30
+ if (!QB_USER_TOKEN) throw new Error("QB_USER_TOKEN is not defined in .env");
31
+
32
+ const relationshipsResponse = await client.getRelationships({
33
+ tableId: QB_TABLE_ID_2,
34
+ skip: 0,
35
+ });
36
+ console.log(
37
+ "Initial relationships response:",
38
+ JSON.stringify(relationshipsResponse, null, 2)
39
+ );
40
+ const relationship = relationshipsResponse.relationships.find(
41
+ (r) => r.parentTableId === QB_TABLE_ID_1
42
+ );
43
+ if (!relationship) {
44
+ throw new Error(
45
+ `No relationship found between parent ${QB_TABLE_ID_1} and child ${QB_TABLE_ID_2}`
46
+ );
47
+ }
48
+ relationshipId = relationship.id;
49
+ console.log(`Identified relationshipId: ${relationshipId}`);
50
+
51
+ const lookupFieldRequest: CreateFieldRequest = {
52
+ label: `Test Lookup Field_${Date.now()}`,
53
+ fieldType: "text",
54
+ };
55
+ const lookupResponse = await client.createField({
56
+ tableId: QB_TABLE_ID_1,
57
+ body: lookupFieldRequest,
58
+ });
59
+ newLookupFieldId = lookupResponse.id;
60
+ if (!newLookupFieldId) {
61
+ throw new Error("Failed to retrieve ID for newly created lookup field");
62
+ }
63
+ console.log(
64
+ `Created lookup field with ID ${newLookupFieldId} in parent table ${QB_TABLE_ID_1}`
65
+ );
66
+
67
+ const childFieldRequest: CreateFieldRequest = {
68
+ label: `Test Child Numeric_${Date.now()}`,
69
+ fieldType: "numeric",
70
+ };
71
+ const childResponse = await client.createField({
72
+ tableId: QB_TABLE_ID_2,
73
+ body: childFieldRequest,
74
+ });
75
+ newChildFieldId = childResponse.id;
76
+ if (!newChildFieldId) {
77
+ throw new Error("Failed to retrieve ID for newly created child field");
78
+ }
79
+ console.log(
80
+ `Created child field with ID ${newChildFieldId} in child table ${QB_TABLE_ID_2}`
81
+ );
82
+
83
+ console.log(
84
+ `Setup complete: relationshipId=${relationshipId}, lookupFieldId=${newLookupFieldId}, childFieldId=${newChildFieldId}`
85
+ );
86
+ }, 30000);
87
+
88
+ afterAll(async () => {
89
+ if (newLookupFieldId) {
90
+ try {
91
+ const deleteLookupResponse = await client.deleteFields({
92
+ tableId: QB_TABLE_ID_1,
93
+ body: { fieldIds: [newLookupFieldId] },
94
+ });
95
+ console.log(
96
+ `Deleted original lookup field ${newLookupFieldId} from ${QB_TABLE_ID_1}:`,
97
+ JSON.stringify(deleteLookupResponse, null, 2)
98
+ );
99
+ } catch (error) {
100
+ console.error(
101
+ `Failed to delete original lookup field ${newLookupFieldId}:`,
102
+ error
103
+ );
104
+ throw error;
105
+ }
106
+ }
107
+ if (newChildFieldId) {
108
+ try {
109
+ const deleteChildResponse = await client.deleteFields({
110
+ tableId: QB_TABLE_ID_2,
111
+ body: { fieldIds: [newChildFieldId] },
112
+ });
113
+ console.log(
114
+ `Deleted original child field ${newChildFieldId} from ${QB_TABLE_ID_2}:`,
115
+ JSON.stringify(deleteChildResponse, null, 2)
116
+ );
117
+ } catch (error) {
118
+ console.error(
119
+ `Failed to delete original child field ${newChildFieldId}:`,
120
+ error
121
+ );
122
+ throw error;
123
+ }
124
+ }
125
+ if (generatedLookupFieldId) {
126
+ try {
127
+ const deleteGeneratedLookupResponse = await client.deleteFields({
128
+ tableId: QB_TABLE_ID_2,
129
+ body: { fieldIds: [generatedLookupFieldId] },
130
+ });
131
+ console.log(
132
+ `Deleted generated lookup field ${generatedLookupFieldId} from ${QB_TABLE_ID_2}:`,
133
+ JSON.stringify(deleteGeneratedLookupResponse, null, 2)
134
+ );
135
+ } catch (error) {
136
+ console.error(
137
+ `Failed to delete generated lookup field ${generatedLookupFieldId}:`,
138
+ error
139
+ );
140
+ throw error;
141
+ }
142
+ }
143
+ if (generatedSummaryFieldId) {
144
+ try {
145
+ const deleteGeneratedSummaryResponse = await client.deleteFields({
146
+ tableId: QB_TABLE_ID_1,
147
+ body: { fieldIds: [generatedSummaryFieldId] },
148
+ });
149
+ console.log(
150
+ `Deleted generated summary field ${generatedSummaryFieldId} from ${QB_TABLE_ID_1}:`,
151
+ JSON.stringify(deleteGeneratedSummaryResponse, null, 2)
152
+ );
153
+ } catch (error) {
154
+ console.error(
155
+ `Failed to delete generated summary field ${generatedSummaryFieldId}:`,
156
+ error
157
+ );
158
+ throw error;
159
+ }
160
+ }
161
+ }, 30000);
162
+
163
+ beforeEach(async () => {
164
+ const relationshipsResponse = await client.getRelationships({
165
+ tableId: QB_TABLE_ID_2,
166
+ });
167
+ const relationship = relationshipsResponse.relationships.find(
168
+ (r) => r.id === relationshipId
169
+ );
170
+ if (!relationship) {
171
+ throw new Error(`Relationship ${relationshipId} not found before test`);
172
+ }
173
+ console.log(
174
+ "Relationship state before test:",
175
+ JSON.stringify(relationship, null, 2)
176
+ );
177
+ }, 30000);
178
+
179
+ it("adds a lookup field to the relationship", async ({ expect }) => {
180
+ const tableId = QB_TABLE_ID_2;
181
+ const initialResponse = await client.getRelationships({ tableId });
182
+ const initialRelationship = initialResponse.relationships.find(
183
+ (r) => r.id === relationshipId
184
+ );
185
+ const currentLookupFields = (initialRelationship?.lookupFields || []).map(
186
+ (f) => f.id
187
+ );
188
+ console.log("Current lookupFields before update:", currentLookupFields);
189
+
190
+ const request: UpdateRelationshipRequest = {
191
+ lookupFieldIds: [newLookupFieldId!],
192
+ };
193
+ console.log(
194
+ "Adding lookup field request:",
195
+ JSON.stringify(request, null, 2)
196
+ );
197
+
198
+ const updateResponse = await client.updateRelationship({
199
+ tableId,
200
+ relationshipId,
201
+ body: request,
202
+ });
203
+ console.log("Update response:", JSON.stringify(updateResponse, null, 2));
204
+
205
+ const newLookup = updateResponse.lookupFields.find(
206
+ (f) =>
207
+ f.label.includes("Test Lookup Field") &&
208
+ !currentLookupFields.includes(f.id)
209
+ );
210
+ generatedLookupFieldId = newLookup?.id;
211
+ console.log(
212
+ `Captured generated lookup field ID: ${generatedLookupFieldId}`
213
+ );
214
+
215
+ expect(updateResponse.id).toBe(relationshipId);
216
+ expect(updateResponse.childTableId).toBe(QB_TABLE_ID_2);
217
+ expect(updateResponse.parentTableId).toBe(QB_TABLE_ID_1);
218
+ expect(
219
+ updateResponse.lookupFields.some((f) =>
220
+ f.label.includes("Test Lookup Field")
221
+ )
222
+ ).toBe(true);
223
+ expect(updateResponse.lookupFields.some((f) => f.id === 19)).toBe(true);
224
+ }, 30000);
225
+
226
+ it("adds a summary field to the parent table", async ({ expect }) => {
227
+ const tableId = QB_TABLE_ID_2;
228
+ const initialResponse = await client.getRelationships({ tableId });
229
+ const initialRelationship = initialResponse.relationships.find(
230
+ (r) => r.id === relationshipId
231
+ );
232
+ const currentSummaryFields = (initialRelationship?.summaryFields || []).map(
233
+ (f) => f.id
234
+ );
235
+ console.log("Current summaryFields before update:", currentSummaryFields);
236
+
237
+ const request: UpdateRelationshipRequest = {
238
+ summaryFields: [
239
+ {
240
+ summaryFid: newChildFieldId!,
241
+ label: "Test Summary Field",
242
+ accumulationType: "SUM",
243
+ },
244
+ ],
245
+ };
246
+ console.log(
247
+ "Adding summary field request:",
248
+ JSON.stringify(request, null, 2)
249
+ );
250
+
251
+ const updateResponse = await client.updateRelationship({
252
+ tableId,
253
+ relationshipId,
254
+ body: request,
255
+ });
256
+ console.log("Update response:", JSON.stringify(updateResponse, null, 2));
257
+
258
+ const newSummary = updateResponse.summaryFields.find(
259
+ (f) =>
260
+ f.label.includes("Test Summary Field") &&
261
+ !currentSummaryFields.includes(f.id)
262
+ );
263
+ generatedSummaryFieldId = newSummary?.id;
264
+ console.log(
265
+ `Captured generated summary field ID: ${generatedSummaryFieldId}`
266
+ );
267
+
268
+ expect(updateResponse.id).toBe(relationshipId);
269
+ expect(updateResponse.childTableId).toBe(QB_TABLE_ID_2);
270
+ expect(updateResponse.parentTableId).toBe(QB_TABLE_ID_1);
271
+ expect(
272
+ updateResponse.summaryFields.some((f) =>
273
+ f.label.includes("Test Summary Field")
274
+ )
275
+ ).toBe(true);
276
+ }, 30000);
277
+ });