@wix/patterns-cms 1.1.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/dist/cjs/adapter/__tests__/cmsFieldActions.spec.js +436 -0
- package/dist/cjs/adapter/__tests__/cmsFieldActions.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/cmsPermissions.spec.js +175 -0
- package/dist/cjs/adapter/__tests__/cmsPermissions.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/createCmsBackend.spec.js +440 -0
- package/dist/cjs/adapter/__tests__/createCmsBackend.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/filterUtils.spec.js +426 -0
- package/dist/cjs/adapter/__tests__/filterUtils.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/getFieldKey.spec.js +24 -0
- package/dist/cjs/adapter/__tests__/getFieldKey.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/loadCmsSchema.spec.js +166 -0
- package/dist/cjs/adapter/__tests__/loadCmsSchema.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/searchUtils.spec.js +116 -0
- package/dist/cjs/adapter/__tests__/searchUtils.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/sortUtils.spec.js +118 -0
- package/dist/cjs/adapter/__tests__/sortUtils.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/transformCmsSchema.spec.js +210 -0
- package/dist/cjs/adapter/__tests__/transformCmsSchema.spec.js.map +1 -0
- package/dist/cjs/adapter/__tests__/wixDataQueryRecorder.js +54 -0
- package/dist/cjs/adapter/__tests__/wixDataQueryRecorder.js.map +1 -0
- package/dist/cjs/adapter/cmsBackup.js +52 -0
- package/dist/cjs/adapter/cmsBackup.js.map +1 -0
- package/dist/cjs/adapter/cmsFieldTypeMap.js +43 -0
- package/dist/cjs/adapter/cmsFieldTypeMap.js.map +1 -0
- package/dist/cjs/adapter/cmsPermissions.js +94 -0
- package/dist/cjs/adapter/cmsPermissions.js.map +1 -0
- package/dist/cjs/adapter/constants.js +20 -0
- package/dist/cjs/adapter/constants.js.map +1 -0
- package/dist/cjs/adapter/createCmsBackend.js +131 -0
- package/dist/cjs/adapter/createCmsBackend.js.map +1 -0
- package/dist/cjs/adapter/createCmsFieldActions.js +182 -0
- package/dist/cjs/adapter/createCmsFieldActions.js.map +1 -0
- package/dist/cjs/adapter/fetchExternalDbConnections.js +24 -0
- package/dist/cjs/adapter/fetchExternalDbConnections.js.map +1 -0
- package/dist/cjs/adapter/fetchUserPermissions.js +24 -0
- package/dist/cjs/adapter/fetchUserPermissions.js.map +1 -0
- package/dist/cjs/adapter/filterUtils.js +128 -0
- package/dist/cjs/adapter/filterUtils.js.map +1 -0
- package/dist/cjs/adapter/getFieldKey.js +19 -0
- package/dist/cjs/adapter/getFieldKey.js.map +1 -0
- package/dist/cjs/adapter/index.js +36 -0
- package/dist/cjs/adapter/index.js.map +1 -0
- package/dist/cjs/adapter/loadCmsSchema.js +51 -0
- package/dist/cjs/adapter/loadCmsSchema.js.map +1 -0
- package/dist/cjs/adapter/pollUntilReady.js +30 -0
- package/dist/cjs/adapter/pollUntilReady.js.map +1 -0
- package/dist/cjs/adapter/searchUtils.js +32 -0
- package/dist/cjs/adapter/searchUtils.js.map +1 -0
- package/dist/cjs/adapter/sortUtils.js +34 -0
- package/dist/cjs/adapter/sortUtils.js.map +1 -0
- package/dist/cjs/adapter/transformCmsSchema.js +75 -0
- package/dist/cjs/adapter/transformCmsSchema.js.map +1 -0
- package/dist/cjs/index.js +10 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/adapter/__tests__/cmsFieldActions.spec.js +335 -0
- package/dist/esm/adapter/__tests__/cmsFieldActions.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/cmsPermissions.spec.js +173 -0
- package/dist/esm/adapter/__tests__/cmsPermissions.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/createCmsBackend.spec.js +307 -0
- package/dist/esm/adapter/__tests__/createCmsBackend.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/filterUtils.spec.js +298 -0
- package/dist/esm/adapter/__tests__/filterUtils.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/getFieldKey.spec.js +22 -0
- package/dist/esm/adapter/__tests__/getFieldKey.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/loadCmsSchema.spec.js +147 -0
- package/dist/esm/adapter/__tests__/loadCmsSchema.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/searchUtils.spec.js +109 -0
- package/dist/esm/adapter/__tests__/searchUtils.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/sortUtils.spec.js +85 -0
- package/dist/esm/adapter/__tests__/sortUtils.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/transformCmsSchema.spec.js +201 -0
- package/dist/esm/adapter/__tests__/transformCmsSchema.spec.js.map +1 -0
- package/dist/esm/adapter/__tests__/wixDataQueryRecorder.js +42 -0
- package/dist/esm/adapter/__tests__/wixDataQueryRecorder.js.map +1 -0
- package/dist/esm/adapter/cmsBackup.js +34 -0
- package/dist/esm/adapter/cmsBackup.js.map +1 -0
- package/dist/esm/adapter/cmsFieldTypeMap.js +37 -0
- package/dist/esm/adapter/cmsFieldTypeMap.js.map +1 -0
- package/dist/esm/adapter/cmsPermissions.js +91 -0
- package/dist/esm/adapter/cmsPermissions.js.map +1 -0
- package/dist/esm/adapter/constants.js +25 -0
- package/dist/esm/adapter/constants.js.map +1 -0
- package/dist/esm/adapter/createCmsBackend.js +113 -0
- package/dist/esm/adapter/createCmsBackend.js.map +1 -0
- package/dist/esm/adapter/createCmsFieldActions.js +159 -0
- package/dist/esm/adapter/createCmsFieldActions.js.map +1 -0
- package/dist/esm/adapter/fetchExternalDbConnections.js +17 -0
- package/dist/esm/adapter/fetchExternalDbConnections.js.map +1 -0
- package/dist/esm/adapter/fetchUserPermissions.js +15 -0
- package/dist/esm/adapter/fetchUserPermissions.js.map +1 -0
- package/dist/esm/adapter/filterUtils.js +126 -0
- package/dist/esm/adapter/filterUtils.js.map +1 -0
- package/dist/esm/adapter/getFieldKey.js +13 -0
- package/dist/esm/adapter/getFieldKey.js.map +1 -0
- package/dist/esm/adapter/index.js +15 -0
- package/dist/esm/adapter/index.js.map +1 -0
- package/dist/esm/adapter/loadCmsSchema.js +48 -0
- package/dist/esm/adapter/loadCmsSchema.js.map +1 -0
- package/dist/esm/adapter/pollUntilReady.js +24 -0
- package/dist/esm/adapter/pollUntilReady.js.map +1 -0
- package/dist/esm/adapter/searchUtils.js +24 -0
- package/dist/esm/adapter/searchUtils.js.map +1 -0
- package/dist/esm/adapter/sortUtils.js +21 -0
- package/dist/esm/adapter/sortUtils.js.map +1 -0
- package/dist/esm/adapter/transformCmsSchema.js +70 -0
- package/dist/esm/adapter/transformCmsSchema.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/adapter/__tests__/cmsFieldActions.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/cmsFieldActions.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/cmsPermissions.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/cmsPermissions.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/createCmsBackend.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/createCmsBackend.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/filterUtils.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/filterUtils.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/getFieldKey.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/getFieldKey.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/loadCmsSchema.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/loadCmsSchema.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/searchUtils.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/searchUtils.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/sortUtils.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/sortUtils.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/transformCmsSchema.spec.d.ts +2 -0
- package/dist/types/adapter/__tests__/transformCmsSchema.spec.d.ts.map +1 -0
- package/dist/types/adapter/__tests__/wixDataQueryRecorder.d.ts +40 -0
- package/dist/types/adapter/__tests__/wixDataQueryRecorder.d.ts.map +1 -0
- package/dist/types/adapter/cmsBackup.d.ts +7 -0
- package/dist/types/adapter/cmsBackup.d.ts.map +1 -0
- package/dist/types/adapter/cmsFieldTypeMap.d.ts +8 -0
- package/dist/types/adapter/cmsFieldTypeMap.d.ts.map +1 -0
- package/dist/types/adapter/cmsPermissions.d.ts +21 -0
- package/dist/types/adapter/cmsPermissions.d.ts.map +1 -0
- package/dist/types/adapter/constants.d.ts +14 -0
- package/dist/types/adapter/constants.d.ts.map +1 -0
- package/dist/types/adapter/createCmsBackend.d.ts +9 -0
- package/dist/types/adapter/createCmsBackend.d.ts.map +1 -0
- package/dist/types/adapter/createCmsFieldActions.d.ts +8 -0
- package/dist/types/adapter/createCmsFieldActions.d.ts.map +1 -0
- package/dist/types/adapter/fetchExternalDbConnections.d.ts +11 -0
- package/dist/types/adapter/fetchExternalDbConnections.d.ts.map +1 -0
- package/dist/types/adapter/fetchUserPermissions.d.ts +7 -0
- package/dist/types/adapter/fetchUserPermissions.d.ts.map +1 -0
- package/dist/types/adapter/filterUtils.d.ts +14 -0
- package/dist/types/adapter/filterUtils.d.ts.map +1 -0
- package/dist/types/adapter/getFieldKey.d.ts +3 -0
- package/dist/types/adapter/getFieldKey.d.ts.map +1 -0
- package/dist/types/adapter/index.d.ts +15 -0
- package/dist/types/adapter/index.d.ts.map +1 -0
- package/dist/types/adapter/loadCmsSchema.d.ts +29 -0
- package/dist/types/adapter/loadCmsSchema.d.ts.map +1 -0
- package/dist/types/adapter/pollUntilReady.d.ts +11 -0
- package/dist/types/adapter/pollUntilReady.d.ts.map +1 -0
- package/dist/types/adapter/searchUtils.d.ts +12 -0
- package/dist/types/adapter/searchUtils.d.ts.map +1 -0
- package/dist/types/adapter/sortUtils.d.ts +11 -0
- package/dist/types/adapter/sortUtils.d.ts.map +1 -0
- package/dist/types/adapter/transformCmsSchema.d.ts +12 -0
- package/dist/types/adapter/transformCmsSchema.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +123 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _cmsPermissions = require("../cmsPermissions");
|
|
4
|
+
const WIX_STORES = '1380b703-ce81-ff05-f115-39571d94dfcd';
|
|
5
|
+
const SITE_MEMBERS = 'SiteMembers';
|
|
6
|
+
const FORM_APP = '225dd912-7dea-4738-8688-4b8c6955ffc2';
|
|
7
|
+
const WIX_DATA = {
|
|
8
|
+
CREATE_FIELD: 'WIX_DATA.CREATE_COLLECTION_FIELD',
|
|
9
|
+
UPDATE_FIELD: 'WIX_DATA.UPDATE_COLLECTION_FIELD',
|
|
10
|
+
DELETE_FIELD: 'WIX_DATA.DELETE_COLLECTION_FIELD',
|
|
11
|
+
INSERT: 'WIX_DATA.INSERT',
|
|
12
|
+
UPDATE: 'WIX_DATA.UPDATE',
|
|
13
|
+
REMOVE: 'WIX_DATA.REMOVE'
|
|
14
|
+
};
|
|
15
|
+
const ALL_FIELD_PERMS = [WIX_DATA.CREATE_FIELD, WIX_DATA.UPDATE_FIELD, WIX_DATA.DELETE_FIELD];
|
|
16
|
+
const ALL_PERMS = [...ALL_FIELD_PERMS, WIX_DATA.INSERT, WIX_DATA.UPDATE, WIX_DATA.REMOVE];
|
|
17
|
+
const makeSchema = (overrides = {}) => {
|
|
18
|
+
const base = {
|
|
19
|
+
_id: 'col-1',
|
|
20
|
+
collectionType: 'NATIVE',
|
|
21
|
+
capabilities: {
|
|
22
|
+
dataOperations: ['SAVE', 'UPDATE', 'REMOVE']
|
|
23
|
+
},
|
|
24
|
+
permissions: {
|
|
25
|
+
read: 'ANYONE',
|
|
26
|
+
insert: 'SITE_MEMBER',
|
|
27
|
+
update: 'SITE_MEMBER',
|
|
28
|
+
remove: 'SITE_MEMBER'
|
|
29
|
+
},
|
|
30
|
+
plugins: []
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
...base,
|
|
34
|
+
...overrides
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
describe('computeCmsPermissions', () => {
|
|
38
|
+
it('shared collection: schema not editable, no item permissions', () => {
|
|
39
|
+
const schema = makeSchema({
|
|
40
|
+
plugins: [{
|
|
41
|
+
type: 'SHARED'
|
|
42
|
+
}]
|
|
43
|
+
});
|
|
44
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
45
|
+
|
|
46
|
+
// patterns-cms returns `schemaPermissions: undefined` (NOT an isSchemaEditable boolean)
|
|
47
|
+
// when the schema is not structurally editable.
|
|
48
|
+
expect(result.schemaPermissions).toBeUndefined();
|
|
49
|
+
expect(result.itemPermissions).toEqual({
|
|
50
|
+
createItem: false,
|
|
51
|
+
updateItem: false,
|
|
52
|
+
deleteItem: false
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it('Wix-app collection (Stores): item permissions all false', () => {
|
|
56
|
+
const schema = makeSchema({
|
|
57
|
+
collectionType: 'WIX_APP',
|
|
58
|
+
ownerAppId: WIX_STORES
|
|
59
|
+
});
|
|
60
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
61
|
+
expect(result.itemPermissions).toEqual({
|
|
62
|
+
createItem: false,
|
|
63
|
+
updateItem: false,
|
|
64
|
+
deleteItem: false
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it('Wix-app SiteMembers: NOT treated as readonly', () => {
|
|
68
|
+
const schema = makeSchema({
|
|
69
|
+
collectionType: 'WIX_APP',
|
|
70
|
+
ownerAppId: SITE_MEMBERS
|
|
71
|
+
});
|
|
72
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
73
|
+
expect(result.itemPermissions.createItem).toBe(true);
|
|
74
|
+
expect(result.itemPermissions.updateItem).toBe(true);
|
|
75
|
+
expect(result.itemPermissions.deleteItem).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it('Wix-app Forms collection: NOT treated as readonly', () => {
|
|
78
|
+
const schema = makeSchema({
|
|
79
|
+
collectionType: 'WIX_APP',
|
|
80
|
+
ownerAppId: FORM_APP
|
|
81
|
+
});
|
|
82
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
83
|
+
expect(result.itemPermissions.createItem).toBe(true);
|
|
84
|
+
expect(result.itemPermissions.updateItem).toBe(true);
|
|
85
|
+
expect(result.itemPermissions.deleteItem).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it('NATIVE collection with full ABAC + roles: all permissions granted', () => {
|
|
88
|
+
const schema = makeSchema();
|
|
89
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
90
|
+
|
|
91
|
+
// Structurally editable → schemaPermissions is defined.
|
|
92
|
+
expect(result.schemaPermissions).toEqual({
|
|
93
|
+
createField: true,
|
|
94
|
+
updateField: true,
|
|
95
|
+
deleteField: true
|
|
96
|
+
});
|
|
97
|
+
expect(result.itemPermissions).toEqual({
|
|
98
|
+
createItem: true,
|
|
99
|
+
updateItem: true,
|
|
100
|
+
deleteItem: true
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
it("role 'ANYONE' bypasses ABAC for update/remove but NOT for insert", () => {
|
|
104
|
+
const schema = makeSchema({
|
|
105
|
+
permissions: {
|
|
106
|
+
read: 'ANYONE',
|
|
107
|
+
insert: 'ANYONE',
|
|
108
|
+
update: 'ANYONE',
|
|
109
|
+
remove: 'ANYONE'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// User has ZERO ABAC permissions.
|
|
114
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, [], []);
|
|
115
|
+
|
|
116
|
+
// Insert intentionally does NOT honor ANYONE bypass (cms-web parity).
|
|
117
|
+
expect(result.itemPermissions.createItem).toBe(false);
|
|
118
|
+
expect(result.itemPermissions.updateItem).toBe(true);
|
|
119
|
+
expect(result.itemPermissions.deleteItem).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
it('allowedDataPermissions.itemRemove === false overrides role grant', () => {
|
|
122
|
+
const schema = makeSchema({
|
|
123
|
+
allowedDataPermissions: {
|
|
124
|
+
itemRemove: false
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
128
|
+
expect(result.itemPermissions.deleteItem).toBe(false);
|
|
129
|
+
expect(result.itemPermissions.updateItem).toBe(true);
|
|
130
|
+
expect(result.itemPermissions.createItem).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
it('EXTERNAL collection with no matching connection: schema not editable', () => {
|
|
133
|
+
const schema = makeSchema({
|
|
134
|
+
_id: 'mydb/products',
|
|
135
|
+
collectionType: 'EXTERNAL'
|
|
136
|
+
});
|
|
137
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, []);
|
|
138
|
+
expect(result.schemaPermissions).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
it('EXTERNAL collection with capable connection: schema editable', () => {
|
|
141
|
+
const schema = makeSchema({
|
|
142
|
+
_id: 'mydb/products',
|
|
143
|
+
collectionType: 'EXTERNAL'
|
|
144
|
+
});
|
|
145
|
+
const connections = [{
|
|
146
|
+
namespace: 'mydb',
|
|
147
|
+
capabilities: {
|
|
148
|
+
collectionModificationsSupported: true,
|
|
149
|
+
fieldTypes: ['TEXT', 'NUMBER']
|
|
150
|
+
}
|
|
151
|
+
}];
|
|
152
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, ALL_PERMS, connections);
|
|
153
|
+
expect(result.schemaPermissions).toEqual({
|
|
154
|
+
createField: true,
|
|
155
|
+
updateField: true,
|
|
156
|
+
deleteField: true
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it('NATIVE editable but missing field-management ABAC: schemaPermissions all false', () => {
|
|
160
|
+
const schema = makeSchema();
|
|
161
|
+
const userPermissions = [WIX_DATA.INSERT, WIX_DATA.UPDATE, WIX_DATA.REMOVE
|
|
162
|
+
// Note: no field-management perms.
|
|
163
|
+
];
|
|
164
|
+
const result = (0, _cmsPermissions.computeCmsPermissions)(schema, userPermissions, []);
|
|
165
|
+
|
|
166
|
+
// Schema is editable so schemaPermissions is defined, but each flag is false
|
|
167
|
+
// because the user lacks field-management ABAC.
|
|
168
|
+
expect(result.schemaPermissions).toEqual({
|
|
169
|
+
createField: false,
|
|
170
|
+
updateField: false,
|
|
171
|
+
deleteField: false
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=cmsPermissions.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_cmsPermissions","require","WIX_STORES","SITE_MEMBERS","FORM_APP","WIX_DATA","CREATE_FIELD","UPDATE_FIELD","DELETE_FIELD","INSERT","UPDATE","REMOVE","ALL_FIELD_PERMS","ALL_PERMS","makeSchema","overrides","base","_id","collectionType","capabilities","dataOperations","permissions","read","insert","update","remove","plugins","describe","it","schema","type","result","computeCmsPermissions","expect","schemaPermissions","toBeUndefined","itemPermissions","toEqual","createItem","updateItem","deleteItem","ownerAppId","toBe","createField","updateField","deleteField","allowedDataPermissions","itemRemove","connections","namespace","collectionModificationsSupported","fieldTypes","userPermissions"],"sources":["../../../../src/adapter/__tests__/cmsPermissions.spec.ts"],"sourcesContent":["import { collections } from '@wix/data';\nimport { computeCmsPermissions } from '../cmsPermissions';\nimport type { ExternalDbConnection } from '../cmsPermissions';\n\nconst WIX_STORES = '1380b703-ce81-ff05-f115-39571d94dfcd';\nconst SITE_MEMBERS = 'SiteMembers';\nconst FORM_APP = '225dd912-7dea-4738-8688-4b8c6955ffc2';\n\nconst WIX_DATA = {\n CREATE_FIELD: 'WIX_DATA.CREATE_COLLECTION_FIELD',\n UPDATE_FIELD: 'WIX_DATA.UPDATE_COLLECTION_FIELD',\n DELETE_FIELD: 'WIX_DATA.DELETE_COLLECTION_FIELD',\n INSERT: 'WIX_DATA.INSERT',\n UPDATE: 'WIX_DATA.UPDATE',\n REMOVE: 'WIX_DATA.REMOVE',\n};\n\nconst ALL_FIELD_PERMS = [\n WIX_DATA.CREATE_FIELD,\n WIX_DATA.UPDATE_FIELD,\n WIX_DATA.DELETE_FIELD,\n];\n\nconst ALL_PERMS = [\n ...ALL_FIELD_PERMS,\n WIX_DATA.INSERT,\n WIX_DATA.UPDATE,\n WIX_DATA.REMOVE,\n];\n\ntype SchemaWithAllowed = collections.DataCollection & {\n allowedDataPermissions?: {\n itemInsert?: boolean;\n itemUpdate?: boolean;\n itemRemove?: boolean;\n };\n};\n\nconst makeSchema = (\n overrides: Partial<SchemaWithAllowed> = {},\n): SchemaWithAllowed => {\n const base = {\n _id: 'col-1',\n collectionType: 'NATIVE',\n capabilities: { dataOperations: ['SAVE', 'UPDATE', 'REMOVE'] },\n permissions: {\n read: 'ANYONE',\n insert: 'SITE_MEMBER',\n update: 'SITE_MEMBER',\n remove: 'SITE_MEMBER',\n },\n plugins: [],\n };\n return { ...base, ...overrides } as SchemaWithAllowed;\n};\n\ndescribe('computeCmsPermissions', () => {\n it('shared collection: schema not editable, no item permissions', () => {\n const schema = makeSchema({\n plugins: [{ type: 'SHARED' }],\n });\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n // patterns-cms returns `schemaPermissions: undefined` (NOT an isSchemaEditable boolean)\n // when the schema is not structurally editable.\n expect(result.schemaPermissions).toBeUndefined();\n expect(result.itemPermissions).toEqual({\n createItem: false,\n updateItem: false,\n deleteItem: false,\n });\n });\n\n it('Wix-app collection (Stores): item permissions all false', () => {\n const schema = makeSchema({\n collectionType: 'WIX_APP',\n ownerAppId: WIX_STORES,\n });\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n expect(result.itemPermissions).toEqual({\n createItem: false,\n updateItem: false,\n deleteItem: false,\n });\n });\n\n it('Wix-app SiteMembers: NOT treated as readonly', () => {\n const schema = makeSchema({\n collectionType: 'WIX_APP',\n ownerAppId: SITE_MEMBERS,\n });\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n expect(result.itemPermissions.createItem).toBe(true);\n expect(result.itemPermissions.updateItem).toBe(true);\n expect(result.itemPermissions.deleteItem).toBe(true);\n });\n\n it('Wix-app Forms collection: NOT treated as readonly', () => {\n const schema = makeSchema({\n collectionType: 'WIX_APP',\n ownerAppId: FORM_APP,\n });\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n expect(result.itemPermissions.createItem).toBe(true);\n expect(result.itemPermissions.updateItem).toBe(true);\n expect(result.itemPermissions.deleteItem).toBe(true);\n });\n\n it('NATIVE collection with full ABAC + roles: all permissions granted', () => {\n const schema = makeSchema();\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n // Structurally editable → schemaPermissions is defined.\n expect(result.schemaPermissions).toEqual({\n createField: true,\n updateField: true,\n deleteField: true,\n });\n expect(result.itemPermissions).toEqual({\n createItem: true,\n updateItem: true,\n deleteItem: true,\n });\n });\n\n it(\"role 'ANYONE' bypasses ABAC for update/remove but NOT for insert\", () => {\n const schema = makeSchema({\n permissions: {\n read: 'ANYONE',\n insert: 'ANYONE',\n update: 'ANYONE',\n remove: 'ANYONE',\n },\n });\n\n // User has ZERO ABAC permissions.\n const result = computeCmsPermissions(schema, [], []);\n\n // Insert intentionally does NOT honor ANYONE bypass (cms-web parity).\n expect(result.itemPermissions.createItem).toBe(false);\n expect(result.itemPermissions.updateItem).toBe(true);\n expect(result.itemPermissions.deleteItem).toBe(true);\n });\n\n it('allowedDataPermissions.itemRemove === false overrides role grant', () => {\n const schema = makeSchema({\n allowedDataPermissions: { itemRemove: false },\n });\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n expect(result.itemPermissions.deleteItem).toBe(false);\n expect(result.itemPermissions.updateItem).toBe(true);\n expect(result.itemPermissions.createItem).toBe(true);\n });\n\n it('EXTERNAL collection with no matching connection: schema not editable', () => {\n const schema = makeSchema({\n _id: 'mydb/products',\n collectionType: 'EXTERNAL',\n });\n\n const result = computeCmsPermissions(schema, ALL_PERMS, []);\n\n expect(result.schemaPermissions).toBeUndefined();\n });\n\n it('EXTERNAL collection with capable connection: schema editable', () => {\n const schema = makeSchema({\n _id: 'mydb/products',\n collectionType: 'EXTERNAL',\n });\n const connections: ExternalDbConnection[] = [\n {\n namespace: 'mydb',\n capabilities: {\n collectionModificationsSupported: true,\n fieldTypes: ['TEXT', 'NUMBER'],\n },\n },\n ];\n\n const result = computeCmsPermissions(schema, ALL_PERMS, connections);\n\n expect(result.schemaPermissions).toEqual({\n createField: true,\n updateField: true,\n deleteField: true,\n });\n });\n\n it('NATIVE editable but missing field-management ABAC: schemaPermissions all false', () => {\n const schema = makeSchema();\n const userPermissions = [\n WIX_DATA.INSERT,\n WIX_DATA.UPDATE,\n WIX_DATA.REMOVE,\n // Note: no field-management perms.\n ];\n\n const result = computeCmsPermissions(schema, userPermissions, []);\n\n // Schema is editable so schemaPermissions is defined, but each flag is false\n // because the user lacks field-management ABAC.\n expect(result.schemaPermissions).toEqual({\n createField: false,\n updateField: false,\n deleteField: false,\n });\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _data = require("@wix/data");
|
|
4
|
+
var _createCmsBackend = require("../createCmsBackend");
|
|
5
|
+
// `bulkInsert`/`backup` wrap calls in errorHandler.withErrorHandler, which needs an
|
|
6
|
+
// initialized Wix SDK context. In a pure-function unit test there is none, so we
|
|
7
|
+
// stub the wrapper to just invoke its callback (the mapping logic is what we assert).
|
|
8
|
+
jest.mock('@wix/essentials', () => ({
|
|
9
|
+
errorHandler: {
|
|
10
|
+
withErrorHandler: fn => fn()
|
|
11
|
+
}
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// A chainable query stub whose terminal `.find()` resolves to a configurable
|
|
15
|
+
// result. `mockFindResult` is reassigned per-test before the backend call.
|
|
16
|
+
// `mock`-prefixed names are the only out-of-scope refs jest.mock() factories allow.
|
|
17
|
+
let mockFindResult = {
|
|
18
|
+
items: [],
|
|
19
|
+
totalCount: 0
|
|
20
|
+
};
|
|
21
|
+
jest.mock('@wix/data', () => {
|
|
22
|
+
const queryChain = {};
|
|
23
|
+
['eq', 'include', 'skip', 'limit', 'and', 'ge', 'le', 'ascending', 'descending'].forEach(m => {
|
|
24
|
+
queryChain[m] = jest.fn(() => queryChain);
|
|
25
|
+
});
|
|
26
|
+
queryChain.find = jest.fn(() => Promise.resolve(mockFindResult));
|
|
27
|
+
const filterChain = {};
|
|
28
|
+
['eq', 'contains', 'or', 'and', 'not'].forEach(m => {
|
|
29
|
+
filterChain[m] = jest.fn(() => filterChain);
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
__chains: {
|
|
33
|
+
queryChain,
|
|
34
|
+
filterChain
|
|
35
|
+
},
|
|
36
|
+
items: {
|
|
37
|
+
query: jest.fn(() => queryChain),
|
|
38
|
+
filter: jest.fn(() => filterChain),
|
|
39
|
+
insert: jest.fn(),
|
|
40
|
+
update: jest.fn(),
|
|
41
|
+
remove: jest.fn(),
|
|
42
|
+
bulkRemove: jest.fn(),
|
|
43
|
+
bulkInsert: jest.fn(),
|
|
44
|
+
replaceReferences: jest.fn()
|
|
45
|
+
},
|
|
46
|
+
backups: {}
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
51
|
+
const {
|
|
52
|
+
queryChain,
|
|
53
|
+
filterChain
|
|
54
|
+
} = require('@wix/data').__chains;
|
|
55
|
+
const queryMock = _data.items.query;
|
|
56
|
+
const insert = _data.items.insert;
|
|
57
|
+
const update = _data.items.update;
|
|
58
|
+
const remove = _data.items.remove;
|
|
59
|
+
const bulkRemove = _data.items.bulkRemove;
|
|
60
|
+
const bulkInsert = _data.items.bulkInsert;
|
|
61
|
+
const replaceReferences = _data.items.replaceReferences;
|
|
62
|
+
const COLLECTION_ID = 'collection-1';
|
|
63
|
+
const fieldsWithRefs = {
|
|
64
|
+
title: {
|
|
65
|
+
id: 'title',
|
|
66
|
+
type: 'SHORT_TEXT',
|
|
67
|
+
displayName: 'Title',
|
|
68
|
+
capabilities: {
|
|
69
|
+
supportedQueryOperators: [],
|
|
70
|
+
sortable: true
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
author: {
|
|
74
|
+
id: 'author',
|
|
75
|
+
type: 'REFERENCE',
|
|
76
|
+
displayName: 'Author',
|
|
77
|
+
referenceMetadata: {
|
|
78
|
+
referencedCollectionId: 'authors'
|
|
79
|
+
},
|
|
80
|
+
capabilities: {
|
|
81
|
+
supportedQueryOperators: [],
|
|
82
|
+
sortable: false
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
tags: {
|
|
86
|
+
id: 'tags',
|
|
87
|
+
type: 'MULTI_REFERENCE',
|
|
88
|
+
displayName: 'Tags',
|
|
89
|
+
referenceMetadata: {
|
|
90
|
+
referencedCollectionId: 'tags-collection'
|
|
91
|
+
},
|
|
92
|
+
capabilities: {
|
|
93
|
+
supportedQueryOperators: [],
|
|
94
|
+
sortable: false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
/** Calls `backend.find` with the per-call options arg (absent from the erased interface). */
|
|
99
|
+
const findWithOptions = (find, query, options) => find(query, options);
|
|
100
|
+
describe('createCmsBackend', () => {
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
jest.clearAllMocks();
|
|
103
|
+
mockFindResult = {
|
|
104
|
+
items: [],
|
|
105
|
+
totalCount: 0
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
describe('get', () => {
|
|
109
|
+
it('queries by _id and returns the single matching entity', async () => {
|
|
110
|
+
mockFindResult = {
|
|
111
|
+
items: [{
|
|
112
|
+
_id: 'e1',
|
|
113
|
+
title: 'Hello'
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {
|
|
117
|
+
title: fieldsWithRefs.title
|
|
118
|
+
});
|
|
119
|
+
const entity = await backend.get('e1');
|
|
120
|
+
expect(queryMock).toHaveBeenCalledWith(COLLECTION_ID);
|
|
121
|
+
expect(queryChain.eq).toHaveBeenCalledWith('_id', 'e1');
|
|
122
|
+
expect(entity).toEqual({
|
|
123
|
+
_id: 'e1',
|
|
124
|
+
title: 'Hello'
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
it('includes reference fields when the schema has them', async () => {
|
|
128
|
+
mockFindResult = {
|
|
129
|
+
items: [{
|
|
130
|
+
_id: 'e1'
|
|
131
|
+
}]
|
|
132
|
+
};
|
|
133
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, fieldsWithRefs);
|
|
134
|
+
await backend.get('e1');
|
|
135
|
+
expect(queryChain.include).toHaveBeenCalledWith('author', 'tags');
|
|
136
|
+
});
|
|
137
|
+
it('throws when no entity is found', async () => {
|
|
138
|
+
mockFindResult = {
|
|
139
|
+
items: []
|
|
140
|
+
};
|
|
141
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {
|
|
142
|
+
title: fieldsWithRefs.title
|
|
143
|
+
});
|
|
144
|
+
await expect(backend.get('missing')).rejects.toThrow('Entity not found: missing');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('create', () => {
|
|
148
|
+
it('inserts the entity and returns it with the created _id', async () => {
|
|
149
|
+
insert.mockResolvedValue({
|
|
150
|
+
_id: 'new-1'
|
|
151
|
+
});
|
|
152
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {
|
|
153
|
+
title: fieldsWithRefs.title
|
|
154
|
+
});
|
|
155
|
+
const result = await backend.create({
|
|
156
|
+
title: 'Hi'
|
|
157
|
+
});
|
|
158
|
+
expect(insert).toHaveBeenCalledWith(COLLECTION_ID, {
|
|
159
|
+
title: 'Hi'
|
|
160
|
+
});
|
|
161
|
+
expect(result).toEqual({
|
|
162
|
+
title: 'Hi',
|
|
163
|
+
_id: 'new-1'
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
it('syncs multi-reference fields via replaceReferences', async () => {
|
|
167
|
+
insert.mockResolvedValue({
|
|
168
|
+
_id: 'new-1'
|
|
169
|
+
});
|
|
170
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, fieldsWithRefs);
|
|
171
|
+
await backend.create({
|
|
172
|
+
title: 'Hi',
|
|
173
|
+
tags: [{
|
|
174
|
+
_id: 't1'
|
|
175
|
+
}, {
|
|
176
|
+
_id: 't2'
|
|
177
|
+
}]
|
|
178
|
+
});
|
|
179
|
+
expect(replaceReferences).toHaveBeenCalledWith(COLLECTION_ID, 'tags', 'new-1', ['t1', 't2']);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
describe('update', () => {
|
|
183
|
+
it('updates the entity and returns it', async () => {
|
|
184
|
+
update.mockResolvedValue(undefined);
|
|
185
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {
|
|
186
|
+
title: fieldsWithRefs.title
|
|
187
|
+
});
|
|
188
|
+
const entity = {
|
|
189
|
+
_id: 'e1',
|
|
190
|
+
title: 'Edited'
|
|
191
|
+
};
|
|
192
|
+
const result = await backend.update(entity);
|
|
193
|
+
expect(update).toHaveBeenCalledWith(COLLECTION_ID, entity);
|
|
194
|
+
expect(result).toBe(entity);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe('delete / bulkDelete', () => {
|
|
198
|
+
it('removes a single entity by id', async () => {
|
|
199
|
+
remove.mockResolvedValue(undefined);
|
|
200
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {});
|
|
201
|
+
await backend.delete('e1');
|
|
202
|
+
expect(remove).toHaveBeenCalledWith(COLLECTION_ID, 'e1');
|
|
203
|
+
});
|
|
204
|
+
it('bulk-removes entities by ids', async () => {
|
|
205
|
+
bulkRemove.mockResolvedValue(undefined);
|
|
206
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {});
|
|
207
|
+
await backend.bulkDelete(['e1', 'e2']);
|
|
208
|
+
expect(bulkRemove).toHaveBeenCalledWith(COLLECTION_ID, ['e1', 'e2']);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe('find', () => {
|
|
212
|
+
it('applies skip/limit and computes total and hasNext', async () => {
|
|
213
|
+
mockFindResult = {
|
|
214
|
+
items: [{
|
|
215
|
+
_id: 'a'
|
|
216
|
+
}, {
|
|
217
|
+
_id: 'b'
|
|
218
|
+
}],
|
|
219
|
+
totalCount: 5
|
|
220
|
+
};
|
|
221
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {
|
|
222
|
+
title: fieldsWithRefs.title
|
|
223
|
+
});
|
|
224
|
+
const result = await backend.find({
|
|
225
|
+
offset: 0,
|
|
226
|
+
limit: 2
|
|
227
|
+
});
|
|
228
|
+
expect(queryChain.skip).toHaveBeenCalledWith(0);
|
|
229
|
+
expect(queryChain.limit).toHaveBeenCalledWith(2);
|
|
230
|
+
expect(queryChain.find).toHaveBeenCalledWith({
|
|
231
|
+
returnTotalCount: true
|
|
232
|
+
});
|
|
233
|
+
expect(result.items).toEqual([{
|
|
234
|
+
_id: 'a'
|
|
235
|
+
}, {
|
|
236
|
+
_id: 'b'
|
|
237
|
+
}]);
|
|
238
|
+
expect(result.total).toBe(5);
|
|
239
|
+
// 0 + 2 < 5 → there is a next page.
|
|
240
|
+
expect(result.hasNext).toBe(true);
|
|
241
|
+
});
|
|
242
|
+
it('reports hasNext false on the last page', async () => {
|
|
243
|
+
mockFindResult = {
|
|
244
|
+
items: [{
|
|
245
|
+
_id: 'e'
|
|
246
|
+
}],
|
|
247
|
+
totalCount: 3
|
|
248
|
+
};
|
|
249
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {
|
|
250
|
+
title: fieldsWithRefs.title
|
|
251
|
+
});
|
|
252
|
+
const result = await backend.find({
|
|
253
|
+
offset: 2,
|
|
254
|
+
limit: 2
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// 2 + 1 === 3 → no next page.
|
|
258
|
+
expect(result.hasNext).toBe(false);
|
|
259
|
+
});
|
|
260
|
+
it('includes only reference fields that are also searchable', async () => {
|
|
261
|
+
mockFindResult = {
|
|
262
|
+
items: [],
|
|
263
|
+
totalCount: 0
|
|
264
|
+
};
|
|
265
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, fieldsWithRefs);
|
|
266
|
+
|
|
267
|
+
// The per-call `options` arg is part of the concrete `createCmsBackend.find`
|
|
268
|
+
// but not the erased `CollectionBackend.find` contract — cast to reach it.
|
|
269
|
+
await findWithOptions(backend.find, {
|
|
270
|
+
offset: 0,
|
|
271
|
+
limit: 10
|
|
272
|
+
}, {
|
|
273
|
+
searchableFieldIds: ['title', 'author']
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// 'author' is a reference and searchable → included; 'tags' is not searchable.
|
|
277
|
+
expect(queryChain.include).toHaveBeenCalledWith('author');
|
|
278
|
+
});
|
|
279
|
+
it('does not call include when no reference field is searchable', async () => {
|
|
280
|
+
mockFindResult = {
|
|
281
|
+
items: [],
|
|
282
|
+
totalCount: 0
|
|
283
|
+
};
|
|
284
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, fieldsWithRefs);
|
|
285
|
+
await findWithOptions(backend.find, {
|
|
286
|
+
offset: 0,
|
|
287
|
+
limit: 10
|
|
288
|
+
}, {
|
|
289
|
+
searchableFieldIds: ['title']
|
|
290
|
+
});
|
|
291
|
+
expect(queryChain.include).not.toHaveBeenCalled();
|
|
292
|
+
});
|
|
293
|
+
it('includes no reference fields when called with no options (inert default)', async () => {
|
|
294
|
+
mockFindResult = {
|
|
295
|
+
items: [],
|
|
296
|
+
totalCount: 0
|
|
297
|
+
};
|
|
298
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, fieldsWithRefs);
|
|
299
|
+
|
|
300
|
+
// No options → default searchableFieldIds = [] → no refs included, no search.
|
|
301
|
+
await backend.find({
|
|
302
|
+
offset: 0,
|
|
303
|
+
limit: 10
|
|
304
|
+
});
|
|
305
|
+
expect(queryChain.include).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// The cases below mirror auto-patterns' end-to-end coverage: the fetch path
|
|
309
|
+
// must actually apply the query's filters/sort/search, not just skip/limit.
|
|
310
|
+
const filterableFields = {
|
|
311
|
+
title: fieldsWithRefs.title,
|
|
312
|
+
price: {
|
|
313
|
+
id: 'price',
|
|
314
|
+
type: 'NUMBER',
|
|
315
|
+
displayName: 'Price',
|
|
316
|
+
capabilities: {
|
|
317
|
+
supportedQueryOperators: ['GTE', 'LTE'],
|
|
318
|
+
sortable: true
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
done: {
|
|
322
|
+
id: 'done',
|
|
323
|
+
type: 'BOOLEAN',
|
|
324
|
+
displayName: 'Done',
|
|
325
|
+
capabilities: {
|
|
326
|
+
supportedQueryOperators: [],
|
|
327
|
+
sortable: false
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
it('applies a number range filter from the query', async () => {
|
|
332
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, filterableFields);
|
|
333
|
+
await findWithOptions(backend.find, {
|
|
334
|
+
offset: 0,
|
|
335
|
+
limit: 10,
|
|
336
|
+
filters: {
|
|
337
|
+
price: {
|
|
338
|
+
from: 10,
|
|
339
|
+
to: 100
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}, {
|
|
343
|
+
filterFieldMapping: {
|
|
344
|
+
price: {
|
|
345
|
+
fieldId: 'price'
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
expect(queryChain.ge).toHaveBeenCalledWith('price', 10);
|
|
350
|
+
expect(queryChain.le).toHaveBeenCalledWith('price', 100);
|
|
351
|
+
});
|
|
352
|
+
it('applies a checked boolean filter from the query', async () => {
|
|
353
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, filterableFields);
|
|
354
|
+
await findWithOptions(backend.find, {
|
|
355
|
+
offset: 0,
|
|
356
|
+
limit: 10,
|
|
357
|
+
filters: {
|
|
358
|
+
done: [{
|
|
359
|
+
id: 'checked'
|
|
360
|
+
}]
|
|
361
|
+
}
|
|
362
|
+
}, {
|
|
363
|
+
filterFieldMapping: {
|
|
364
|
+
done: {
|
|
365
|
+
fieldId: 'done'
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
expect(queryChain.eq).toHaveBeenCalledWith('done', true);
|
|
370
|
+
});
|
|
371
|
+
it('applies ascending sort from the query', async () => {
|
|
372
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, filterableFields);
|
|
373
|
+
await backend.find({
|
|
374
|
+
offset: 0,
|
|
375
|
+
limit: 10,
|
|
376
|
+
sort: [{
|
|
377
|
+
fieldName: 'title',
|
|
378
|
+
order: 'asc'
|
|
379
|
+
}]
|
|
380
|
+
});
|
|
381
|
+
expect(queryChain.ascending).toHaveBeenCalledWith('title');
|
|
382
|
+
});
|
|
383
|
+
it('applies descending sort from the query', async () => {
|
|
384
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, filterableFields);
|
|
385
|
+
await backend.find({
|
|
386
|
+
offset: 0,
|
|
387
|
+
limit: 10,
|
|
388
|
+
sort: [{
|
|
389
|
+
fieldName: 'title',
|
|
390
|
+
order: 'desc'
|
|
391
|
+
}]
|
|
392
|
+
});
|
|
393
|
+
expect(queryChain.descending).toHaveBeenCalledWith('title');
|
|
394
|
+
});
|
|
395
|
+
it('applies the search term as a contains filter on searchable text fields', async () => {
|
|
396
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, filterableFields);
|
|
397
|
+
await findWithOptions(backend.find, {
|
|
398
|
+
offset: 0,
|
|
399
|
+
limit: 10,
|
|
400
|
+
search: 'hello'
|
|
401
|
+
}, {
|
|
402
|
+
searchableFieldIds: ['title']
|
|
403
|
+
});
|
|
404
|
+
expect(filterChain.contains).toHaveBeenCalledWith('title', 'hello');
|
|
405
|
+
expect(queryChain.and).toHaveBeenCalledWith(filterChain);
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
describe('bulkInsert', () => {
|
|
409
|
+
it('maps inserted/updated to succeeded, errors to failed, and surfaces error rows', async () => {
|
|
410
|
+
bulkInsert.mockResolvedValue({
|
|
411
|
+
inserted: 2,
|
|
412
|
+
updated: 1,
|
|
413
|
+
errors: [{
|
|
414
|
+
originalIndex: 4,
|
|
415
|
+
message: 'bad row',
|
|
416
|
+
code: 'INVALID'
|
|
417
|
+
}]
|
|
418
|
+
});
|
|
419
|
+
const backend = (0, _createCmsBackend.createCmsBackend)(COLLECTION_ID, {});
|
|
420
|
+
const result = await backend.bulkInsert([{
|
|
421
|
+
a: 1
|
|
422
|
+
}, {
|
|
423
|
+
a: 2
|
|
424
|
+
}]);
|
|
425
|
+
expect(bulkInsert).toHaveBeenCalledWith(COLLECTION_ID, [{
|
|
426
|
+
a: 1
|
|
427
|
+
}, {
|
|
428
|
+
a: 2
|
|
429
|
+
}]);
|
|
430
|
+
expect(result.succeeded).toBe(3);
|
|
431
|
+
expect(result.failed).toBe(1);
|
|
432
|
+
expect(result.errors).toEqual([{
|
|
433
|
+
rowIndex: 4,
|
|
434
|
+
message: 'bad row',
|
|
435
|
+
code: 'INVALID'
|
|
436
|
+
}]);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
//# sourceMappingURL=createCmsBackend.spec.js.map
|