quickbase-js 1.1.0-beta.1 → 1.1.0-beta.3
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 +32 -22
- package/dist/quickbase.d.ts +850 -161
- package/dist/quickbase.js +1054 -275
- package/dist/quickbase.js.map +1 -1
- package/log-gen.yaml +1136 -1969
- package/package.json +1 -1
- package/src/code-generation/fix-spec-definitions.ts +269 -3
- package/src/code-generation/fix-spec-paths.ts +84 -0
- package/src/code-generation/output/quickbase-fixed.json +380 -358
- package/src/generated-unified/QuickbaseClient.ts +5 -4
- package/src/log-gen.ts +5 -3
- package/src/quickbaseClient.ts +35 -41
- package/tests/setup.ts +5 -1
- package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
- package/tests/vitest/qb/apps/createApp.test.ts +64 -0
- package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
- package/tests/vitest/qb/records/deleteRecords.test.ts +42 -10
- package/tests/vitest/qb/records/runQuery.test.ts +81 -17
- package/tests/vitest/qb/records/upsert.test.ts +33 -3
- package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
- package/tests/vitest/unit/apps/copyApp.test.ts +282 -0
- package/tests/vitest/unit/apps/createApp.test.ts +125 -0
- package/tests/vitest/unit/apps/deleteApp.test.ts +215 -0
- package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// tests/vitest/qb/records/upsert.test.ts
|
|
2
2
|
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
3
3
|
import { quickbase } from "@/quickbaseClient.ts";
|
|
4
|
+
import {
|
|
5
|
+
Upsert200Response,
|
|
6
|
+
Upsert207Response,
|
|
7
|
+
} from "/home/drew/Projects/quickbase-js/src/generated/models";
|
|
4
8
|
|
|
5
9
|
const TABLE_ID = "buwai2zws";
|
|
6
10
|
const MARKER_FIELD_ID = 6; // Text field for marking test records
|
|
@@ -39,7 +43,7 @@ describe("QuickbaseClient - Upsert Integration", () => {
|
|
|
39
43
|
test(
|
|
40
44
|
"creates a new record successfully",
|
|
41
45
|
async () => {
|
|
42
|
-
const response = await qb.upsert({
|
|
46
|
+
const response: Upsert200Response | Upsert207Response = await qb.upsert({
|
|
43
47
|
body: {
|
|
44
48
|
to: TABLE_ID,
|
|
45
49
|
data: [{ [MARKER_FIELD_ID]: { value: "test-marker-upsert" } }],
|
|
@@ -47,12 +51,38 @@ describe("QuickbaseClient - Upsert Integration", () => {
|
|
|
47
51
|
},
|
|
48
52
|
});
|
|
49
53
|
|
|
54
|
+
// Check metadata with null safety
|
|
55
|
+
expect(
|
|
56
|
+
response.metadata,
|
|
57
|
+
"Expected metadata to be defined"
|
|
58
|
+
).toBeDefined();
|
|
59
|
+
if (!response.metadata) {
|
|
60
|
+
throw new Error("Expected metadata to be present in upsert response");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
expect(
|
|
64
|
+
response.metadata.createdRecordIds,
|
|
65
|
+
"Expected createdRecordIds to be defined"
|
|
66
|
+
).toBeDefined();
|
|
67
|
+
if (!response.metadata.createdRecordIds) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"Expected createdRecordIds to be present in upsert response"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
50
72
|
expect(response.metadata.createdRecordIds.length).toBe(1);
|
|
73
|
+
|
|
74
|
+
// Check data with null safety
|
|
75
|
+
expect(response.data, "Expected data to be defined").toBeDefined();
|
|
76
|
+
if (!response.data || response.data.length === 0) {
|
|
77
|
+
throw new Error("Expected at least one record in data");
|
|
78
|
+
}
|
|
51
79
|
expect(response.data).toHaveLength(1);
|
|
52
|
-
expect(response.data[0][MARKER_FIELD_ID]
|
|
80
|
+
expect(response.data[0][MARKER_FIELD_ID]?.value).toBe(
|
|
53
81
|
"test-marker-upsert"
|
|
54
82
|
);
|
|
55
|
-
expect(response.data[0][3]
|
|
83
|
+
expect(response.data[0][3]?.value).toBeDefined(); // Record ID assigned by QuickBase
|
|
84
|
+
|
|
85
|
+
// Check totalNumberOfRecordsProcessed
|
|
56
86
|
expect(response.metadata.totalNumberOfRecordsProcessed).toBe(1);
|
|
57
87
|
},
|
|
58
88
|
{ timeout: 10000 }
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// tests/vitest/qb/tables/getRelationships.test.ts
|
|
2
|
+
import { describe, test, expect } from "vitest";
|
|
3
|
+
import { quickbase } from "/home/drew/Projects/quickbase-js/src/quickbaseClient.ts";
|
|
4
|
+
import { QB_TABLE_ID_1 } from "/home/drew/Projects/quickbase-js/tests/setup.ts";
|
|
5
|
+
import { GetRelationships200Response } from "/home/drew/Projects/quickbase-js/src/generated/models";
|
|
6
|
+
|
|
7
|
+
describe("QuickbaseClient Integration - getRelationships", () => {
|
|
8
|
+
const qb = quickbase({
|
|
9
|
+
realm: process.env.QB_REALM || "builderprogram-dbradford6815",
|
|
10
|
+
userToken: process.env.QB_USER_TOKEN || "",
|
|
11
|
+
debug: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("fetches real relationships from QuickBase", async () => {
|
|
15
|
+
// Ensure environment variables are set
|
|
16
|
+
if (!process.env.QB_REALM)
|
|
17
|
+
throw new Error("QB_REALM is not defined in .env");
|
|
18
|
+
if (!process.env.QB_USER_TOKEN)
|
|
19
|
+
throw new Error("QB_USER_TOKEN is not defined in .env");
|
|
20
|
+
|
|
21
|
+
console.log("Config used:", {
|
|
22
|
+
realm: process.env.QB_REALM,
|
|
23
|
+
userToken: process.env.QB_USER_TOKEN,
|
|
24
|
+
tableId: QB_TABLE_ID_1,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const response: GetRelationships200Response = await qb.getRelationships({
|
|
28
|
+
tableId: QB_TABLE_ID_1, // Query relationships for QB_TABLE_ID_1 (e.g., "buwai2zws")
|
|
29
|
+
skip: 0,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log("Real API response:", JSON.stringify(response, null, 2));
|
|
33
|
+
|
|
34
|
+
// Basic structure validation
|
|
35
|
+
expect(response).toBeDefined();
|
|
36
|
+
expect(response.metadata).toBeDefined();
|
|
37
|
+
expect(response.metadata).toHaveProperty(
|
|
38
|
+
"numRelationships",
|
|
39
|
+
expect.any(Number)
|
|
40
|
+
);
|
|
41
|
+
expect(response.metadata).toHaveProperty("skip", 0);
|
|
42
|
+
expect(response.metadata).toHaveProperty(
|
|
43
|
+
"totalRelationships",
|
|
44
|
+
expect.any(Number)
|
|
45
|
+
);
|
|
46
|
+
expect(response.relationships).toBeDefined();
|
|
47
|
+
expect(Array.isArray(response.relationships)).toBe(true);
|
|
48
|
+
|
|
49
|
+
// If relationships exist, validate the first one
|
|
50
|
+
if (response.relationships.length > 0) {
|
|
51
|
+
const relationship = response.relationships[0];
|
|
52
|
+
expect(relationship).toHaveProperty("id", expect.any(Number));
|
|
53
|
+
expect(relationship).toHaveProperty("parentTableId", expect.any(String));
|
|
54
|
+
expect(relationship).toHaveProperty("childTableId", QB_TABLE_ID_1);
|
|
55
|
+
expect(relationship).toHaveProperty("foreignKeyField");
|
|
56
|
+
expect(relationship.foreignKeyField).toHaveProperty(
|
|
57
|
+
"id",
|
|
58
|
+
expect.any(Number)
|
|
59
|
+
);
|
|
60
|
+
expect(relationship.foreignKeyField).toHaveProperty(
|
|
61
|
+
"label",
|
|
62
|
+
expect.any(String)
|
|
63
|
+
);
|
|
64
|
+
expect(relationship.foreignKeyField).toHaveProperty(
|
|
65
|
+
"type",
|
|
66
|
+
expect.any(String)
|
|
67
|
+
);
|
|
68
|
+
expect(relationship).toHaveProperty("isCrossApp", expect.any(Boolean));
|
|
69
|
+
expect(relationship).toHaveProperty("lookupFields", expect.any(Array));
|
|
70
|
+
|
|
71
|
+
// summaryFields is optional; only validate if present
|
|
72
|
+
if (
|
|
73
|
+
"summaryFields" in relationship &&
|
|
74
|
+
relationship.summaryFields !== undefined
|
|
75
|
+
) {
|
|
76
|
+
expect(Array.isArray(relationship.summaryFields)).toBe(true);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}, 10000); // Timeout as second argument
|
|
80
|
+
});
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// tests/vitest/unit/apps/copyApp.test.ts
|
|
2
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
createClient,
|
|
5
|
+
mockFetch,
|
|
6
|
+
QB_APP_ID,
|
|
7
|
+
QB_REALM,
|
|
8
|
+
QB_USER_TOKEN,
|
|
9
|
+
} from "@tests/setup.ts";
|
|
10
|
+
import { CopyAppRequest, CopyApp200Response } from "@/generated/models";
|
|
11
|
+
|
|
12
|
+
describe("QuickbaseClient Unit - copyApp", () => {
|
|
13
|
+
let client: ReturnType<typeof createClient>;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
mockFetch.mockClear();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("initializes without errors", () => {
|
|
20
|
+
client = createClient(mockFetch, { debug: true });
|
|
21
|
+
expect(client).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("has copyApp method", () => {
|
|
25
|
+
client = createClient(mockFetch, { debug: true });
|
|
26
|
+
expect(typeof client.copyApp).toBe("function");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("calls copyApp successfully with user token", async () => {
|
|
30
|
+
client = createClient(mockFetch, { debug: true });
|
|
31
|
+
|
|
32
|
+
const request: CopyAppRequest = {
|
|
33
|
+
name: "Copied App",
|
|
34
|
+
description: "A copy of the original app",
|
|
35
|
+
properties: {
|
|
36
|
+
keepData: false,
|
|
37
|
+
excludeFiles: true,
|
|
38
|
+
usersAndRoles: false,
|
|
39
|
+
assignUserToken: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const mockResponse: CopyApp200Response = {
|
|
44
|
+
id: "bpqe82s1",
|
|
45
|
+
name: "Copied App",
|
|
46
|
+
description: "A copy of the original app",
|
|
47
|
+
created: new Date("2025-03-11T10:00:00Z"), // Changed to Date object
|
|
48
|
+
updated: new Date("2025-03-11T10:00:00Z"), // Changed to Date object
|
|
49
|
+
dateFormat: "MM-DD-YYYY",
|
|
50
|
+
timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
|
|
51
|
+
hasEveryoneOnTheInternet: false,
|
|
52
|
+
ancestorId: QB_APP_ID,
|
|
53
|
+
dataClassification: "None",
|
|
54
|
+
variables: [],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
mockFetch.mockResolvedValueOnce({
|
|
58
|
+
ok: true,
|
|
59
|
+
status: 200,
|
|
60
|
+
json: () =>
|
|
61
|
+
Promise.resolve({
|
|
62
|
+
...mockResponse,
|
|
63
|
+
created: "2025-03-11T10:00:00Z", // API returns string
|
|
64
|
+
updated: "2025-03-11T10:00:00Z", // API returns string
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const result = await client.copyApp({ appId: QB_APP_ID, body: request });
|
|
69
|
+
|
|
70
|
+
expect(result).toEqual(mockResponse);
|
|
71
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
72
|
+
`https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
|
|
73
|
+
expect.objectContaining({
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: expect.objectContaining({
|
|
76
|
+
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
77
|
+
Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
}),
|
|
80
|
+
body: JSON.stringify(request),
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("calls copyApp successfully with temp token", async () => {
|
|
86
|
+
client = createClient(mockFetch, { useTempTokens: true, debug: true });
|
|
87
|
+
|
|
88
|
+
const request: CopyAppRequest = {
|
|
89
|
+
name: "Copied App with Temp Token",
|
|
90
|
+
description: "A copy using temp token",
|
|
91
|
+
properties: {
|
|
92
|
+
keepData: true,
|
|
93
|
+
excludeFiles: false,
|
|
94
|
+
usersAndRoles: true,
|
|
95
|
+
assignUserToken: false,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const mockResponse: CopyApp200Response = {
|
|
100
|
+
id: "bpqe82s2",
|
|
101
|
+
name: "Copied App with Temp Token",
|
|
102
|
+
description: "A copy using temp token",
|
|
103
|
+
created: new Date("2025-03-11T11:00:00Z"), // Changed to Date object
|
|
104
|
+
updated: new Date("2025-03-11T11:00:00Z"), // Changed to Date object
|
|
105
|
+
dateFormat: "MM-DD-YYYY",
|
|
106
|
+
timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
|
|
107
|
+
hasEveryoneOnTheInternet: true,
|
|
108
|
+
ancestorId: QB_APP_ID,
|
|
109
|
+
dataClassification: "None",
|
|
110
|
+
variables: [],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
mockFetch
|
|
114
|
+
.mockResolvedValueOnce({
|
|
115
|
+
ok: true,
|
|
116
|
+
status: 200,
|
|
117
|
+
json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
|
|
118
|
+
})
|
|
119
|
+
.mockResolvedValueOnce({
|
|
120
|
+
ok: true,
|
|
121
|
+
status: 200,
|
|
122
|
+
json: () =>
|
|
123
|
+
Promise.resolve({
|
|
124
|
+
...mockResponse,
|
|
125
|
+
created: "2025-03-11T11:00:00Z", // API returns string
|
|
126
|
+
updated: "2025-03-11T11:00:00Z", // API returns string
|
|
127
|
+
}),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const result = await client.copyApp({ appId: QB_APP_ID, body: request });
|
|
131
|
+
|
|
132
|
+
expect(result).toEqual(mockResponse);
|
|
133
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
134
|
+
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
135
|
+
1,
|
|
136
|
+
`https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
|
|
137
|
+
expect.objectContaining({
|
|
138
|
+
method: "GET",
|
|
139
|
+
headers: expect.objectContaining({
|
|
140
|
+
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
141
|
+
"Content-Type": "application/json",
|
|
142
|
+
}),
|
|
143
|
+
credentials: "include",
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
147
|
+
2,
|
|
148
|
+
`https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
|
|
149
|
+
expect.objectContaining({
|
|
150
|
+
method: "POST",
|
|
151
|
+
headers: expect.objectContaining({
|
|
152
|
+
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
153
|
+
Authorization: "QB-TEMP-TOKEN temp_token",
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
}),
|
|
156
|
+
body: JSON.stringify(request),
|
|
157
|
+
})
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("retries successfully after 401 with temp token", async () => {
|
|
162
|
+
client = createClient(mockFetch, { useTempTokens: true, debug: true });
|
|
163
|
+
|
|
164
|
+
const request: CopyAppRequest = {
|
|
165
|
+
name: "Retry Copied App",
|
|
166
|
+
description: "Retry after 401",
|
|
167
|
+
properties: {
|
|
168
|
+
keepData: false,
|
|
169
|
+
excludeFiles: true,
|
|
170
|
+
usersAndRoles: false,
|
|
171
|
+
assignUserToken: true,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const mockResponse: CopyApp200Response = {
|
|
176
|
+
id: "bpqe82s3",
|
|
177
|
+
name: "Retry Copied App",
|
|
178
|
+
description: "Retry after 401",
|
|
179
|
+
created: new Date("2025-03-11T12:00:00Z"), // Changed to Date object
|
|
180
|
+
updated: new Date("2025-03-11T12:00:00Z"), // Changed to Date object
|
|
181
|
+
dateFormat: "MM-DD-YYYY",
|
|
182
|
+
timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
|
|
183
|
+
hasEveryoneOnTheInternet: false,
|
|
184
|
+
ancestorId: QB_APP_ID,
|
|
185
|
+
dataClassification: "None",
|
|
186
|
+
variables: [],
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
mockFetch
|
|
190
|
+
.mockResolvedValueOnce({
|
|
191
|
+
ok: true,
|
|
192
|
+
status: 200,
|
|
193
|
+
json: () =>
|
|
194
|
+
Promise.resolve({ temporaryAuthorization: "initial_token" }),
|
|
195
|
+
})
|
|
196
|
+
.mockResolvedValueOnce({
|
|
197
|
+
ok: false,
|
|
198
|
+
status: 401,
|
|
199
|
+
json: () => Promise.resolve({ message: "Unauthorized" }),
|
|
200
|
+
})
|
|
201
|
+
.mockResolvedValueOnce({
|
|
202
|
+
ok: true,
|
|
203
|
+
status: 200,
|
|
204
|
+
json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
|
|
205
|
+
})
|
|
206
|
+
.mockResolvedValueOnce({
|
|
207
|
+
ok: true,
|
|
208
|
+
status: 200,
|
|
209
|
+
json: () =>
|
|
210
|
+
Promise.resolve({
|
|
211
|
+
...mockResponse,
|
|
212
|
+
created: "2025-03-11T12:00:00Z", // API returns string
|
|
213
|
+
updated: "2025-03-11T12:00:00Z", // API returns string
|
|
214
|
+
}),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = await client.copyApp({ appId: QB_APP_ID, body: request });
|
|
218
|
+
|
|
219
|
+
expect(result).toEqual(mockResponse);
|
|
220
|
+
expect(mockFetch).toHaveBeenCalledTimes(4);
|
|
221
|
+
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
222
|
+
1,
|
|
223
|
+
`https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
|
|
224
|
+
expect.any(Object)
|
|
225
|
+
);
|
|
226
|
+
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
227
|
+
2,
|
|
228
|
+
`https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
|
|
229
|
+
expect.objectContaining({
|
|
230
|
+
headers: expect.objectContaining({
|
|
231
|
+
Authorization: "QB-TEMP-TOKEN initial_token",
|
|
232
|
+
}),
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
236
|
+
3,
|
|
237
|
+
`https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
|
|
238
|
+
expect.any(Object)
|
|
239
|
+
);
|
|
240
|
+
expect(mockFetch).toHaveBeenNthCalledWith(
|
|
241
|
+
4,
|
|
242
|
+
`https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
|
|
243
|
+
expect.objectContaining({
|
|
244
|
+
headers: expect.objectContaining({
|
|
245
|
+
Authorization: "QB-TEMP-TOKEN new_token",
|
|
246
|
+
}),
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("handles 400 error for invalid request", async () => {
|
|
252
|
+
client = createClient(mockFetch, { debug: true });
|
|
253
|
+
|
|
254
|
+
const request: CopyAppRequest = {
|
|
255
|
+
name: "", // Invalid: name is required
|
|
256
|
+
description: "Invalid request",
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
mockFetch.mockResolvedValueOnce({
|
|
260
|
+
ok: false,
|
|
261
|
+
status: 400,
|
|
262
|
+
json: () => Promise.resolve({ message: "Name is required" }),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
await expect(
|
|
266
|
+
client.copyApp({ appId: QB_APP_ID, body: request })
|
|
267
|
+
).rejects.toThrow("API Error: Name is required (Status: 400)");
|
|
268
|
+
|
|
269
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
270
|
+
`https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
|
|
271
|
+
expect.objectContaining({
|
|
272
|
+
method: "POST",
|
|
273
|
+
headers: expect.objectContaining({
|
|
274
|
+
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
275
|
+
Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
}),
|
|
278
|
+
body: JSON.stringify(request),
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// tests/vitest/unit/apps/createApp.test.ts
|
|
2
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
createClient,
|
|
5
|
+
mockFetch,
|
|
6
|
+
QB_REALM,
|
|
7
|
+
QB_USER_TOKEN,
|
|
8
|
+
} from "@tests/setup.ts";
|
|
9
|
+
import { CreateAppRequest, CreateApp200Response } from "@/generated/models";
|
|
10
|
+
|
|
11
|
+
describe("QuickbaseClient Unit - createApp", () => {
|
|
12
|
+
let client: ReturnType<typeof createClient>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockFetch.mockClear();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("initializes without errors", () => {
|
|
19
|
+
client = createClient(mockFetch, { debug: true });
|
|
20
|
+
expect(client).toBeDefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("has createApp method", () => {
|
|
24
|
+
client = createClient(mockFetch, { debug: true });
|
|
25
|
+
expect(typeof client.createApp).toBe("function");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("calls createApp successfully with user token", async () => {
|
|
29
|
+
client = createClient(mockFetch, { debug: true });
|
|
30
|
+
|
|
31
|
+
const request: CreateAppRequest = {
|
|
32
|
+
name: "New App",
|
|
33
|
+
description: "A new app for testing",
|
|
34
|
+
assignToken: true,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const mockResponse: CreateApp200Response = {
|
|
38
|
+
id: "new-app-id",
|
|
39
|
+
name: "New App",
|
|
40
|
+
description: "A new app for testing",
|
|
41
|
+
created: new Date("2025-03-11T10:00:00Z"),
|
|
42
|
+
updated: new Date("2025-03-11T10:00:00Z"),
|
|
43
|
+
dateFormat: "MM-DD-YYYY",
|
|
44
|
+
timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
|
|
45
|
+
hasEveryoneOnTheInternet: false,
|
|
46
|
+
variables: [],
|
|
47
|
+
dataClassification: "None",
|
|
48
|
+
memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
|
|
49
|
+
securityProperties: {
|
|
50
|
+
allowClone: false,
|
|
51
|
+
allowExport: true,
|
|
52
|
+
enableAppTokens: true,
|
|
53
|
+
hideFromPublic: false,
|
|
54
|
+
mustBeRealmApproved: false,
|
|
55
|
+
useIPFilter: false,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
mockFetch.mockResolvedValueOnce({
|
|
60
|
+
ok: true,
|
|
61
|
+
status: 200,
|
|
62
|
+
json: () =>
|
|
63
|
+
Promise.resolve({
|
|
64
|
+
...mockResponse,
|
|
65
|
+
created: "2025-03-11T10:00:00Z", // API returns string
|
|
66
|
+
updated: "2025-03-11T10:00:00Z", // API returns string
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const result = await client.createApp({ body: request });
|
|
71
|
+
|
|
72
|
+
expect(result).toEqual(mockResponse);
|
|
73
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
74
|
+
`https://api.quickbase.com/v1/apps`,
|
|
75
|
+
expect.objectContaining({
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: expect.objectContaining({
|
|
78
|
+
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
79
|
+
Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
}),
|
|
82
|
+
body: expect.stringContaining('"name":"New App"'),
|
|
83
|
+
credentials: "omit", // Added to match actual behavior
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
// Additional check to ensure body contains all properties regardless of order
|
|
87
|
+
const callArgs = mockFetch.mock.calls[0];
|
|
88
|
+
const body = JSON.parse(callArgs[1].body as string);
|
|
89
|
+
expect(body).toEqual(request);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("handles 400 error for invalid request", async () => {
|
|
93
|
+
client = createClient(mockFetch, { debug: true });
|
|
94
|
+
|
|
95
|
+
const request: CreateAppRequest = {
|
|
96
|
+
name: "", // Invalid: name is required
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
mockFetch.mockImplementationOnce(() =>
|
|
100
|
+
Promise.resolve({
|
|
101
|
+
ok: false,
|
|
102
|
+
status: 400,
|
|
103
|
+
json: () => Promise.resolve({ message: "Name is required" }),
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await expect(client.createApp({ body: request })).rejects.toThrow(
|
|
108
|
+
"API Error: Name is required (Status: 400)"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
112
|
+
`https://api.quickbase.com/v1/apps`,
|
|
113
|
+
expect.objectContaining({
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: expect.objectContaining({
|
|
116
|
+
"QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
|
|
117
|
+
Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
|
|
118
|
+
"Content-Type": "application/json",
|
|
119
|
+
}),
|
|
120
|
+
body: JSON.stringify(request),
|
|
121
|
+
credentials: "omit", // Added to match actual behavior
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
});
|