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.
@@ -0,0 +1,215 @@
1
+ // tests/vitest/unit/apps/deleteApp.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 { DeleteAppRequest, DeleteApp200Response } from "@/generated/models";
11
+
12
+ describe("QuickbaseClient Unit - deleteApp", () => {
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 deleteApp method", () => {
25
+ client = createClient(mockFetch, { debug: true });
26
+ expect(typeof client.deleteApp).toBe("function");
27
+ });
28
+
29
+ it("calls deleteApp successfully with user token", async () => {
30
+ client = createClient(mockFetch, { debug: true });
31
+
32
+ const request: DeleteAppRequest = {
33
+ name: "App to Delete",
34
+ };
35
+
36
+ const mockResponse: DeleteApp200Response = {
37
+ deletedAppId: QB_APP_ID,
38
+ };
39
+
40
+ mockFetch.mockResolvedValueOnce({
41
+ ok: true,
42
+ status: 200,
43
+ json: () => Promise.resolve(mockResponse),
44
+ });
45
+
46
+ const result = await client.deleteApp({ appId: QB_APP_ID, body: request });
47
+
48
+ expect(result).toEqual(mockResponse);
49
+ expect(mockFetch).toHaveBeenCalledWith(
50
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
51
+ expect.objectContaining({
52
+ method: "DELETE",
53
+ headers: expect.objectContaining({
54
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
55
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
56
+ "Content-Type": "application/json",
57
+ }),
58
+ body: JSON.stringify(request),
59
+ })
60
+ );
61
+ });
62
+
63
+ it("calls deleteApp successfully with temp token", async () => {
64
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
65
+
66
+ const request: DeleteAppRequest = {
67
+ name: "App to Delete with Temp Token",
68
+ };
69
+
70
+ const mockResponse: DeleteApp200Response = {
71
+ deletedAppId: QB_APP_ID,
72
+ };
73
+
74
+ mockFetch
75
+ .mockResolvedValueOnce({
76
+ ok: true,
77
+ status: 200,
78
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
79
+ })
80
+ .mockResolvedValueOnce({
81
+ ok: true,
82
+ status: 200,
83
+ json: () => Promise.resolve(mockResponse),
84
+ });
85
+
86
+ const result = await client.deleteApp({ appId: QB_APP_ID, body: request });
87
+
88
+ expect(result).toEqual(mockResponse);
89
+ expect(mockFetch).toHaveBeenCalledTimes(2);
90
+ expect(mockFetch).toHaveBeenNthCalledWith(
91
+ 1,
92
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
93
+ expect.objectContaining({
94
+ method: "GET",
95
+ headers: expect.objectContaining({
96
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
97
+ "Content-Type": "application/json",
98
+ }),
99
+ credentials: "include",
100
+ })
101
+ );
102
+ expect(mockFetch).toHaveBeenNthCalledWith(
103
+ 2,
104
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
105
+ expect.objectContaining({
106
+ method: "DELETE",
107
+ headers: expect.objectContaining({
108
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
109
+ Authorization: "QB-TEMP-TOKEN temp_token",
110
+ "Content-Type": "application/json",
111
+ }),
112
+ body: JSON.stringify(request),
113
+ })
114
+ );
115
+ });
116
+
117
+ it("retries successfully after 401 with temp token", async () => {
118
+ client = createClient(mockFetch, { useTempTokens: true, debug: true });
119
+
120
+ const request: DeleteAppRequest = {
121
+ name: "Retry Delete App",
122
+ };
123
+
124
+ const mockResponse: DeleteApp200Response = {
125
+ deletedAppId: QB_APP_ID,
126
+ };
127
+
128
+ mockFetch
129
+ .mockResolvedValueOnce({
130
+ ok: true,
131
+ status: 200,
132
+ json: () =>
133
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
134
+ })
135
+ .mockResolvedValueOnce({
136
+ ok: false,
137
+ status: 401,
138
+ json: () => Promise.resolve({ message: "Unauthorized" }),
139
+ })
140
+ .mockResolvedValueOnce({
141
+ ok: true,
142
+ status: 200,
143
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
144
+ })
145
+ .mockResolvedValueOnce({
146
+ ok: true,
147
+ status: 200,
148
+ json: () => Promise.resolve(mockResponse),
149
+ });
150
+
151
+ const result = await client.deleteApp({ appId: QB_APP_ID, body: request });
152
+
153
+ expect(result).toEqual(mockResponse);
154
+ expect(mockFetch).toHaveBeenCalledTimes(4);
155
+ expect(mockFetch).toHaveBeenNthCalledWith(
156
+ 1,
157
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
158
+ expect.any(Object)
159
+ );
160
+ expect(mockFetch).toHaveBeenNthCalledWith(
161
+ 2,
162
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
163
+ expect.objectContaining({
164
+ headers: expect.objectContaining({
165
+ Authorization: "QB-TEMP-TOKEN initial_token",
166
+ }),
167
+ })
168
+ );
169
+ expect(mockFetch).toHaveBeenNthCalledWith(
170
+ 3,
171
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
172
+ expect.any(Object)
173
+ );
174
+ expect(mockFetch).toHaveBeenNthCalledWith(
175
+ 4,
176
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
177
+ expect.objectContaining({
178
+ headers: expect.objectContaining({
179
+ Authorization: "QB-TEMP-TOKEN new_token",
180
+ }),
181
+ })
182
+ );
183
+ });
184
+
185
+ it("handles 400 error for invalid request", async () => {
186
+ client = createClient(mockFetch, { debug: true });
187
+
188
+ const request: DeleteAppRequest = {
189
+ name: "Wrong Name", // Assuming this doesn’t match the app’s actual name
190
+ };
191
+
192
+ mockFetch.mockResolvedValueOnce({
193
+ ok: false,
194
+ status: 400,
195
+ json: () => Promise.resolve({ message: "Invalid application name" }),
196
+ });
197
+
198
+ await expect(
199
+ client.deleteApp({ appId: QB_APP_ID, body: request })
200
+ ).rejects.toThrow("API Error: Invalid application name (Status: 400)");
201
+
202
+ expect(mockFetch).toHaveBeenCalledWith(
203
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
204
+ expect.objectContaining({
205
+ method: "DELETE",
206
+ headers: expect.objectContaining({
207
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
208
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
209
+ "Content-Type": "application/json",
210
+ }),
211
+ body: JSON.stringify(request),
212
+ })
213
+ );
214
+ });
215
+ });
@@ -0,0 +1,259 @@
1
+ // tests/vitest/unit/relationships/getRelationships.test.ts
2
+ import { describe, it, expect, beforeEach } from "vitest";
3
+ import {
4
+ createClient,
5
+ mockFetch,
6
+ QB_REALM,
7
+ QB_USER_TOKEN,
8
+ QB_TABLE_ID_1,
9
+ QB_TABLE_ID_2,
10
+ } from "@tests/setup.ts";
11
+ import { GetRelationships200Response } from "/home/drew/Projects/quickbase-js/src/generated/models";
12
+
13
+ describe("QuickbaseClient Unit - getRelationships", () => {
14
+ let client: ReturnType<typeof createClient>;
15
+
16
+ beforeEach(() => {
17
+ mockFetch.mockClear();
18
+ client = createClient(mockFetch, { debug: true }); // Consistent with output
19
+ });
20
+
21
+ it("initializes without errors", () => {
22
+ expect(client).toBeDefined();
23
+ });
24
+
25
+ it("has getRelationships method", () => {
26
+ expect(typeof client.getRelationships).toBe("function");
27
+ });
28
+
29
+ it("sends correct GET request and handles successful response", async () => {
30
+ const mockResponse: GetRelationships200Response = {
31
+ metadata: {
32
+ numRelationships: 1,
33
+ skip: 0,
34
+ totalRelationships: 1,
35
+ },
36
+ relationships: [
37
+ {
38
+ id: 6,
39
+ parentTableId: QB_TABLE_ID_1, // Parent table
40
+ childTableId: QB_TABLE_ID_2, // Child table
41
+ foreignKeyField: {
42
+ id: 6,
43
+ label: "Related record",
44
+ type: "numeric",
45
+ },
46
+ isCrossApp: false,
47
+ lookupFields: [
48
+ {
49
+ id: 3,
50
+ label: "Name",
51
+ type: "text",
52
+ },
53
+ ],
54
+ summaryFields: [
55
+ {
56
+ id: 8,
57
+ label: "my summary field",
58
+ type: "numeric",
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ };
64
+
65
+ mockFetch.mockResolvedValueOnce({
66
+ ok: true,
67
+ status: 200,
68
+ json: () => Promise.resolve(mockResponse),
69
+ });
70
+
71
+ const result = await client.getRelationships({
72
+ tableId: QB_TABLE_ID_2,
73
+ skip: 0,
74
+ });
75
+
76
+ expect(mockFetch).toHaveBeenCalledWith(
77
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
78
+ expect.objectContaining({
79
+ method: "GET",
80
+ headers: expect.objectContaining({
81
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
82
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
83
+ "Content-Type": "application/json",
84
+ }),
85
+ })
86
+ );
87
+
88
+ expect(result).toEqual(mockResponse);
89
+ expect(result.metadata.numRelationships).toBe(1);
90
+ expect(result.relationships).toHaveLength(1);
91
+ expect(result.relationships[0].id).toBe(6);
92
+ expect(result.relationships[0].foreignKeyField.label).toBe(
93
+ "Related record"
94
+ );
95
+ });
96
+
97
+ it("handles request without skip parameter", async () => {
98
+ const mockResponse: GetRelationships200Response = {
99
+ metadata: {
100
+ numRelationships: 1,
101
+ skip: 0,
102
+ totalRelationships: 1,
103
+ },
104
+ relationships: [
105
+ {
106
+ id: 6,
107
+ parentTableId: QB_TABLE_ID_1,
108
+ childTableId: QB_TABLE_ID_2,
109
+ foreignKeyField: {
110
+ id: 6,
111
+ label: "Related record",
112
+ type: "numeric",
113
+ },
114
+ isCrossApp: false,
115
+ lookupFields: [],
116
+ summaryFields: [],
117
+ },
118
+ ],
119
+ };
120
+
121
+ mockFetch.mockResolvedValueOnce({
122
+ ok: true,
123
+ status: 200,
124
+ json: () => Promise.resolve(mockResponse),
125
+ });
126
+
127
+ const result = await client.getRelationships({
128
+ tableId: QB_TABLE_ID_2, // Corrected from QB_TABLE_ID_1
129
+ });
130
+
131
+ expect(mockFetch).toHaveBeenCalledWith(
132
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships`,
133
+ expect.objectContaining({
134
+ method: "GET",
135
+ headers: expect.objectContaining({
136
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
137
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
138
+ "Content-Type": "application/json",
139
+ }),
140
+ })
141
+ );
142
+
143
+ expect(result).toEqual(mockResponse);
144
+ });
145
+
146
+ it("handles API error response", async () => {
147
+ mockFetch.mockResolvedValueOnce({
148
+ ok: false,
149
+ status: 404,
150
+ json: () => Promise.resolve({ message: "Table not found" }),
151
+ });
152
+
153
+ await expect(
154
+ client.getRelationships({
155
+ tableId: QB_TABLE_ID_2, // Corrected from QB_TABLE_ID_1
156
+ skip: 0,
157
+ })
158
+ ).rejects.toThrow("API Error: Table not found (Status: 404)");
159
+
160
+ expect(mockFetch).toHaveBeenCalledWith(
161
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
162
+ expect.objectContaining({
163
+ method: "GET",
164
+ headers: expect.objectContaining({
165
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
166
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
167
+ "Content-Type": "application/json",
168
+ }),
169
+ })
170
+ );
171
+ });
172
+
173
+ it("retries with temp token after 401", async () => {
174
+ const mockResponse: GetRelationships200Response = {
175
+ metadata: {
176
+ numRelationships: 1,
177
+ skip: 0,
178
+ totalRelationships: 1,
179
+ },
180
+ relationships: [
181
+ {
182
+ id: 6,
183
+ parentTableId: QB_TABLE_ID_1,
184
+ childTableId: QB_TABLE_ID_2,
185
+ foreignKeyField: {
186
+ id: 6,
187
+ label: "Related record",
188
+ type: "numeric",
189
+ },
190
+ isCrossApp: false,
191
+ },
192
+ ],
193
+ };
194
+
195
+ mockFetch
196
+ .mockResolvedValueOnce({
197
+ ok: true,
198
+ status: 200,
199
+ json: () =>
200
+ Promise.resolve({ temporaryAuthorization: "initial_token" }),
201
+ })
202
+ .mockResolvedValueOnce({
203
+ ok: false,
204
+ status: 401,
205
+ json: () => Promise.resolve({ message: "Unauthorized" }),
206
+ })
207
+ .mockResolvedValueOnce({
208
+ ok: true,
209
+ status: 200,
210
+ json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
211
+ })
212
+ .mockResolvedValueOnce({
213
+ ok: true,
214
+ status: 200,
215
+ json: () => Promise.resolve(mockResponse),
216
+ });
217
+
218
+ const tempClient = createClient(mockFetch, {
219
+ useTempTokens: true,
220
+ debug: true,
221
+ });
222
+ const result = await tempClient.getRelationships({
223
+ tableId: QB_TABLE_ID_2,
224
+ skip: 0,
225
+ });
226
+
227
+ expect(mockFetch).toHaveBeenCalledTimes(4);
228
+ expect(mockFetch).toHaveBeenNthCalledWith(
229
+ 1,
230
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_2}`,
231
+ expect.any(Object)
232
+ );
233
+ expect(mockFetch).toHaveBeenNthCalledWith(
234
+ 2,
235
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
236
+ expect.objectContaining({
237
+ headers: expect.objectContaining({
238
+ Authorization: "QB-TEMP-TOKEN initial_token",
239
+ }),
240
+ })
241
+ );
242
+ expect(mockFetch).toHaveBeenNthCalledWith(
243
+ 3,
244
+ `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_2}`,
245
+ expect.any(Object)
246
+ );
247
+ expect(mockFetch).toHaveBeenNthCalledWith(
248
+ 4,
249
+ `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
250
+ expect.objectContaining({
251
+ headers: expect.objectContaining({
252
+ Authorization: "QB-TEMP-TOKEN new_token",
253
+ }),
254
+ })
255
+ );
256
+
257
+ expect(result).toEqual(mockResponse);
258
+ });
259
+ });