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.
- package/CHANGELOG.md +26 -0
- package/dist/esm/quickbase.d.ts +589 -125
- package/dist/esm/quickbase.js +484 -145
- 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 +492 -144
- 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/fix-spec-main.ts +69 -68
- package/open-api/generate-override-report.ts +214 -0
- package/open-api/generate-unified-interface.ts +73 -94
- package/open-api/output/override-report.html +11 -0
- package/open-api/output/override-report.json +14395 -58
- package/open-api/output/quickbase-fixed.json +583 -2
- package/open-api/schema/tags/fields.ts +331 -16
- package/open-api/schema/tags/index.ts +2 -4
- package/open-api/types/spec.ts +2 -1
- package/package.json +31 -33
- package/src/RateLimitError.ts +12 -0
- package/src/ThrottleBucket.ts +36 -0
- package/src/generated-unified/QuickbaseClient.ts +119 -119
- package/src/invokeMethod.ts +357 -0
- package/src/quickbaseClient.ts +51 -209
- package/tests/setup.ts +2 -0
- package/tests/vitest/qb/apps/getApp.test.ts +6 -2
- package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
- package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
- package/tests/vitest/qb/auth/retryUserToken401.test.ts +56 -0
- package/tests/vitest/qb/fields/createField.test.ts +1 -2
- package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
- package/tests/vitest/qb/fields/updateField.test.ts +93 -0
- package/tests/vitest/qb/index.ts +1 -0
- package/tests/vitest/qb/tables/updateRelationship.test.ts +277 -0
- package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
- package/tests/vitest/unit/apps/updateApp.test.ts +381 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +6 -26
- package/tests/vitest/unit/auth/{retryOn401.test.ts → retryTempToken401.test.ts} +3 -6
- package/tests/vitest/unit/auth/{two401s.test.ts → retryTempTokenTwo401.test.ts} +4 -9
- package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -0
- package/tests/vitest/unit/fields/createField.test.ts +8 -12
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +33 -14
- package/tests/vitest/unit/fields/updateField.test.ts +280 -0
- package/tests/vitest/unit/rateLimit.test.ts +189 -0
- package/tests/vitest/unit/tables/updateRelationship.test.ts +305 -0
- package/tests/vitest/unit/tokenLifespan.test.ts +138 -0
- package/tools/log-gen.ts +8 -4
- package/tools/log-gen.yaml +3004 -1848
- package/tsconfig.json +5 -2
- package/vitest.config.ts +2 -2
- package/notes.txt +0 -158
- package/tools/generate-types.ts +0 -29
- package/tools/openapi-schema-validator.ts +0 -38
- package/tools/spec-analyzer.ts +0 -163
- package/tools/split-spec.ts +0 -148
- package/tools/test-import.ts +0 -3
- package/tools/validate-spec.ts +0 -40
- /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
- /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),
|
|
24
|
-
updated: expect.any(Date),
|
|
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;
|
|
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);
|
|
44
|
+
expect(response.errors ?? []).toHaveLength(0);
|
|
51
45
|
} catch (error) {
|
|
52
46
|
console.error("deleteFields failed:", error);
|
|
53
|
-
throw error;
|
|
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;
|
|
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
|
+
});
|
package/tests/vitest/qb/index.ts
CHANGED
|
@@ -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
|
+
});
|