quickbase-js 1.0.1-beta.0
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 +7 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/build.js +66 -0
- package/dist/quickbaseClient.d.ts +3888 -0
- package/dist/quickbaseClient.js +5812 -0
- package/dist/quickbaseClient.js.map +1 -0
- package/examples/react.js +22 -0
- package/examples/vanillajs.html +23 -0
- package/logGen.yaml +1820 -0
- package/package.json +72 -0
- package/playwright.config.ts +37 -0
- package/rollup.config.js +70 -0
- package/src/code-generation/fix-spec-definitions.ts +136 -0
- package/src/code-generation/fix-spec-main.ts +176 -0
- package/src/code-generation/fix-spec-paths.ts +163 -0
- package/src/code-generation/generate-unified-interface.ts +152 -0
- package/src/code-generation/output/quickbase-fixed.json +8435 -0
- package/src/code-generation/regenerate-client.ts +110 -0
- package/src/generated-unified/QuickbaseClient.ts +59 -0
- package/src/index.ts +1 -0
- package/src/log-gen.ts +122 -0
- package/src/quickbaseClient.ts +320 -0
- package/src/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
- package/src/tokenCache.ts +51 -0
- package/src/utils.ts +5 -0
- package/test-artifacts/.last-run.json +4 -0
- package/test-artifacts/test-results.json +1801 -0
- package/test-results.json +2050 -0
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +189 -0
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +217 -0
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +151 -0
- package/tests/playwright/qb/auth/getTempToken.test.ts +180 -0
- package/tests/playwright/qb/auth/tokenPrefetch.test.ts +192 -0
- package/tests/playwright/qb/auth/tokenRenewal.test.ts +110 -0
- package/tests/setup.ts +19 -0
- package/tests/vitest/qb/apps/getApp.test.ts +46 -0
- package/tests/vitest/qb/fields/getFields.test.ts +234 -0
- package/tests/vitest/qb/index.ts +4 -0
- package/tests/vitest/qb/tables/getAppTables.test.ts +67 -0
- package/tests/vitest/unit/apps/getApp.test.ts +81 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +59 -0
- package/tests/vitest/unit/auth/getTempToken.test.ts +83 -0
- package/tests/vitest/unit/auth/retryOn401.test.ts +72 -0
- package/tests/vitest/unit/auth/two401s.test.ts +77 -0
- package/tests/vitest/unit/fields/getFields.test.ts +115 -0
- package/tests/vitest/unit/index.ts +4 -0
- package/tests/vitest/unit/tables/getAppTables.test.ts +72 -0
- package/text.txt +104 -0
- package/tsconfig.json +28 -0
- package/versionrc.json +9 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// tests/integration/auth/getTempToken.test.ts
|
|
2
|
+
import { test, expect } from "@playwright/test";
|
|
3
|
+
import { quickbaseClient } from "../../../../src/quickbaseClient.ts";
|
|
4
|
+
import fetch from "node-fetch";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const loginToQuickbase = async (
|
|
10
|
+
page: import("@playwright/test").Page,
|
|
11
|
+
quickbaseUrl: string,
|
|
12
|
+
username: string,
|
|
13
|
+
password: string
|
|
14
|
+
): Promise<boolean> => {
|
|
15
|
+
console.log("Logging in to QuickBase");
|
|
16
|
+
let loginSuccess = false;
|
|
17
|
+
const maxLoginAttempts = 3;
|
|
18
|
+
let loginAttempt = 0;
|
|
19
|
+
|
|
20
|
+
while (!loginSuccess && loginAttempt < maxLoginAttempts) {
|
|
21
|
+
try {
|
|
22
|
+
loginAttempt++;
|
|
23
|
+
if (loginAttempt > 1) console.log(`Login attempt ${loginAttempt}`);
|
|
24
|
+
|
|
25
|
+
await page.goto(quickbaseUrl, { timeout: 60000 });
|
|
26
|
+
await page.waitForSelector("input[name='loginid']", { timeout: 60000 });
|
|
27
|
+
await page.fill("input[name='loginid']", username);
|
|
28
|
+
await page.fill("input[name='password']", password);
|
|
29
|
+
await page.click("#signin");
|
|
30
|
+
await page.waitForURL(`https://*.quickbase.com/**`, {
|
|
31
|
+
waitUntil: "networkidle",
|
|
32
|
+
timeout: 60000,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const loginError = await page.$(".login-error");
|
|
36
|
+
if (loginError) {
|
|
37
|
+
throw new Error("Login failed. Please check your credentials.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log("Signed In to QuickBase.");
|
|
41
|
+
loginSuccess = true;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(
|
|
44
|
+
`Login attempt ${loginAttempt} failed: ${(error as Error).message}`
|
|
45
|
+
);
|
|
46
|
+
if (loginAttempt >= maxLoginAttempts) {
|
|
47
|
+
console.error("Max login attempts reached. Exiting.");
|
|
48
|
+
await page.context().browser()!.close();
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return loginSuccess;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
test.describe("QuickbaseClient Integration - getApp with Temp Tokens", () => {
|
|
58
|
+
test("uses temp tokens automatically for getApp in browser", async ({
|
|
59
|
+
page,
|
|
60
|
+
}) => {
|
|
61
|
+
const realm = process.env.QB_REALM;
|
|
62
|
+
const appId = process.env.QB_APP_ID;
|
|
63
|
+
const username = process.env.QB_USERNAME;
|
|
64
|
+
const password = process.env.QB_PASSWORD;
|
|
65
|
+
|
|
66
|
+
if (!realm) throw new Error("QB_REALM is not defined in .env");
|
|
67
|
+
if (!appId) throw new Error("QB_APP_ID is not defined in .env");
|
|
68
|
+
if (!username) throw new Error("QB_USERNAME is not defined in .env");
|
|
69
|
+
if (!password) throw new Error("QB_PASSWORD is not defined in .env");
|
|
70
|
+
|
|
71
|
+
// Login to QuickBase
|
|
72
|
+
const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
|
|
73
|
+
const loginSuccess = await loginToQuickbase(
|
|
74
|
+
page,
|
|
75
|
+
quickbaseUrl,
|
|
76
|
+
username,
|
|
77
|
+
password
|
|
78
|
+
);
|
|
79
|
+
if (!loginSuccess) {
|
|
80
|
+
throw new Error("Failed to log in to QuickBase after max attempts.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Navigate to the app page to ensure session context
|
|
84
|
+
await page.goto(`https://${realm}.quickbase.com/db/${appId}`, {
|
|
85
|
+
waitUntil: "networkidle",
|
|
86
|
+
timeout: 60000,
|
|
87
|
+
});
|
|
88
|
+
console.log("Post-login URL after app navigation:", page.url());
|
|
89
|
+
|
|
90
|
+
// Define browser fetch function
|
|
91
|
+
const browserFetch = async (url: string, init?: RequestInit) => {
|
|
92
|
+
const response = await page.evaluate(
|
|
93
|
+
async ([fetchUrl, fetchInit]) => {
|
|
94
|
+
const res = await fetch(fetchUrl, {
|
|
95
|
+
...fetchInit,
|
|
96
|
+
credentials: "include",
|
|
97
|
+
});
|
|
98
|
+
const body = await res.text();
|
|
99
|
+
return {
|
|
100
|
+
ok: res.ok,
|
|
101
|
+
status: res.status,
|
|
102
|
+
statusText: res.statusText,
|
|
103
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
104
|
+
body,
|
|
105
|
+
};
|
|
106
|
+
},
|
|
107
|
+
[url, init] as [string, RequestInit]
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
console.log(`Raw response from ${url}:`, {
|
|
111
|
+
status: response.status,
|
|
112
|
+
statusText: response.statusText,
|
|
113
|
+
body: response.body,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const fetchResponse = new Response(response.body || null, {
|
|
117
|
+
status: response.status,
|
|
118
|
+
statusText: response.statusText,
|
|
119
|
+
headers: response.headers,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
fetchResponse.json = async () => {
|
|
123
|
+
if (!response.body) throw new Error("Empty response body from API");
|
|
124
|
+
try {
|
|
125
|
+
return JSON.parse(response.body);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
throw new SyntaxError(`Invalid JSON response: ${response.body}`);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return fetchResponse;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Single client with conditional fetch
|
|
135
|
+
const client = quickbaseClient({
|
|
136
|
+
realm,
|
|
137
|
+
useTempTokens: true,
|
|
138
|
+
debug: true,
|
|
139
|
+
fetchApi: async (url, init) => {
|
|
140
|
+
if (url.includes("/auth/temporary/")) {
|
|
141
|
+
return browserFetch(url, init); // Use browser context for temp token
|
|
142
|
+
}
|
|
143
|
+
console.log(`Fetching ${url} with init:`, init);
|
|
144
|
+
const response = await fetch(url, init as RequestInit); // Node-fetch for API calls
|
|
145
|
+
const body = await response.text();
|
|
146
|
+
|
|
147
|
+
console.log(`Raw response from ${url}:`, {
|
|
148
|
+
status: response.status,
|
|
149
|
+
statusText: response.statusText,
|
|
150
|
+
body,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const fetchResponse = new Response(body || null, {
|
|
154
|
+
status: response.status,
|
|
155
|
+
statusText: response.statusText,
|
|
156
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
fetchResponse.json = async () => {
|
|
160
|
+
if (!body) throw new Error("Empty response body from API");
|
|
161
|
+
try {
|
|
162
|
+
return JSON.parse(body);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
throw new SyntaxError(`Invalid JSON response: ${body}`);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
return fetchResponse;
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Call getApp directly, letting the library handle temp token logic
|
|
173
|
+
const appResult = await client.getApp({ appId });
|
|
174
|
+
console.log("App response using auto-fetched temp token:", appResult);
|
|
175
|
+
|
|
176
|
+
// Validate the app response
|
|
177
|
+
expect(appResult).toHaveProperty("id", appId);
|
|
178
|
+
expect(appResult).toHaveProperty("name");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// tests/playwright/qb/auth/tokenPrefetch.test.ts
|
|
2
|
+
import { test, expect } from "@playwright/test";
|
|
3
|
+
import { quickbaseClient } from "../../../../src/quickbaseClient.ts";
|
|
4
|
+
import fetch from "node-fetch";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const loginToQuickbase = async (
|
|
10
|
+
page: import("@playwright/test").Page,
|
|
11
|
+
quickbaseUrl: string,
|
|
12
|
+
username: string,
|
|
13
|
+
password: string
|
|
14
|
+
): Promise<boolean> => {
|
|
15
|
+
console.log("Logging in to QuickBase");
|
|
16
|
+
let loginSuccess = false;
|
|
17
|
+
const maxLoginAttempts = 3;
|
|
18
|
+
let loginAttempt = 0;
|
|
19
|
+
|
|
20
|
+
while (!loginSuccess && loginAttempt < maxLoginAttempts) {
|
|
21
|
+
try {
|
|
22
|
+
loginAttempt++;
|
|
23
|
+
if (loginAttempt > 1) console.log(`Login attempt ${loginAttempt}`);
|
|
24
|
+
|
|
25
|
+
await page.goto(quickbaseUrl, { timeout: 60000 });
|
|
26
|
+
await page.waitForSelector("input[name='loginid']", { timeout: 60000 });
|
|
27
|
+
await page.fill("input[name='loginid']", username);
|
|
28
|
+
await page.fill("input[name='password']", password);
|
|
29
|
+
await page.click("#signin");
|
|
30
|
+
await page.waitForURL(`https://*.quickbase.com/**`, {
|
|
31
|
+
waitUntil: "networkidle",
|
|
32
|
+
timeout: 60000,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const loginError = await page.$(".login-error");
|
|
36
|
+
if (loginError) {
|
|
37
|
+
throw new Error("Login failed. Please check your credentials.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log("Signed In to QuickBase.");
|
|
41
|
+
loginSuccess = true;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(
|
|
44
|
+
`Login attempt ${loginAttempt} failed: ${(error as Error).message}`
|
|
45
|
+
);
|
|
46
|
+
if (loginAttempt >= maxLoginAttempts) {
|
|
47
|
+
console.error("Max login attempts reached. Exiting.");
|
|
48
|
+
await page.context().browser()!.close();
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return loginSuccess;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
test.describe("QuickbaseClient Integration - Token Pre-Fetch and Cache Reuse", () => {
|
|
58
|
+
test("pre-fetches token and reuses it for subsequent API calls", async ({
|
|
59
|
+
page,
|
|
60
|
+
}) => {
|
|
61
|
+
const realm = process.env.QB_REALM;
|
|
62
|
+
const appId = process.env.QB_APP_ID;
|
|
63
|
+
const username = process.env.QB_USERNAME;
|
|
64
|
+
const password = process.env.QB_PASSWORD;
|
|
65
|
+
|
|
66
|
+
if (!realm) throw new Error("QB_REALM is not defined in .env");
|
|
67
|
+
if (!appId) throw new Error("QB_APP_ID is not defined in .env");
|
|
68
|
+
if (!username) throw new Error("QB_USERNAME is not defined in .env");
|
|
69
|
+
if (!password) throw new Error("QB_PASSWORD is not defined in .env");
|
|
70
|
+
|
|
71
|
+
// Login to QuickBase
|
|
72
|
+
const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
|
|
73
|
+
const loginSuccess = await loginToQuickbase(
|
|
74
|
+
page,
|
|
75
|
+
quickbaseUrl,
|
|
76
|
+
username,
|
|
77
|
+
password
|
|
78
|
+
);
|
|
79
|
+
if (!loginSuccess) {
|
|
80
|
+
throw new Error("Failed to log in to QuickBase after max attempts.");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Navigate to the app page to ensure session context
|
|
84
|
+
await page.goto(`https://${realm}.quickbase.com/db/${appId}`, {
|
|
85
|
+
waitUntil: "networkidle",
|
|
86
|
+
timeout: 60000,
|
|
87
|
+
});
|
|
88
|
+
console.log("Post-login URL after app navigation:", page.url());
|
|
89
|
+
|
|
90
|
+
// Track tokens and call counts
|
|
91
|
+
let callCount = 0;
|
|
92
|
+
let cachedToken: string | null = null;
|
|
93
|
+
|
|
94
|
+
const browserFetch = async (url: string, init?: RequestInit) => {
|
|
95
|
+
console.log(`browserFetch called for ${url}, callCount: ${callCount}`);
|
|
96
|
+
const response = await page.evaluate(
|
|
97
|
+
async ([fetchUrl, fetchInit]) => {
|
|
98
|
+
const res = await fetch(fetchUrl, {
|
|
99
|
+
...fetchInit,
|
|
100
|
+
credentials: "include",
|
|
101
|
+
});
|
|
102
|
+
const body = await res.text();
|
|
103
|
+
return {
|
|
104
|
+
ok: res.ok,
|
|
105
|
+
status: res.status,
|
|
106
|
+
statusText: res.statusText,
|
|
107
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
108
|
+
body,
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
[url, init] as [string, RequestInit]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
console.log(`Raw response from ${url}:`, {
|
|
115
|
+
status: response.status,
|
|
116
|
+
statusText: response.statusText,
|
|
117
|
+
body: response.body,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const fetchResponse = new Response(response.body || null, {
|
|
121
|
+
status: response.status,
|
|
122
|
+
statusText: response.statusText,
|
|
123
|
+
headers: response.headers,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Capture token from response
|
|
127
|
+
if (url.includes("/auth/temporary/")) {
|
|
128
|
+
const json = await fetchResponse.clone().json();
|
|
129
|
+
const token = json.temporaryAuthorization;
|
|
130
|
+
cachedToken = token; // Store the pre-fetched token
|
|
131
|
+
console.log(`Captured pre-fetched token: ${cachedToken}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return fetchResponse;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const client = quickbaseClient({
|
|
138
|
+
realm,
|
|
139
|
+
useTempTokens: true,
|
|
140
|
+
debug: true,
|
|
141
|
+
fetchApi: async (url, init) => {
|
|
142
|
+
callCount++;
|
|
143
|
+
console.log(`fetchApi called for ${url}, callCount: ${callCount}`);
|
|
144
|
+
|
|
145
|
+
if (url.includes("/auth/temporary/")) {
|
|
146
|
+
return browserFetch(url, init); // Use browser fetch for temp tokens
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Use node-fetch for app calls, ensuring we track reuse
|
|
150
|
+
console.log(`Fetching ${url} with init:`, init);
|
|
151
|
+
const response = await fetch(url, init as RequestInit);
|
|
152
|
+
const body = await response.text();
|
|
153
|
+
|
|
154
|
+
console.log(`Raw response from ${url}:`, {
|
|
155
|
+
status: response.status,
|
|
156
|
+
statusText: response.statusText,
|
|
157
|
+
body,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return new Response(body || null, {
|
|
161
|
+
status: response.status,
|
|
162
|
+
statusText: response.statusText,
|
|
163
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Step 1: Pre-fetch token
|
|
169
|
+
console.log("Pre-fetching token...");
|
|
170
|
+
const tokenResult = await client.getTempTokenDBID({ dbid: appId });
|
|
171
|
+
console.log("Pre-fetched token result:", tokenResult);
|
|
172
|
+
expect(tokenResult).toHaveProperty("temporaryAuthorization");
|
|
173
|
+
expect(cachedToken).toBe(tokenResult.temporaryAuthorization);
|
|
174
|
+
|
|
175
|
+
// Step 2: Call getApp, expecting it to reuse the cached token
|
|
176
|
+
console.log("Calling getApp with pre-fetched token...");
|
|
177
|
+
const appResult = await client.getApp({ appId });
|
|
178
|
+
console.log("App response:", appResult);
|
|
179
|
+
|
|
180
|
+
// Validate the app response
|
|
181
|
+
expect(appResult).toHaveProperty("id", appId);
|
|
182
|
+
expect(appResult).toHaveProperty("name");
|
|
183
|
+
|
|
184
|
+
// Verify call count and token reuse
|
|
185
|
+
console.log(`Final callCount: ${callCount}`);
|
|
186
|
+
expect(callCount).toBe(2); // One for pre-fetch, one for getApp
|
|
187
|
+
expect(cachedToken).toBeDefined();
|
|
188
|
+
|
|
189
|
+
// Check that getApp reused the token by inspecting logs manually or adding a header check if needed
|
|
190
|
+
console.log(`Pre-fetched token: ${cachedToken}`);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// tests/playwright/qb/auth/tokenRenewal.test.ts
|
|
2
|
+
import { test, expect } from "@playwright/test";
|
|
3
|
+
import { quickbaseClient } from "../../../../src/quickbaseClient.ts";
|
|
4
|
+
import dotenv from "dotenv";
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const loginToQuickbase = async (
|
|
9
|
+
page,
|
|
10
|
+
quickbaseUrl,
|
|
11
|
+
username,
|
|
12
|
+
password,
|
|
13
|
+
appUrl
|
|
14
|
+
) => {
|
|
15
|
+
console.log("Logging in to QuickBase");
|
|
16
|
+
await page.goto(quickbaseUrl, { timeout: 60000 });
|
|
17
|
+
await page.waitForSelector("input[name='loginid']", { timeout: 60000 });
|
|
18
|
+
await page.fill("input[name='loginid']", username);
|
|
19
|
+
await page.fill("input[name='password']", password);
|
|
20
|
+
await page.click("#signin");
|
|
21
|
+
await page.waitForURL(`https://*.quickbase.com/**`, {
|
|
22
|
+
waitUntil: "networkidle",
|
|
23
|
+
timeout: 60000,
|
|
24
|
+
});
|
|
25
|
+
console.log("Signed In to QuickBase.");
|
|
26
|
+
await page.goto(appUrl, { waitUntil: "networkidle", timeout: 60000 });
|
|
27
|
+
console.log("Navigated to app URL:", page.url());
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
test.describe("QuickbaseClient Integration - Temp Token Renewal", () => {
|
|
31
|
+
test("renews temp token after 5 minutes with getApp → wait → getApp sequence", async ({
|
|
32
|
+
page,
|
|
33
|
+
}) => {
|
|
34
|
+
const realm = process.env.QB_REALM!;
|
|
35
|
+
const appId = process.env.QB_APP_ID!;
|
|
36
|
+
const username = process.env.QB_USERNAME!;
|
|
37
|
+
const password = process.env.QB_PASSWORD!;
|
|
38
|
+
const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
|
|
39
|
+
const appUrl = `https://${realm}.quickbase.com/db/${appId}`;
|
|
40
|
+
|
|
41
|
+
await loginToQuickbase(page, quickbaseUrl, username, password, appUrl);
|
|
42
|
+
|
|
43
|
+
const browserFetch = async (
|
|
44
|
+
url: string,
|
|
45
|
+
init: RequestInit
|
|
46
|
+
): Promise<Response> => {
|
|
47
|
+
const response = await page.request.fetch(url, {
|
|
48
|
+
method: init.method || "GET",
|
|
49
|
+
headers: {
|
|
50
|
+
...init.headers,
|
|
51
|
+
Referer: `https://${realm}.quickbase.com/db/${appId}`,
|
|
52
|
+
Origin: `https://${realm}.quickbase.com`,
|
|
53
|
+
},
|
|
54
|
+
data: init.body,
|
|
55
|
+
credentials: "include",
|
|
56
|
+
});
|
|
57
|
+
const body = await response.text();
|
|
58
|
+
console.log(
|
|
59
|
+
"Response from:",
|
|
60
|
+
url,
|
|
61
|
+
"status:",
|
|
62
|
+
response.status(),
|
|
63
|
+
"body:",
|
|
64
|
+
body
|
|
65
|
+
);
|
|
66
|
+
return new Response(body, {
|
|
67
|
+
status: response.status(),
|
|
68
|
+
statusText: response.statusText(),
|
|
69
|
+
headers: response.headers(),
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const client = quickbaseClient({
|
|
74
|
+
realm,
|
|
75
|
+
useTempTokens: true,
|
|
76
|
+
debug: true,
|
|
77
|
+
fetchApi: browserFetch,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
console.log("Checking session state before getApp...");
|
|
81
|
+
const cookies = await page.context().cookies();
|
|
82
|
+
const sessionCookie = cookies.find(
|
|
83
|
+
(c) => c.name === "TICKET" || c.name.startsWith("TICKET_")
|
|
84
|
+
);
|
|
85
|
+
console.log(
|
|
86
|
+
"Session cookie present:",
|
|
87
|
+
!!sessionCookie,
|
|
88
|
+
"Cookies:",
|
|
89
|
+
cookies
|
|
90
|
+
);
|
|
91
|
+
if (!sessionCookie) throw new Error("TICKET session cookie not found");
|
|
92
|
+
|
|
93
|
+
await page.waitForTimeout(5000); // Debug delay
|
|
94
|
+
console.log("Cookies after delay:", await page.context().cookies());
|
|
95
|
+
|
|
96
|
+
const appResult1 = await client.getApp({ appId });
|
|
97
|
+
console.log("First getApp response:", appResult1);
|
|
98
|
+
expect(appResult1.id).toBe(appId);
|
|
99
|
+
|
|
100
|
+
console.log("Waiting 5 minutes to test token expiration...");
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000));
|
|
102
|
+
|
|
103
|
+
await page.reload({ waitUntil: "load", timeout: 60000 });
|
|
104
|
+
console.log("Refreshed page after 5 minutes, URL:", page.url());
|
|
105
|
+
|
|
106
|
+
const appResult2 = await client.getApp({ appId });
|
|
107
|
+
console.log("Second getApp response:", appResult2);
|
|
108
|
+
expect(appResult2.id).toBe(appId);
|
|
109
|
+
});
|
|
110
|
+
});
|
package/tests/setup.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { quickbaseClient, QuickbaseConfig } from "../src/quickbaseClient.ts";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import { vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
export const createClient = (
|
|
8
|
+
fetchApi?: any,
|
|
9
|
+
config: Partial<QuickbaseConfig> = {}
|
|
10
|
+
) =>
|
|
11
|
+
quickbaseClient({
|
|
12
|
+
realm: process.env.QB_REALM || "default-realm",
|
|
13
|
+
userToken: process.env.QB_USER_TOKEN || "default-token",
|
|
14
|
+
debug: true,
|
|
15
|
+
fetchApi,
|
|
16
|
+
...config,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const mockFetch = vi.fn();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { createClient } from "../../../setup.ts";
|
|
3
|
+
|
|
4
|
+
describe("QuickbaseClient Integration - getApp", () => {
|
|
5
|
+
const client = createClient();
|
|
6
|
+
|
|
7
|
+
it(
|
|
8
|
+
"fetches real app data from QuickBase",
|
|
9
|
+
async () => {
|
|
10
|
+
const getAppId = process.env.QB_APP_ID;
|
|
11
|
+
if (!getAppId) throw new Error("QB_APP_ID is not defined in .env");
|
|
12
|
+
if (!process.env.QB_REALM)
|
|
13
|
+
throw new Error("QB_REALM is not defined in .env");
|
|
14
|
+
if (!process.env.QB_USER_TOKEN)
|
|
15
|
+
throw new Error("QB_USER_TOKEN is not defined in .env");
|
|
16
|
+
|
|
17
|
+
console.log("Config used:", {
|
|
18
|
+
realm: process.env.QB_REALM,
|
|
19
|
+
userToken: process.env.QB_USER_TOKEN,
|
|
20
|
+
appId: getAppId,
|
|
21
|
+
});
|
|
22
|
+
const result = await client.getApp({ appId: getAppId });
|
|
23
|
+
console.log("Real API response:", result);
|
|
24
|
+
expect(result).toEqual({
|
|
25
|
+
id: getAppId,
|
|
26
|
+
name: "qb-copy",
|
|
27
|
+
created: new Date("2025-02-13T18:22:33Z"),
|
|
28
|
+
updated: new Date("2025-03-04T04:25:51Z"),
|
|
29
|
+
description: "",
|
|
30
|
+
timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
|
|
31
|
+
dateFormat: "MM-DD-YYYY",
|
|
32
|
+
hasEveryoneOnTheInternet: false,
|
|
33
|
+
memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
|
|
34
|
+
securityProperties: {
|
|
35
|
+
allowClone: false,
|
|
36
|
+
allowExport: true,
|
|
37
|
+
enableAppTokens: true,
|
|
38
|
+
hideFromPublic: false,
|
|
39
|
+
mustBeRealmApproved: false,
|
|
40
|
+
useIPFilter: false,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
{ timeout: 10000 }
|
|
45
|
+
);
|
|
46
|
+
});
|