quickbase-js 0.1.0-beta.7 → 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 (82) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +46 -130
  3. package/dist/esm/quickbase.d.ts +1292 -125
  4. package/dist/esm/quickbase.js +666 -190
  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 +674 -189
  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/fix-spec-main.ts +69 -68
  13. package/open-api/generate-override-report.ts +214 -0
  14. package/open-api/generate-unified-interface.ts +162 -103
  15. package/open-api/output/override-report.html +11 -0
  16. package/open-api/output/override-report.json +14395 -58
  17. package/open-api/output/quickbase-fixed.json +583 -2
  18. package/open-api/schema/tags/fields.ts +331 -16
  19. package/open-api/schema/tags/index.ts +2 -4
  20. package/open-api/types/spec.ts +2 -1
  21. package/open-api/utils/generateJsDoc.ts +96 -0
  22. package/package.json +33 -33
  23. package/src/RateLimitError.ts +12 -0
  24. package/src/ThrottleBucket.ts +41 -0
  25. package/src/authorizationStrategy.ts +246 -0
  26. package/src/generated-unified/QuickbaseClient.ts +809 -119
  27. package/src/invokeMethod.ts +242 -0
  28. package/src/quickbaseClient.ts +117 -248
  29. package/src/rateLimiter.ts +25 -0
  30. package/src/tokenCache.ts +10 -26
  31. package/tests/setup.ts +2 -0
  32. package/tests/vitest/qb/apps/getApp.test.ts +6 -2
  33. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  34. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  35. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +60 -0
  36. package/tests/vitest/qb/auth/retryUserToken401.test.ts +73 -0
  37. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +65 -0
  38. package/tests/vitest/qb/fields/createField.test.ts +1 -2
  39. package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
  40. package/tests/vitest/qb/fields/getFieldUsage.test.ts +58 -150
  41. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  42. package/tests/vitest/qb/formulas/runFormula.test.ts +96 -0
  43. package/tests/vitest/qb/index.ts +1 -0
  44. package/tests/vitest/qb/tables/createRelationship.test.ts +102 -0
  45. package/tests/vitest/qb/tables/deleteRelationship.test.ts +143 -0
  46. package/tests/vitest/qb/tables/updateRelationship.test.ts +250 -0
  47. package/tests/vitest/unit/ThrottleBucket.test.ts +95 -0
  48. package/tests/vitest/unit/apps/copyApp.test.ts +133 -40
  49. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  50. package/tests/vitest/unit/apps/updateApp.test.ts +412 -0
  51. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +7 -26
  52. package/tests/vitest/unit/auth/getTempToken.test.ts +42 -18
  53. package/tests/vitest/unit/auth/retryTempToken401.test.ts +95 -0
  54. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +235 -0
  55. package/tests/vitest/unit/auth/retryUserToken401.test.ts +69 -0
  56. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +325 -0
  57. package/tests/vitest/unit/fields/createField.test.ts +180 -120
  58. package/tests/vitest/unit/fields/getFieldUsage.test.ts +47 -14
  59. package/tests/vitest/unit/fields/updateField.test.ts +285 -0
  60. package/tests/vitest/unit/formulas/runFormula.test.ts +205 -0
  61. package/tests/vitest/unit/index.ts +2 -1
  62. package/tests/vitest/unit/inferHttpMethod.test.ts +131 -0
  63. package/tests/vitest/unit/rateLimit.test.ts +191 -0
  64. package/tests/vitest/unit/tables/createRelationship.test.ts +212 -0
  65. package/tests/vitest/unit/tables/deleteRelationship.test.ts +97 -0
  66. package/tests/vitest/unit/tables/updateRelationship.test.ts +307 -0
  67. package/tests/vitest/unit/tokenLifespan.test.ts +204 -0
  68. package/tools/log-gen.ts +20 -7
  69. package/tools/log-gen.yaml +3446 -1848
  70. package/tsconfig.json +5 -2
  71. package/vitest.config.ts +2 -2
  72. package/notes.txt +0 -158
  73. package/tests/vitest/unit/auth/retryOn401.test.ts +0 -116
  74. package/tests/vitest/unit/auth/two401s.test.ts +0 -86
  75. package/tools/generate-types.ts +0 -29
  76. package/tools/openapi-schema-validator.ts +0 -38
  77. package/tools/spec-analyzer.ts +0 -163
  78. package/tools/split-spec.ts +0 -148
  79. package/tools/test-import.ts +0 -3
  80. package/tools/validate-spec.ts +0 -40
  81. /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
  82. /package/tests/playwright/qb/auth/{tokenRenewal.test.ts → tempTokenRenewal.test.ts} +0 -0
