@budibase/server 2.22.0 → 2.22.1
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/package.json +8 -8
- package/src/api/routes/tests/permissions.spec.ts +5 -2
- package/src/api/routes/tests/row.spec.ts +103 -140
- package/src/api/routes/tests/table.spec.ts +4 -4
- package/src/api/routes/tests/view.spec.ts +50 -0
- package/src/api/routes/tests/viewV2.spec.ts +143 -66
- package/src/tests/utilities/api/viewV2.ts +1 -13
- package/src/sdk/tests/rows/search.spec.ts +0 -21
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/server",
|
|
3
3
|
"email": "hi@budibase.com",
|
|
4
|
-
"version": "2.22.
|
|
4
|
+
"version": "2.22.1",
|
|
5
5
|
"description": "Budibase Web Server",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"repository": {
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"license": "GPL-3.0",
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@apidevtools/swagger-parser": "10.0.3",
|
|
51
|
-
"@budibase/backend-core": "2.22.
|
|
52
|
-
"@budibase/client": "2.22.
|
|
53
|
-
"@budibase/pro": "2.22.
|
|
54
|
-
"@budibase/shared-core": "2.22.
|
|
55
|
-
"@budibase/string-templates": "2.22.
|
|
56
|
-
"@budibase/types": "2.22.
|
|
51
|
+
"@budibase/backend-core": "2.22.1",
|
|
52
|
+
"@budibase/client": "2.22.1",
|
|
53
|
+
"@budibase/pro": "2.22.1",
|
|
54
|
+
"@budibase/shared-core": "2.22.1",
|
|
55
|
+
"@budibase/string-templates": "2.22.1",
|
|
56
|
+
"@budibase/types": "2.22.1",
|
|
57
57
|
"@bull-board/api": "5.10.2",
|
|
58
58
|
"@bull-board/koa": "5.10.2",
|
|
59
59
|
"@elastic/elasticsearch": "7.10.0",
|
|
@@ -192,5 +192,5 @@
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
},
|
|
195
|
-
"gitHead": "
|
|
195
|
+
"gitHead": "b91392adfadf5e27c40a78102046cb3e51a7a792"
|
|
196
196
|
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
ViewV2,
|
|
17
17
|
} from "@budibase/types"
|
|
18
18
|
import * as setup from "./utilities"
|
|
19
|
-
import { mocks } from "@budibase/backend-core/tests"
|
|
19
|
+
import { generator, mocks } from "@budibase/backend-core/tests"
|
|
20
20
|
|
|
21
21
|
const { basicRow } = setup.structures
|
|
22
22
|
const { BUILTIN_ROLE_IDS } = roles
|
|
@@ -44,7 +44,10 @@ describe("/permission", () => {
|
|
|
44
44
|
|
|
45
45
|
table = (await config.createTable()) as typeof table
|
|
46
46
|
row = await config.createRow()
|
|
47
|
-
view = await config.api.viewV2.create({
|
|
47
|
+
view = await config.api.viewV2.create({
|
|
48
|
+
tableId: table._id!,
|
|
49
|
+
name: generator.guid(),
|
|
50
|
+
})
|
|
48
51
|
perms = await config.api.permission.add({
|
|
49
52
|
roleId: STD_ROLE_ID,
|
|
50
53
|
resourceId: table._id,
|
|
@@ -42,6 +42,7 @@ tk.freeze(timestamp)
|
|
|
42
42
|
jest.unmock("mysql2")
|
|
43
43
|
jest.unmock("mysql2/promise")
|
|
44
44
|
jest.unmock("mssql")
|
|
45
|
+
jest.unmock("pg")
|
|
45
46
|
|
|
46
47
|
describe.each([
|
|
47
48
|
["internal", undefined],
|
|
@@ -152,8 +153,8 @@ describe.each([
|
|
|
152
153
|
table = await config.api.table.save(defaultTable())
|
|
153
154
|
})
|
|
154
155
|
|
|
155
|
-
describe("
|
|
156
|
-
it("
|
|
156
|
+
describe("create", () => {
|
|
157
|
+
it("creates a new row successfully", async () => {
|
|
157
158
|
const rowUsage = await getRowUsage()
|
|
158
159
|
const row = await config.api.row.save(table._id!, {
|
|
159
160
|
name: "Test Contact",
|
|
@@ -163,7 +164,44 @@ describe.each([
|
|
|
163
164
|
await assertRowUsage(rowUsage + 1)
|
|
164
165
|
})
|
|
165
166
|
|
|
166
|
-
it("
|
|
167
|
+
it("fails to create a row for a table that does not exist", async () => {
|
|
168
|
+
const rowUsage = await getRowUsage()
|
|
169
|
+
await config.api.row.save("1234567", {}, { status: 404 })
|
|
170
|
+
await assertRowUsage(rowUsage)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it("fails to create a row if required fields are missing", async () => {
|
|
174
|
+
const rowUsage = await getRowUsage()
|
|
175
|
+
const table = await config.api.table.save(
|
|
176
|
+
saveTableRequest({
|
|
177
|
+
schema: {
|
|
178
|
+
required: {
|
|
179
|
+
type: FieldType.STRING,
|
|
180
|
+
name: "required",
|
|
181
|
+
constraints: {
|
|
182
|
+
type: "string",
|
|
183
|
+
presence: true,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
await config.api.row.save(
|
|
190
|
+
table._id!,
|
|
191
|
+
{},
|
|
192
|
+
{
|
|
193
|
+
status: 500,
|
|
194
|
+
body: {
|
|
195
|
+
validationErrors: {
|
|
196
|
+
required: ["can't be blank"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
await assertRowUsage(rowUsage)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it("increment row autoId per create row request", async () => {
|
|
167
205
|
const rowUsage = await getRowUsage()
|
|
168
206
|
|
|
169
207
|
const newTable = await config.api.table.save(
|
|
@@ -198,52 +236,6 @@ describe.each([
|
|
|
198
236
|
await assertRowUsage(rowUsage + 10)
|
|
199
237
|
})
|
|
200
238
|
|
|
201
|
-
it("updates a row successfully", async () => {
|
|
202
|
-
const existing = await config.api.row.save(table._id!, {})
|
|
203
|
-
const rowUsage = await getRowUsage()
|
|
204
|
-
|
|
205
|
-
const res = await config.api.row.save(table._id!, {
|
|
206
|
-
_id: existing._id,
|
|
207
|
-
_rev: existing._rev,
|
|
208
|
-
name: "Updated Name",
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
expect(res.name).toEqual("Updated Name")
|
|
212
|
-
await assertRowUsage(rowUsage)
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it("should load a row", async () => {
|
|
216
|
-
const existing = await config.api.row.save(table._id!, {})
|
|
217
|
-
|
|
218
|
-
const res = await config.api.row.get(table._id!, existing._id!)
|
|
219
|
-
|
|
220
|
-
expect(res).toEqual({
|
|
221
|
-
...existing,
|
|
222
|
-
...defaultRowFields,
|
|
223
|
-
})
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
it("should list all rows for given tableId", async () => {
|
|
227
|
-
const table = await config.api.table.save(defaultTable())
|
|
228
|
-
const rows = await Promise.all([
|
|
229
|
-
config.api.row.save(table._id!, {}),
|
|
230
|
-
config.api.row.save(table._id!, {}),
|
|
231
|
-
])
|
|
232
|
-
|
|
233
|
-
const res = await config.api.row.fetch(table._id!)
|
|
234
|
-
expect(res.map(r => r._id)).toEqual(
|
|
235
|
-
expect.arrayContaining(rows.map(r => r._id))
|
|
236
|
-
)
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it("load should return 404 when row does not exist", async () => {
|
|
240
|
-
const table = await config.api.table.save(defaultTable())
|
|
241
|
-
await config.api.row.save(table._id!, {})
|
|
242
|
-
await config.api.row.get(table._id!, "1234567", {
|
|
243
|
-
status: 404,
|
|
244
|
-
})
|
|
245
|
-
})
|
|
246
|
-
|
|
247
239
|
isInternal &&
|
|
248
240
|
it("row values are coerced", async () => {
|
|
249
241
|
const str: FieldSchema = {
|
|
@@ -296,8 +288,6 @@ describe.each([
|
|
|
296
288
|
}
|
|
297
289
|
const table = await config.api.table.save(
|
|
298
290
|
saveTableRequest({
|
|
299
|
-
name: "TestTable2",
|
|
300
|
-
type: "table",
|
|
301
291
|
schema: {
|
|
302
292
|
name: str,
|
|
303
293
|
stringUndefined: str,
|
|
@@ -404,52 +394,59 @@ describe.each([
|
|
|
404
394
|
})
|
|
405
395
|
})
|
|
406
396
|
|
|
407
|
-
describe("
|
|
408
|
-
it("
|
|
409
|
-
const
|
|
410
|
-
saveTableRequest({
|
|
411
|
-
name: "orders",
|
|
412
|
-
schema: {
|
|
413
|
-
Country: {
|
|
414
|
-
type: FieldType.STRING,
|
|
415
|
-
name: "Country",
|
|
416
|
-
},
|
|
417
|
-
Story: {
|
|
418
|
-
type: FieldType.STRING,
|
|
419
|
-
name: "Story",
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
})
|
|
423
|
-
)
|
|
397
|
+
describe("get", () => {
|
|
398
|
+
it("reads an existing row successfully", async () => {
|
|
399
|
+
const existing = await config.api.row.save(table._id!, {})
|
|
424
400
|
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
visible: true,
|
|
431
|
-
},
|
|
432
|
-
},
|
|
401
|
+
const res = await config.api.row.get(table._id!, existing._id!)
|
|
402
|
+
|
|
403
|
+
expect(res).toEqual({
|
|
404
|
+
...existing,
|
|
405
|
+
...defaultRowFields,
|
|
433
406
|
})
|
|
407
|
+
})
|
|
434
408
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
409
|
+
it("returns 404 when row does not exist", async () => {
|
|
410
|
+
const table = await config.api.table.save(defaultTable())
|
|
411
|
+
await config.api.row.save(table._id!, {})
|
|
412
|
+
await config.api.row.get(table._id!, "1234567", {
|
|
413
|
+
status: 404,
|
|
414
|
+
})
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
describe("fetch", () => {
|
|
419
|
+
it("fetches all rows for given tableId", async () => {
|
|
420
|
+
const table = await config.api.table.save(defaultTable())
|
|
421
|
+
const rows = await Promise.all([
|
|
422
|
+
config.api.row.save(table._id!, {}),
|
|
423
|
+
config.api.row.save(table._id!, {}),
|
|
424
|
+
])
|
|
425
|
+
|
|
426
|
+
const res = await config.api.row.fetch(table._id!)
|
|
427
|
+
expect(res.map(r => r._id)).toEqual(
|
|
428
|
+
expect.arrayContaining(rows.map(r => r._id))
|
|
441
429
|
)
|
|
430
|
+
})
|
|
442
431
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
432
|
+
it("returns 404 when table does not exist", async () => {
|
|
433
|
+
await config.api.row.fetch("1234567", { status: 404 })
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
describe("update", () => {
|
|
438
|
+
it("updates an existing row successfully", async () => {
|
|
439
|
+
const existing = await config.api.row.save(table._id!, {})
|
|
440
|
+
const rowUsage = await getRowUsage()
|
|
441
|
+
|
|
442
|
+
const res = await config.api.row.save(table._id!, {
|
|
443
|
+
_id: existing._id,
|
|
444
|
+
_rev: existing._rev,
|
|
445
|
+
name: "Updated Name",
|
|
452
446
|
})
|
|
447
|
+
|
|
448
|
+
expect(res.name).toEqual("Updated Name")
|
|
449
|
+
await assertRowUsage(rowUsage)
|
|
453
450
|
})
|
|
454
451
|
})
|
|
455
452
|
|
|
@@ -722,50 +719,7 @@ describe.each([
|
|
|
722
719
|
})
|
|
723
720
|
})
|
|
724
721
|
|
|
725
|
-
|
|
726
|
-
isInternal &&
|
|
727
|
-
describe("fetchView", () => {
|
|
728
|
-
beforeEach(async () => {
|
|
729
|
-
table = await config.api.table.save(defaultTable())
|
|
730
|
-
})
|
|
731
|
-
|
|
732
|
-
it("should be able to fetch tables contents via 'view'", async () => {
|
|
733
|
-
const row = await config.api.row.save(table._id!, {})
|
|
734
|
-
const rowUsage = await getRowUsage()
|
|
735
|
-
|
|
736
|
-
const rows = await config.api.legacyView.get(table._id!)
|
|
737
|
-
expect(rows.length).toEqual(1)
|
|
738
|
-
expect(rows[0]._id).toEqual(row._id)
|
|
739
|
-
await assertRowUsage(rowUsage)
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
it("should throw an error if view doesn't exist", async () => {
|
|
743
|
-
const rowUsage = await getRowUsage()
|
|
744
|
-
|
|
745
|
-
await config.api.legacyView.get("derp", undefined, { status: 404 })
|
|
746
|
-
|
|
747
|
-
await assertRowUsage(rowUsage)
|
|
748
|
-
})
|
|
749
|
-
|
|
750
|
-
it("should be able to run on a view", async () => {
|
|
751
|
-
const view = await config.createLegacyView({
|
|
752
|
-
tableId: table._id!,
|
|
753
|
-
name: "ViewTest",
|
|
754
|
-
filters: [],
|
|
755
|
-
schema: {},
|
|
756
|
-
})
|
|
757
|
-
const row = await config.api.row.save(table._id!, {})
|
|
758
|
-
const rowUsage = await getRowUsage()
|
|
759
|
-
|
|
760
|
-
const rows = await config.api.legacyView.get(view.name)
|
|
761
|
-
expect(rows.length).toEqual(1)
|
|
762
|
-
expect(rows[0]._id).toEqual(row._id)
|
|
763
|
-
|
|
764
|
-
await assertRowUsage(rowUsage)
|
|
765
|
-
})
|
|
766
|
-
})
|
|
767
|
-
|
|
768
|
-
describe("fetchEnrichedRows", () => {
|
|
722
|
+
describe("enrich", () => {
|
|
769
723
|
beforeAll(async () => {
|
|
770
724
|
table = await config.api.table.save(defaultTable())
|
|
771
725
|
})
|
|
@@ -827,10 +781,6 @@ describe.each([
|
|
|
827
781
|
|
|
828
782
|
isInternal &&
|
|
829
783
|
describe("attachments", () => {
|
|
830
|
-
beforeAll(async () => {
|
|
831
|
-
table = await config.api.table.save(defaultTable())
|
|
832
|
-
})
|
|
833
|
-
|
|
834
784
|
it("should allow enriching attachment rows", async () => {
|
|
835
785
|
const table = await config.api.table.save(
|
|
836
786
|
defaultTable({
|
|
@@ -865,7 +815,7 @@ describe.each([
|
|
|
865
815
|
})
|
|
866
816
|
})
|
|
867
817
|
|
|
868
|
-
describe("
|
|
818
|
+
describe("exportRows", () => {
|
|
869
819
|
beforeAll(async () => {
|
|
870
820
|
table = await config.api.table.save(defaultTable())
|
|
871
821
|
})
|
|
@@ -947,6 +897,7 @@ describe.each([
|
|
|
947
897
|
const table = await config.api.table.save(await userTable())
|
|
948
898
|
const view = await config.api.viewV2.create({
|
|
949
899
|
tableId: table._id!,
|
|
900
|
+
name: generator.guid(),
|
|
950
901
|
schema: {
|
|
951
902
|
name: { visible: true },
|
|
952
903
|
surname: { visible: true },
|
|
@@ -984,6 +935,7 @@ describe.each([
|
|
|
984
935
|
const tableId = table._id!
|
|
985
936
|
const view = await config.api.viewV2.create({
|
|
986
937
|
tableId: tableId,
|
|
938
|
+
name: generator.guid(),
|
|
987
939
|
schema: {
|
|
988
940
|
name: { visible: true },
|
|
989
941
|
address: { visible: true },
|
|
@@ -1026,6 +978,7 @@ describe.each([
|
|
|
1026
978
|
const tableId = table._id!
|
|
1027
979
|
const view = await config.api.viewV2.create({
|
|
1028
980
|
tableId: tableId,
|
|
981
|
+
name: generator.guid(),
|
|
1029
982
|
schema: {
|
|
1030
983
|
name: { visible: true },
|
|
1031
984
|
address: { visible: true },
|
|
@@ -1049,6 +1002,7 @@ describe.each([
|
|
|
1049
1002
|
const tableId = table._id!
|
|
1050
1003
|
const view = await config.api.viewV2.create({
|
|
1051
1004
|
tableId: tableId,
|
|
1005
|
+
name: generator.guid(),
|
|
1052
1006
|
schema: {
|
|
1053
1007
|
name: { visible: true },
|
|
1054
1008
|
address: { visible: true },
|
|
@@ -1109,6 +1063,7 @@ describe.each([
|
|
|
1109
1063
|
|
|
1110
1064
|
const createViewResponse = await config.api.viewV2.create({
|
|
1111
1065
|
tableId: table._id!,
|
|
1066
|
+
name: generator.guid(),
|
|
1112
1067
|
})
|
|
1113
1068
|
const response = await config.api.viewV2.search(createViewResponse.id)
|
|
1114
1069
|
|
|
@@ -1155,6 +1110,7 @@ describe.each([
|
|
|
1155
1110
|
|
|
1156
1111
|
const createViewResponse = await config.api.viewV2.create({
|
|
1157
1112
|
tableId: table._id!,
|
|
1113
|
+
name: generator.guid(),
|
|
1158
1114
|
query: [
|
|
1159
1115
|
{ operator: SearchQueryOperators.EQUAL, field: "age", value: 40 },
|
|
1160
1116
|
],
|
|
@@ -1279,6 +1235,7 @@ describe.each([
|
|
|
1279
1235
|
async (sortParams, expected) => {
|
|
1280
1236
|
const createViewResponse = await config.api.viewV2.create({
|
|
1281
1237
|
tableId: table._id!,
|
|
1238
|
+
name: generator.guid(),
|
|
1282
1239
|
sort: sortParams,
|
|
1283
1240
|
schema: viewSchema,
|
|
1284
1241
|
})
|
|
@@ -1299,6 +1256,7 @@ describe.each([
|
|
|
1299
1256
|
async (sortParams, expected) => {
|
|
1300
1257
|
const createViewResponse = await config.api.viewV2.create({
|
|
1301
1258
|
tableId: table._id!,
|
|
1259
|
+
name: generator.guid(),
|
|
1302
1260
|
sort: {
|
|
1303
1261
|
field: "name",
|
|
1304
1262
|
order: SortOrder.ASCENDING,
|
|
@@ -1339,6 +1297,7 @@ describe.each([
|
|
|
1339
1297
|
|
|
1340
1298
|
const view = await config.api.viewV2.create({
|
|
1341
1299
|
tableId: table._id!,
|
|
1300
|
+
name: generator.guid(),
|
|
1342
1301
|
schema: { name: { visible: true } },
|
|
1343
1302
|
})
|
|
1344
1303
|
const response = await config.api.viewV2.search(view.id)
|
|
@@ -1361,6 +1320,7 @@ describe.each([
|
|
|
1361
1320
|
const table = await config.api.table.save(await userTable())
|
|
1362
1321
|
const createViewResponse = await config.api.viewV2.create({
|
|
1363
1322
|
tableId: table._id!,
|
|
1323
|
+
name: generator.guid(),
|
|
1364
1324
|
})
|
|
1365
1325
|
const response = await config.api.viewV2.search(createViewResponse.id)
|
|
1366
1326
|
expect(response.rows).toHaveLength(0)
|
|
@@ -1376,6 +1336,7 @@ describe.each([
|
|
|
1376
1336
|
|
|
1377
1337
|
const createViewResponse = await config.api.viewV2.create({
|
|
1378
1338
|
tableId: table._id!,
|
|
1339
|
+
name: generator.guid(),
|
|
1379
1340
|
})
|
|
1380
1341
|
const response = await config.api.viewV2.search(createViewResponse.id, {
|
|
1381
1342
|
limit,
|
|
@@ -1392,6 +1353,7 @@ describe.each([
|
|
|
1392
1353
|
)
|
|
1393
1354
|
const view = await config.api.viewV2.create({
|
|
1394
1355
|
tableId: table._id!,
|
|
1356
|
+
name: generator.guid(),
|
|
1395
1357
|
})
|
|
1396
1358
|
const rows = (await config.api.viewV2.search(view.id)).rows
|
|
1397
1359
|
|
|
@@ -1466,6 +1428,7 @@ describe.each([
|
|
|
1466
1428
|
|
|
1467
1429
|
view = await config.api.viewV2.create({
|
|
1468
1430
|
tableId: table._id!,
|
|
1431
|
+
name: generator.guid(),
|
|
1469
1432
|
})
|
|
1470
1433
|
})
|
|
1471
1434
|
|
|
@@ -20,7 +20,7 @@ import sdk from "../../../sdk"
|
|
|
20
20
|
import * as uuid from "uuid"
|
|
21
21
|
|
|
22
22
|
import tk from "timekeeper"
|
|
23
|
-
import { mocks } from "@budibase/backend-core/tests"
|
|
23
|
+
import { generator, mocks } from "@budibase/backend-core/tests"
|
|
24
24
|
import { TableToBuild } from "../../../tests/utilities/TestConfiguration"
|
|
25
25
|
|
|
26
26
|
tk.freeze(mocks.date.MOCK_DATE)
|
|
@@ -417,8 +417,8 @@ describe("/tables", () => {
|
|
|
417
417
|
it("should fetch views", async () => {
|
|
418
418
|
const tableId = config.table!._id!
|
|
419
419
|
const views = [
|
|
420
|
-
await config.api.viewV2.create({ tableId }),
|
|
421
|
-
await config.api.viewV2.create({ tableId }),
|
|
420
|
+
await config.api.viewV2.create({ tableId, name: generator.guid() }),
|
|
421
|
+
await config.api.viewV2.create({ tableId, name: generator.guid() }),
|
|
422
422
|
]
|
|
423
423
|
|
|
424
424
|
const res = await request
|
|
@@ -455,7 +455,7 @@ describe("/tables", () => {
|
|
|
455
455
|
},
|
|
456
456
|
}))
|
|
457
457
|
|
|
458
|
-
await config.api.viewV2.create({ tableId })
|
|
458
|
+
await config.api.viewV2.create({ tableId, name: generator.guid() })
|
|
459
459
|
await config.createLegacyView()
|
|
460
460
|
|
|
461
461
|
const res = await config.api.table.fetch()
|
|
@@ -3,12 +3,15 @@ import * as setup from "./utilities"
|
|
|
3
3
|
import {
|
|
4
4
|
FieldType,
|
|
5
5
|
INTERNAL_TABLE_SOURCE_ID,
|
|
6
|
+
QuotaUsageType,
|
|
6
7
|
SaveTableRequest,
|
|
8
|
+
StaticQuotaName,
|
|
7
9
|
Table,
|
|
8
10
|
TableSourceType,
|
|
9
11
|
View,
|
|
10
12
|
ViewCalculation,
|
|
11
13
|
} from "@budibase/types"
|
|
14
|
+
import { quotas } from "@budibase/pro"
|
|
12
15
|
|
|
13
16
|
const priceTable: SaveTableRequest = {
|
|
14
17
|
name: "table",
|
|
@@ -57,6 +60,18 @@ describe("/views", () => {
|
|
|
57
60
|
return config.api.legacyView.save(viewToSave)
|
|
58
61
|
}
|
|
59
62
|
|
|
63
|
+
const getRowUsage = async () => {
|
|
64
|
+
const { total } = await config.doInContext(undefined, () =>
|
|
65
|
+
quotas.getCurrentUsageValues(QuotaUsageType.STATIC, StaticQuotaName.ROWS)
|
|
66
|
+
)
|
|
67
|
+
return total
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const assertRowUsage = async (expected: number) => {
|
|
71
|
+
const usage = await getRowUsage()
|
|
72
|
+
expect(usage).toBe(expected)
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
describe("create", () => {
|
|
61
76
|
it("returns a success message when the view is successfully created", async () => {
|
|
62
77
|
const res = await saveView()
|
|
@@ -265,6 +280,41 @@ describe("/views", () => {
|
|
|
265
280
|
expect(views.length).toBe(1)
|
|
266
281
|
expect(views.find(({ name }) => name === "TestView")).toBeDefined()
|
|
267
282
|
})
|
|
283
|
+
|
|
284
|
+
it("should be able to fetch tables contents via 'view'", async () => {
|
|
285
|
+
const row = await config.api.row.save(table._id!, {})
|
|
286
|
+
const rowUsage = await getRowUsage()
|
|
287
|
+
|
|
288
|
+
const rows = await config.api.legacyView.get(table._id!)
|
|
289
|
+
expect(rows.length).toEqual(1)
|
|
290
|
+
expect(rows[0]._id).toEqual(row._id)
|
|
291
|
+
await assertRowUsage(rowUsage)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it("should throw an error if view doesn't exist", async () => {
|
|
295
|
+
const rowUsage = await getRowUsage()
|
|
296
|
+
|
|
297
|
+
await config.api.legacyView.get("derp", undefined, { status: 404 })
|
|
298
|
+
|
|
299
|
+
await assertRowUsage(rowUsage)
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it("should be able to run on a view", async () => {
|
|
303
|
+
const view = await config.api.legacyView.save({
|
|
304
|
+
tableId: table._id!,
|
|
305
|
+
name: "ViewTest",
|
|
306
|
+
filters: [],
|
|
307
|
+
schema: {},
|
|
308
|
+
})
|
|
309
|
+
const row = await config.api.row.save(table._id!, {})
|
|
310
|
+
const rowUsage = await getRowUsage()
|
|
311
|
+
|
|
312
|
+
const rows = await config.api.legacyView.get(view.name!)
|
|
313
|
+
expect(rows.length).toEqual(1)
|
|
314
|
+
expect(rows[0]._id).toEqual(row._id)
|
|
315
|
+
|
|
316
|
+
await assertRowUsage(rowUsage)
|
|
317
|
+
})
|
|
268
318
|
})
|
|
269
319
|
|
|
270
320
|
describe("query", () => {
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as setup from "./utilities"
|
|
2
2
|
import {
|
|
3
3
|
CreateViewRequest,
|
|
4
|
+
Datasource,
|
|
4
5
|
FieldSchema,
|
|
5
6
|
FieldType,
|
|
6
7
|
INTERNAL_TABLE_SOURCE_ID,
|
|
8
|
+
SaveTableRequest,
|
|
7
9
|
SearchQueryOperators,
|
|
8
10
|
SortOrder,
|
|
9
11
|
SortType,
|
|
@@ -14,65 +16,88 @@ import {
|
|
|
14
16
|
ViewV2,
|
|
15
17
|
} from "@budibase/types"
|
|
16
18
|
import { generator } from "@budibase/backend-core/tests"
|
|
17
|
-
import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return {
|
|
21
|
-
name: "table",
|
|
22
|
-
type: "table",
|
|
23
|
-
sourceId: INTERNAL_TABLE_SOURCE_ID,
|
|
24
|
-
sourceType: TableSourceType.INTERNAL,
|
|
25
|
-
schema: {
|
|
26
|
-
Price: {
|
|
27
|
-
type: FieldType.NUMBER,
|
|
28
|
-
name: "Price",
|
|
29
|
-
constraints: {},
|
|
30
|
-
},
|
|
31
|
-
Category: {
|
|
32
|
-
type: FieldType.STRING,
|
|
33
|
-
name: "Category",
|
|
34
|
-
constraints: {
|
|
35
|
-
type: "string",
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
}
|
|
40
|
-
}
|
|
19
|
+
import * as uuid from "uuid"
|
|
20
|
+
import { databaseTestProviders } from "../../../integrations/tests/utils"
|
|
21
|
+
import merge from "lodash/merge"
|
|
41
22
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
23
|
+
jest.unmock("mysql2")
|
|
24
|
+
jest.unmock("mysql2/promise")
|
|
25
|
+
jest.unmock("mssql")
|
|
26
|
+
jest.unmock("pg")
|
|
47
27
|
|
|
48
28
|
describe.each([
|
|
49
|
-
["internal
|
|
50
|
-
[
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
plus: true,
|
|
57
|
-
_id: generateDatasourceID({ plus: true }),
|
|
58
|
-
},
|
|
59
|
-
})
|
|
29
|
+
["internal", undefined],
|
|
30
|
+
["postgres", databaseTestProviders.postgres],
|
|
31
|
+
["mysql", databaseTestProviders.mysql],
|
|
32
|
+
["mssql", databaseTestProviders.mssql],
|
|
33
|
+
["mariadb", databaseTestProviders.mariadb],
|
|
34
|
+
])("/v2/views (%s)", (_, dsProvider) => {
|
|
35
|
+
const config = setup.getConfig()
|
|
60
36
|
|
|
61
|
-
return config.createExternalTable({
|
|
62
|
-
...priceTable(),
|
|
63
|
-
sourceId: datasource._id,
|
|
64
|
-
sourceType: TableSourceType.EXTERNAL,
|
|
65
|
-
})
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
])("/v2/views (%s)", (_, tableBuilder) => {
|
|
69
37
|
let table: Table
|
|
38
|
+
let datasource: Datasource
|
|
39
|
+
|
|
40
|
+
function saveTableRequest(
|
|
41
|
+
...overrides: Partial<SaveTableRequest>[]
|
|
42
|
+
): SaveTableRequest {
|
|
43
|
+
const req: SaveTableRequest = {
|
|
44
|
+
name: uuid.v4().substring(0, 16),
|
|
45
|
+
type: "table",
|
|
46
|
+
sourceType: datasource
|
|
47
|
+
? TableSourceType.EXTERNAL
|
|
48
|
+
: TableSourceType.INTERNAL,
|
|
49
|
+
sourceId: datasource ? datasource._id! : INTERNAL_TABLE_SOURCE_ID,
|
|
50
|
+
primary: ["id"],
|
|
51
|
+
schema: {
|
|
52
|
+
id: {
|
|
53
|
+
type: FieldType.AUTO,
|
|
54
|
+
name: "id",
|
|
55
|
+
autocolumn: true,
|
|
56
|
+
constraints: {
|
|
57
|
+
presence: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
return merge(req, ...overrides)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function priceTable(): SaveTableRequest {
|
|
66
|
+
return saveTableRequest({
|
|
67
|
+
schema: {
|
|
68
|
+
Price: {
|
|
69
|
+
type: FieldType.NUMBER,
|
|
70
|
+
name: "Price",
|
|
71
|
+
constraints: {},
|
|
72
|
+
},
|
|
73
|
+
Category: {
|
|
74
|
+
type: FieldType.STRING,
|
|
75
|
+
name: "Category",
|
|
76
|
+
constraints: {
|
|
77
|
+
type: "string",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
}
|
|
70
83
|
|
|
71
84
|
beforeAll(async () => {
|
|
72
|
-
|
|
85
|
+
await config.init()
|
|
86
|
+
|
|
87
|
+
if (dsProvider) {
|
|
88
|
+
datasource = await config.createDatasource({
|
|
89
|
+
datasource: await dsProvider.datasource(),
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
table = await config.api.table.save(priceTable())
|
|
73
93
|
})
|
|
74
94
|
|
|
75
|
-
afterAll(
|
|
95
|
+
afterAll(async () => {
|
|
96
|
+
if (dsProvider) {
|
|
97
|
+
await dsProvider.stop()
|
|
98
|
+
}
|
|
99
|
+
setup.afterAll()
|
|
100
|
+
})
|
|
76
101
|
|
|
77
102
|
describe("create", () => {
|
|
78
103
|
it("persist the view when the view is successfully created", async () => {
|
|
@@ -186,9 +211,12 @@ describe.each([
|
|
|
186
211
|
let view: ViewV2
|
|
187
212
|
|
|
188
213
|
beforeEach(async () => {
|
|
189
|
-
table = await
|
|
214
|
+
table = await config.api.table.save(priceTable())
|
|
190
215
|
|
|
191
|
-
view = await config.api.viewV2.create({
|
|
216
|
+
view = await config.api.viewV2.create({
|
|
217
|
+
tableId: table._id!,
|
|
218
|
+
name: "View A",
|
|
219
|
+
})
|
|
192
220
|
})
|
|
193
221
|
|
|
194
222
|
it("can update an existing view data", async () => {
|
|
@@ -247,6 +275,9 @@ describe.each([
|
|
|
247
275
|
...updatedData,
|
|
248
276
|
schema: {
|
|
249
277
|
...table.schema,
|
|
278
|
+
id: expect.objectContaining({
|
|
279
|
+
visible: false,
|
|
280
|
+
}),
|
|
250
281
|
Category: expect.objectContaining({
|
|
251
282
|
visible: false,
|
|
252
283
|
}),
|
|
@@ -320,23 +351,27 @@ describe.each([
|
|
|
320
351
|
})
|
|
321
352
|
|
|
322
353
|
it("cannot update views v1", async () => {
|
|
323
|
-
const viewV1 = await config.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
},
|
|
328
|
-
|
|
354
|
+
const viewV1 = await config.api.legacyView.save({
|
|
355
|
+
tableId: table._id!,
|
|
356
|
+
name: generator.guid(),
|
|
357
|
+
filters: [],
|
|
358
|
+
schema: {},
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
await config.api.viewV2.update(viewV1 as unknown as ViewV2, {
|
|
362
|
+
status: 400,
|
|
363
|
+
body: {
|
|
364
|
+
message: "Only views V2 can be updated",
|
|
329
365
|
status: 400,
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
status: 400,
|
|
333
|
-
},
|
|
334
|
-
}
|
|
335
|
-
)
|
|
366
|
+
},
|
|
367
|
+
})
|
|
336
368
|
})
|
|
337
369
|
|
|
338
370
|
it("cannot update the a view with unmatching ids between url and body", async () => {
|
|
339
|
-
const anotherView = await config.api.viewV2.create(
|
|
371
|
+
const anotherView = await config.api.viewV2.create({
|
|
372
|
+
tableId: table._id!,
|
|
373
|
+
name: generator.guid(),
|
|
374
|
+
})
|
|
340
375
|
const result = await config
|
|
341
376
|
.request!.put(`/api/v2/views/${anotherView.id}`)
|
|
342
377
|
.send(view)
|
|
@@ -411,7 +446,10 @@ describe.each([
|
|
|
411
446
|
let view: ViewV2
|
|
412
447
|
|
|
413
448
|
beforeAll(async () => {
|
|
414
|
-
view = await config.api.viewV2.create(
|
|
449
|
+
view = await config.api.viewV2.create({
|
|
450
|
+
tableId: table._id!,
|
|
451
|
+
name: generator.guid(),
|
|
452
|
+
})
|
|
415
453
|
})
|
|
416
454
|
|
|
417
455
|
it("can delete an existing view", async () => {
|
|
@@ -448,4 +486,43 @@ describe.each([
|
|
|
448
486
|
expect(viewSchema.Price?.visible).toEqual(false)
|
|
449
487
|
})
|
|
450
488
|
})
|
|
489
|
+
|
|
490
|
+
describe("read", () => {
|
|
491
|
+
it("views have extra data trimmed", async () => {
|
|
492
|
+
const table = await config.api.table.save(
|
|
493
|
+
saveTableRequest({
|
|
494
|
+
name: "orders",
|
|
495
|
+
schema: {
|
|
496
|
+
Country: {
|
|
497
|
+
type: FieldType.STRING,
|
|
498
|
+
name: "Country",
|
|
499
|
+
},
|
|
500
|
+
Story: {
|
|
501
|
+
type: FieldType.STRING,
|
|
502
|
+
name: "Story",
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
})
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
const view = await config.api.viewV2.create({
|
|
509
|
+
tableId: table._id!,
|
|
510
|
+
name: uuid.v4(),
|
|
511
|
+
schema: {
|
|
512
|
+
Country: {
|
|
513
|
+
visible: true,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
let row = await config.api.row.save(view.id, {
|
|
519
|
+
Country: "Aussy",
|
|
520
|
+
Story: "aaaaa",
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
row = await config.api.row.get(table._id!, row._id!)
|
|
524
|
+
expect(row.Story).toBeUndefined()
|
|
525
|
+
expect(row.Country).toEqual("Aussy")
|
|
526
|
+
})
|
|
527
|
+
})
|
|
451
528
|
})
|
|
@@ -11,21 +11,9 @@ import sdk from "../../../sdk"
|
|
|
11
11
|
|
|
12
12
|
export class ViewV2API extends TestAPI {
|
|
13
13
|
create = async (
|
|
14
|
-
|
|
14
|
+
view: CreateViewRequest,
|
|
15
15
|
expectations?: Expectations
|
|
16
16
|
): Promise<ViewV2> => {
|
|
17
|
-
let tableId = viewData?.tableId
|
|
18
|
-
if (!tableId && !this.config.table) {
|
|
19
|
-
throw "Test requires table to be configured."
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
tableId = tableId || this.config.table!._id!
|
|
23
|
-
const view = {
|
|
24
|
-
tableId,
|
|
25
|
-
name: generator.guid(),
|
|
26
|
-
...viewData,
|
|
27
|
-
}
|
|
28
|
-
|
|
29
17
|
const exp: Expectations = {
|
|
30
18
|
status: 201,
|
|
31
19
|
...expectations,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as search from "../../app/rows/search"
|
|
2
|
-
|
|
3
|
-
describe("removeEmptyFilters", () => {
|
|
4
|
-
it("0 should not be removed", () => {
|
|
5
|
-
const filters = search.removeEmptyFilters({
|
|
6
|
-
equal: {
|
|
7
|
-
column: 0,
|
|
8
|
-
},
|
|
9
|
-
})
|
|
10
|
-
expect((filters.equal as any).column).toBe(0)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it("empty string should be removed", () => {
|
|
14
|
-
const filters = search.removeEmptyFilters({
|
|
15
|
-
equal: {
|
|
16
|
-
column: "",
|
|
17
|
-
},
|
|
18
|
-
})
|
|
19
|
-
expect(Object.values(filters.equal as any).length).toBe(0)
|
|
20
|
-
})
|
|
21
|
-
})
|