@@ -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,60 @@
1
+ // tests/vitest/qb/auth/mockQuickbaseServer.ts
2
+ import express from "express";
3
+
4
+ export function startMockQuickbaseServer(port: number = 3000) {
5
+ const app = express();
6
+ app.use(express.json());
7
+ let failNextCall = false;
8
+
9
+ app.post("/v1/auth/oauth/token", (req, res) => {
10
+ const { subject_token } = req.body;
11
+ console.log("[Mock Server] Received SSO token request:", req.body);
12
+ if (!subject_token) {
13
+ return res.status(400).json({ message: "Missing SAML token" });
14
+ }
15
+ res.json({
16
+ access_token: "mock-temp-token",
17
+ token_type: "Bearer",
18
+ });
19
+ });
20
+
21
+ app.get("/v1/apps/:appId", (req, res) => {
22
+ const authHeader = req.headers.authorization;
23
+ console.log(
24
+ "[Mock Server] Received getApp request:",
25
+ req.params,
26
+ "Auth:",
27
+ authHeader
28
+ );
29
+ if (!authHeader || !authHeader.startsWith("QB-TEMP-TOKEN")) {
30
+ return res.status(401).json({ message: "Unauthorized" });
31
+ }
32
+ if (failNextCall) {
33
+ failNextCall = false;
34
+ return res.status(401).json({ message: "Token expired" });
35
+ }
36
+ res.json({
37
+ id: req.params.appId,
38
+ name: "Mock App",
39
+ created: "2025-03-19T12:00:00Z",
40
+ updated: "2025-03-19T12:00:00Z",
41
+ });
42
+ });
43
+
44
+ app.post("/mock/toggle-fail", (req, res) => {
45
+ failNextCall = true;
46
+ console.log("[Mock Server] Toggling next getApp call to fail with 401");
47
+ res.sendStatus(200);
48
+ });
49
+
50
+ const server = app.listen(port, () => {
51
+ console.log(`Mock QuickBase server running on port ${port}`);
52
+ });
53
+
54
+ return {
55
+ server,
56
+ close: () => server.close(),
57
+ toggleFail: () =>
58
+ fetch(`http://localhost:${port}/mock/toggle-fail`, { method: "POST" }),
59
+ };
60
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, it, vi, expect, beforeEach } from "vitest";
2
+ import { quickbase, QuickbaseClient } from "../../../../src/quickbaseClient";
3
+
4
+ describe("QuickbaseClient Integration - User Token Retry on 401", () => {
5
+ let client: QuickbaseClient;
6
+
7
+ beforeEach(() => {
8
+ const userToken = process.env.QB_USER_TOKEN;
9
+ const realm = process.env.QB_REALM;
10
+ const tableId = process.env.QB_TABLE_ID_1; // Consistent env var name
11
+
12
+ if (!userToken) {
13
+ throw new Error(
14
+ "QB_USER_TOKEN environment variable is required for integration test"
15
+ );
16
+ }
17
+ if (!tableId) {
18
+ throw new Error(
19
+ "QB_TABLE_ID_1 environment variable is required for integration test"
20
+ );
21
+ }
22
+
23
+ const config = {
24
+ realm,
25
+ userToken,
26
+ debug: true,
27
+ };
28
+ console.log("[quickbaseTest] Config:", {
29
+ realm,
30
+ userToken: userToken.substring(0, 10) + "...",
31
+ tableId, // Log to confirm
32
+ debug: true,
33
+ });
34
+ client = quickbase(config);
35
+ });
36
+
37
+ it(
38
+ "successfully retrieves fields with user token, retrying on transient 401 if needed",
39
+ async () => {
40
+ const consoleSpy = vi.spyOn(console, "log");
41
+
42
+ const tableId = process.env.QB_TABLE_ID_1!; // Match beforeEach
43
+ let fields;
44
+ try {
45
+ fields = await client.getFields({ tableId });
46
+ } catch (error) {
47
+ console.error("[integrationTest] Failed to retrieve fields:", error);
48
+ throw error;
49
+ }
50
+
51
+ expect(fields).toBeInstanceOf(Array);
52
+ expect(fields.length).toBeGreaterThan(0);
53
+ expect(fields[0]).toHaveProperty("id");
54
+ expect(fields[0]).toHaveProperty("label");
55
+
56
+ console.log("[integrationTest] Fields retrieved:", fields.length);
57
+
58
+ const retryLog = consoleSpy.mock.calls.some((call) =>
59
+ call[0].includes("Retrying getFields with existing user token")
60
+ );
61
+ if (retryLog) {
62
+ console.log("[integrationTest] Retry on 401 detected");
63
+ } else {
64
+ console.log(
65
+ "[integrationTest] No 401 retries observed (expected if no transient failure)"
66
+ );
67
+ }
68
+
69
+ consoleSpy.mockRestore();
70
+ },
71
+ { timeout: 10000 }
72
+ );
73
+ });
@@ -0,0 +1,65 @@
1
+ // tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts
2
+
3
+ import { describe, it, expect, beforeAll, afterAll, vi } from "vitest"; // Add 'vi' to imports
4
+ import { quickbase } from "../../../../src/quickbaseClient";
5
+ import { startMockQuickbaseServer } from "./mockQuickbaseServer";
6
+
7
+ describe("QuickbaseClient Integration - SSO with Mock Server", () => {
8
+ let mockServer: ReturnType<typeof startMockQuickbaseServer>;
9
+ const mockBaseUrl = "http://localhost:3000";
10
+
11
+ beforeAll(async () => {
12
+ mockServer = startMockQuickbaseServer(3000);
13
+ await new Promise((resolve) => setTimeout(resolve, 500));
14
+ console.log("Server setup complete, port 3000 should be listening");
15
+ });
16
+
17
+ afterAll(() => {
18
+ mockServer.close();
19
+ console.log("Server closed");
20
+ });
21
+
22
+ it(
23
+ "uses SSO token from mock server and retries on 401",
24
+ { timeout: 10000 }, // Move timeout to second argument
25
+ async () => {
26
+ const consoleSpy = vi.spyOn(console, "log"); // Spy on console.log to capture logs
27
+ const client = quickbase({
28
+ realm: "testrealm",
29
+ samlToken: "mock-saml-token",
30
+ useSso: true,
31
+ fetchApi: (url, options) => {
32
+ const rewrittenUrl = url.replace(
33
+ "https://api.quickbase.com/v1",
34
+ mockBaseUrl + "/v1"
35
+ );
36
+ console.log(
37
+ "[fetchApi] Requesting:",
38
+ rewrittenUrl,
39
+ "Options:",
40
+ options
41
+ );
42
+ return fetch(rewrittenUrl, options);
43
+ },
44
+ debug: true,
45
+ });
46
+
47
+ await mockServer.toggleFail();
48
+ const response = await client.getApp({ appId: "mockappid" });
49
+
50
+ expect(response).toBeDefined();
51
+ expect(response.id).toBe("mockappid");
52
+ expect(response.name).toBe("Mock App");
53
+
54
+ // Verify retry happened
55
+ const retryLog = consoleSpy.mock.calls.some((call) =>
56
+ call[0].includes(
57
+ "Authorization error for getApp (SSO), refreshing token"
58
+ )
59
+ );
60
+ expect(retryLog).toBe(true); // Ensure retry logic was triggered
61
+
62
+ consoleSpy.mockRestore();
63
+ }
64
+ );
65
+ });
@@ -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);
@@ -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,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
+ });