@saltcorn/server 0.8.8-beta.1 → 0.8.8-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/load_plugins.js +8 -1
- package/locales/en.json +6 -1
- package/locales/ru.json +4 -1
- package/package.json +8 -8
- package/public/saltcorn.js +44 -1
- package/routes/admin.js +98 -2
- package/routes/fields.js +0 -8
- package/routes/scapi.js +1 -1
- package/routes/sync.js +293 -57
- package/routes/tables.js +19 -1
- package/tests/plugin_install.test.js +114 -0
- package/tests/plugins.test.js +2 -102
- package/tests/sync.test.js +451 -75
- package/tests/view.test.js +2 -0
- package/tests/viewedit.test.js +2 -0
- package/wrapper.js +6 -4
package/tests/sync.test.js
CHANGED
|
@@ -4,10 +4,10 @@ const {
|
|
|
4
4
|
getUserLoginCookie,
|
|
5
5
|
getAdminLoginCookie,
|
|
6
6
|
resetToFixtures,
|
|
7
|
-
notAuthorized,
|
|
8
7
|
respondJsonWith,
|
|
9
8
|
} = require("../auth/testhelp");
|
|
10
9
|
const db = require("@saltcorn/data/db");
|
|
10
|
+
const { sleep } = require("@saltcorn/data/tests/mocks");
|
|
11
11
|
|
|
12
12
|
const Table = require("@saltcorn/data/models/table");
|
|
13
13
|
|
|
@@ -16,93 +16,469 @@ beforeAll(async () => {
|
|
|
16
16
|
});
|
|
17
17
|
afterAll(db.close);
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
jest.setTimeout(10000);
|
|
20
|
+
|
|
21
|
+
const initSyncInfo = async (tbls) => {
|
|
22
|
+
for (const tbl of tbls) {
|
|
23
|
+
const books = Table.findOne({ name: tbl });
|
|
24
|
+
if (books.has_sync_info) await db.deleteWhere(`${tbl}_sync_info`, {});
|
|
25
|
+
else {
|
|
26
|
+
books.has_sync_info = true;
|
|
27
|
+
await books.update(books);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe("load remote insert/updates", () => {
|
|
33
|
+
if (!db.isSQLite) {
|
|
34
|
+
beforeAll(async () => {
|
|
35
|
+
await initSyncInfo(["books", "publisher"]);
|
|
36
|
+
});
|
|
37
|
+
it("check params", async () => {
|
|
26
38
|
const app = await getApp({ disableCsrf: true });
|
|
27
|
-
const loginCookie = await
|
|
39
|
+
const loginCookie = await getAdminLoginCookie();
|
|
28
40
|
await request(app)
|
|
29
|
-
.post("/sync/
|
|
41
|
+
.post("/sync/load_changes")
|
|
30
42
|
.set("Cookie", loginCookie)
|
|
31
43
|
.send({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
name: "Brad Pitt",
|
|
36
|
-
favbook: 2,
|
|
37
|
-
parent: 1,
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 84,
|
|
41
|
-
name: "Pitt Brad",
|
|
42
|
-
favbook: 2,
|
|
43
|
-
parent: 1,
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
books: [
|
|
47
|
-
{
|
|
48
|
-
id: 3,
|
|
49
|
-
author: "foo",
|
|
50
|
-
pages: 20,
|
|
51
|
-
publisher: 1,
|
|
52
|
-
},
|
|
53
|
-
],
|
|
44
|
+
syncInfos: {
|
|
45
|
+
books: {},
|
|
54
46
|
},
|
|
55
47
|
})
|
|
56
|
-
.expect(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
.expect(
|
|
49
|
+
respondJsonWith(400, (resp) => resp.error === "loadUntil is missing")
|
|
50
|
+
);
|
|
51
|
+
await request(app)
|
|
52
|
+
.post("/sync/load_changes")
|
|
53
|
+
.set("Cookie", loginCookie)
|
|
54
|
+
.send({
|
|
55
|
+
loadUntil: new Date().valueOf(),
|
|
56
|
+
})
|
|
57
|
+
.expect(
|
|
58
|
+
respondJsonWith(400, (resp) => resp.error === "syncInfos is missing")
|
|
59
|
+
);
|
|
60
|
+
});
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
62
|
+
it("without syncFrom", async () => {
|
|
63
|
+
const app = await getApp({ disableCsrf: true });
|
|
64
|
+
const loginCookie = await getAdminLoginCookie();
|
|
67
65
|
const books = Table.findOne({ name: "books" });
|
|
68
|
-
const
|
|
69
|
-
const
|
|
66
|
+
const dbLength = await books.countRows();
|
|
67
|
+
const loadUntil = new Date();
|
|
68
|
+
const resp = await request(app)
|
|
69
|
+
.post("/sync/load_changes")
|
|
70
|
+
.set("Cookie", loginCookie)
|
|
71
|
+
.send({
|
|
72
|
+
loadUntil: loadUntil.valueOf(),
|
|
73
|
+
syncInfos: {
|
|
74
|
+
books: {
|
|
75
|
+
maxLoadedId: 0,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
expect(resp.status).toBe(200);
|
|
80
|
+
const data = resp._body;
|
|
81
|
+
expect(data.books.rows.length).toBe(dbLength);
|
|
82
|
+
for (const row of data.books.rows) {
|
|
83
|
+
const fromDb = await books.getRows({ id: row._sync_info_tbl_ref_ });
|
|
84
|
+
expect(fromDb.length).toBe(1);
|
|
85
|
+
expect(row._sync_info_tbl_last_modified_).toBe(loadUntil.valueOf());
|
|
86
|
+
expect(row._sync_info_tbl_deleted_).toBe(null);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("with syncFrom, without sync_infos", async () => {
|
|
70
91
|
const app = await getApp({ disableCsrf: true });
|
|
71
92
|
const loginCookie = await getAdminLoginCookie();
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
const loadUntil = new Date();
|
|
94
|
+
const resp = await request(app)
|
|
95
|
+
.post("/sync/load_changes")
|
|
74
96
|
.set("Cookie", loginCookie)
|
|
75
97
|
.send({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
98
|
+
loadUntil: loadUntil.valueOf(),
|
|
99
|
+
syncInfos: {
|
|
100
|
+
books: {
|
|
101
|
+
maxLoadedId: 0,
|
|
102
|
+
syncFrom: 1000,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
expect(resp.status).toBe(200);
|
|
107
|
+
const data = resp._body;
|
|
108
|
+
expect(data.books.rows.length).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("with syncFrom, with sync_infos", async () => {
|
|
112
|
+
const app = await getApp({ disableCsrf: true });
|
|
113
|
+
const loginCookie = await getAdminLoginCookie();
|
|
114
|
+
const books = Table.findOne({ name: "books" });
|
|
115
|
+
await books.updateRow({ author: "Herman Melville" }, 1);
|
|
116
|
+
await sleep(200);
|
|
117
|
+
const dbTime = await db.time();
|
|
118
|
+
await books.updateRow({ author: "Leo Tolstoy" }, 2);
|
|
119
|
+
const { last_modified } = await books.latestSyncInfo(2);
|
|
120
|
+
{
|
|
121
|
+
const resp = await request(app)
|
|
122
|
+
.post("/sync/load_changes")
|
|
123
|
+
.set("Cookie", loginCookie)
|
|
124
|
+
.send({
|
|
125
|
+
loadUntil: (await db.time()).valueOf(),
|
|
126
|
+
syncInfos: {
|
|
127
|
+
books: {
|
|
128
|
+
maxLoadedId: 0,
|
|
129
|
+
syncFrom: dbTime.valueOf(),
|
|
88
130
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
expect(resp.status).toBe(200);
|
|
134
|
+
const data = resp._body;
|
|
135
|
+
expect(data.books.rows.length).toBe(1);
|
|
136
|
+
|
|
137
|
+
const {
|
|
138
|
+
_sync_info_tbl_ref_,
|
|
139
|
+
_sync_info_tbl_last_modified_,
|
|
140
|
+
_sync_info_tbl_deleted_,
|
|
141
|
+
...rest
|
|
142
|
+
} = data.books.rows[0];
|
|
143
|
+
expect(_sync_info_tbl_ref_).toBe(2);
|
|
144
|
+
expect(_sync_info_tbl_last_modified_).toBe(last_modified.valueOf());
|
|
145
|
+
expect(_sync_info_tbl_deleted_).toBe(false);
|
|
146
|
+
expect(rest.author).toBe("Leo Tolstoy");
|
|
147
|
+
}
|
|
148
|
+
await books.updateRow({ author: "Herman Melville" }, 1);
|
|
149
|
+
{
|
|
150
|
+
const resp = await request(app)
|
|
151
|
+
.post("/sync/load_changes")
|
|
152
|
+
.set("Cookie", loginCookie)
|
|
153
|
+
.send({
|
|
154
|
+
loadUntil: (await db.time()).valueOf(),
|
|
155
|
+
syncInfos: {
|
|
156
|
+
books: {
|
|
157
|
+
maxLoadedId: 0,
|
|
158
|
+
syncFrom: dbTime.valueOf(),
|
|
96
159
|
},
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
expect(resp.status).toBe(200);
|
|
163
|
+
const data = resp._body;
|
|
164
|
+
expect(data.books.rows.length).toBe(2);
|
|
165
|
+
for (const row of data.books.rows) {
|
|
166
|
+
const {
|
|
167
|
+
_sync_info_tbl_ref_,
|
|
168
|
+
_sync_info_tbl_last_modified_,
|
|
169
|
+
_sync_info_tbl_deleted_,
|
|
170
|
+
...rest
|
|
171
|
+
} = row;
|
|
172
|
+
expect(_sync_info_tbl_ref_).toBe(rest.id);
|
|
173
|
+
const { last_modified } = await books.latestSyncInfo(rest.id);
|
|
174
|
+
expect(_sync_info_tbl_last_modified_).toBe(last_modified.valueOf());
|
|
175
|
+
expect(_sync_info_tbl_deleted_).toBe(false);
|
|
176
|
+
}
|
|
177
|
+
expect(data.books.rows[0].author).toBe("Herman Melville");
|
|
178
|
+
expect(data.books.rows[1].author).toBe("Leo Tolstoy");
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
} else
|
|
182
|
+
it("only pq support", () => {
|
|
183
|
+
expect(true).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// describe("load remote deletes", () => {});
|
|
188
|
+
|
|
189
|
+
describe("Upload changes", () => {
|
|
190
|
+
const doUpload = async (app, loginCookie, syncTimestamp, changes) => {
|
|
191
|
+
const resp = await request(app)
|
|
192
|
+
.post("/sync/offline_changes")
|
|
193
|
+
.set("Cookie", loginCookie)
|
|
194
|
+
.send({
|
|
195
|
+
syncTimestamp,
|
|
196
|
+
changes,
|
|
197
|
+
});
|
|
198
|
+
return resp;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const getResult = async (app, loginCookie, syncDir) => {
|
|
202
|
+
let pollCount = 0;
|
|
203
|
+
while (pollCount < 10) {
|
|
204
|
+
const resp = await request(app)
|
|
205
|
+
.get(`/sync/upload_finished?dir_name=${encodeURIComponent(syncDir)}`)
|
|
206
|
+
.set("Cookie", loginCookie);
|
|
207
|
+
expect(resp.status).toBe(200);
|
|
208
|
+
const { finished, translatedIds, error } = resp._body;
|
|
209
|
+
if (finished) return translatedIds ? translatedIds : error;
|
|
210
|
+
await sleep(1000);
|
|
106
211
|
}
|
|
107
|
-
|
|
212
|
+
return null;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const cleanSyncDir = async (app, loginCookie, syncDir) => {
|
|
216
|
+
const resp = await request(app)
|
|
217
|
+
.post("/sync/clean_sync_dir")
|
|
218
|
+
.send({ dir_name: syncDir })
|
|
219
|
+
.set("Cookie", loginCookie);
|
|
220
|
+
expect(resp.status).toBe(200);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const maxId = async (tblName) => {
|
|
224
|
+
const table = Table.findOne({ name: tblName });
|
|
225
|
+
const pkName = table.pk_name;
|
|
226
|
+
const rows = await table.getRows({}, { orderBy: pkName, orderDesc: true });
|
|
227
|
+
return rows.length === 0 ? 0 : rows[0][pkName];
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (!db.isSQLite) {
|
|
231
|
+
beforeAll(async () => {
|
|
232
|
+
await initSyncInfo(["books", "publisher"]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("inserts with translations", async () => {
|
|
236
|
+
const app = await getApp({ disableCsrf: true });
|
|
237
|
+
const loginCookie = await getAdminLoginCookie();
|
|
238
|
+
const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
|
|
239
|
+
books: {
|
|
240
|
+
inserts: [
|
|
241
|
+
{
|
|
242
|
+
id: 1,
|
|
243
|
+
author: "app agi",
|
|
244
|
+
pages: 1,
|
|
245
|
+
publisher: 1,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
},
|
|
249
|
+
publisher: {
|
|
250
|
+
inserts: [
|
|
251
|
+
{
|
|
252
|
+
id: 1,
|
|
253
|
+
name: "agi",
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
expect(resp.status).toBe(200);
|
|
259
|
+
const { syncDir } = resp._body;
|
|
260
|
+
const translatedIds = await getResult(app, loginCookie, syncDir);
|
|
261
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
262
|
+
expect(translatedIds).toBeDefined();
|
|
263
|
+
expect(translatedIds).toEqual({
|
|
264
|
+
books: {
|
|
265
|
+
1: 3,
|
|
266
|
+
},
|
|
267
|
+
publisher: {
|
|
268
|
+
1: 3,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("update with translation", async () => {
|
|
274
|
+
const app = await getApp({ disableCsrf: true });
|
|
275
|
+
const loginCookie = await getAdminLoginCookie();
|
|
276
|
+
const maxPublId = await maxId("publisher");
|
|
277
|
+
const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
|
|
278
|
+
books: {
|
|
279
|
+
updates: [
|
|
280
|
+
{
|
|
281
|
+
id: 1,
|
|
282
|
+
publisher: 1,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
publisher: {
|
|
287
|
+
inserts: [
|
|
288
|
+
{
|
|
289
|
+
id: 1,
|
|
290
|
+
name: "my_agi",
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
expect(resp.status).toBe(200);
|
|
296
|
+
const { syncDir } = resp._body;
|
|
297
|
+
const translatedIds = await getResult(app, loginCookie, syncDir);
|
|
298
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
299
|
+
expect(translatedIds).toBeDefined();
|
|
300
|
+
expect(translatedIds).toEqual({
|
|
301
|
+
publisher: {
|
|
302
|
+
1: maxPublId + 1,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
// TODO check update conflics on book
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("deletes normal", async () => {
|
|
309
|
+
const books = Table.findOne({ name: "books" });
|
|
310
|
+
const syncTimeStamp = new Date();
|
|
311
|
+
const oldRows = await books.getRows();
|
|
312
|
+
const newId = await books.insertRow(
|
|
313
|
+
{ author: "agi", pages: 22 },
|
|
314
|
+
null,
|
|
315
|
+
null,
|
|
316
|
+
false,
|
|
317
|
+
syncTimeStamp
|
|
318
|
+
);
|
|
319
|
+
const newRows = await books.getRows();
|
|
320
|
+
expect(newRows.length).toBe(oldRows.length + 1);
|
|
321
|
+
const app = await getApp({ disableCsrf: true });
|
|
322
|
+
const loginCookie = await getAdminLoginCookie();
|
|
323
|
+
const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
|
|
324
|
+
books: {
|
|
325
|
+
deletes: [
|
|
326
|
+
{
|
|
327
|
+
id: newId,
|
|
328
|
+
last_modified: syncTimeStamp.valueOf(),
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
expect(resp.status).toBe(200);
|
|
334
|
+
const { syncDir } = resp._body;
|
|
335
|
+
const translatedIds = await getResult(app, loginCookie, syncDir);
|
|
336
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
337
|
+
expect(translatedIds).toBeDefined();
|
|
338
|
+
const afterDelete = await books.getRows();
|
|
339
|
+
expect(afterDelete.length).toBe(oldRows.length);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// skip delete because of larger last_modified on the server side
|
|
343
|
+
it("deletes with conflicts", async () => {
|
|
344
|
+
const books = Table.findOne({ name: "books" });
|
|
345
|
+
const syncTimeStamp = new Date();
|
|
346
|
+
const oldRows = await books.getRows();
|
|
347
|
+
const newId = await books.insertRow(
|
|
348
|
+
{ author: "my_agi", pages: 22 },
|
|
349
|
+
null,
|
|
350
|
+
null,
|
|
351
|
+
false,
|
|
352
|
+
syncTimeStamp
|
|
353
|
+
);
|
|
354
|
+
await books.updateRow(
|
|
355
|
+
{ pages: 11 },
|
|
356
|
+
newId,
|
|
357
|
+
undefined,
|
|
358
|
+
undefined,
|
|
359
|
+
undefined,
|
|
360
|
+
undefined,
|
|
361
|
+
new Date(syncTimeStamp.valueOf() + 1)
|
|
362
|
+
);
|
|
363
|
+
const newRows = await books.getRows();
|
|
364
|
+
expect(newRows.length).toBe(oldRows.length + 1);
|
|
365
|
+
const app = await getApp({ disableCsrf: true });
|
|
366
|
+
const loginCookie = await getAdminLoginCookie();
|
|
367
|
+
const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
|
|
368
|
+
books: {
|
|
369
|
+
deletes: [
|
|
370
|
+
{
|
|
371
|
+
id: newId,
|
|
372
|
+
last_modified: syncTimeStamp.valueOf(),
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
expect(resp.status).toBe(200);
|
|
378
|
+
const { syncDir } = resp._body;
|
|
379
|
+
const translatedIds = await getResult(app, loginCookie, syncDir);
|
|
380
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
381
|
+
expect(translatedIds).toBeDefined();
|
|
382
|
+
const afterDelete = await books.getRows();
|
|
383
|
+
expect(afterDelete.length).toBe(newRows.length);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("insert not authorized", async () => {
|
|
387
|
+
const books = Table.findOne({ name: "books" });
|
|
388
|
+
books.min_role_write = 1;
|
|
389
|
+
await books.update(books);
|
|
390
|
+
const app = await getApp({ disableCsrf: true });
|
|
391
|
+
const loginCookie = await getUserLoginCookie();
|
|
392
|
+
const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
|
|
393
|
+
books: {
|
|
394
|
+
inserts: [
|
|
395
|
+
{
|
|
396
|
+
id: 1,
|
|
397
|
+
author: "app agi",
|
|
398
|
+
pages: 1,
|
|
399
|
+
publisher: 1,
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
},
|
|
403
|
+
publisher: {
|
|
404
|
+
inserts: [
|
|
405
|
+
{
|
|
406
|
+
id: 1,
|
|
407
|
+
name: "agi",
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
expect(resp.status).toBe(200);
|
|
413
|
+
const { syncDir } = resp._body;
|
|
414
|
+
const error = await getResult(app, loginCookie, syncDir);
|
|
415
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
416
|
+
expect(error).toBeDefined();
|
|
417
|
+
expect(error).toEqual({ message: "Unable to insert into books" });
|
|
418
|
+
books.min_role_write = 100;
|
|
419
|
+
await books.update(books);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it("update not authorized", async () => {
|
|
423
|
+
const books = Table.findOne({ name: "books" });
|
|
424
|
+
books.min_role_write = 1;
|
|
425
|
+
await books.update(books);
|
|
426
|
+
|
|
427
|
+
const app = await getApp({ disableCsrf: true });
|
|
428
|
+
const loginCookie = await getUserLoginCookie();
|
|
429
|
+
const resp = await doUpload(app, loginCookie, new Date().valueOf(), {
|
|
430
|
+
books: {
|
|
431
|
+
updates: [
|
|
432
|
+
{
|
|
433
|
+
id: 1,
|
|
434
|
+
pages: 1,
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
expect(resp.status).toBe(200);
|
|
440
|
+
const { syncDir } = resp._body;
|
|
441
|
+
const error = await getResult(app, loginCookie, syncDir);
|
|
442
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
443
|
+
expect(error).toBeDefined();
|
|
444
|
+
expect(error).toEqual({
|
|
445
|
+
message: "Unable to update books: Not authorized",
|
|
446
|
+
});
|
|
447
|
+
books.min_role_write = 100;
|
|
448
|
+
await books.update(books);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("delete not authorized", async () => {
|
|
452
|
+
const syncTimeStamp = new Date();
|
|
453
|
+
const books = Table.findOne({ name: "books" });
|
|
454
|
+
books.min_role_write = 1;
|
|
455
|
+
await books.update(books);
|
|
456
|
+
|
|
457
|
+
const app = await getApp({ disableCsrf: true });
|
|
458
|
+
const loginCookie = await getUserLoginCookie();
|
|
459
|
+
const resp = await doUpload(app, loginCookie, syncTimeStamp.valueOf(), {
|
|
460
|
+
books: {
|
|
461
|
+
deletes: [
|
|
462
|
+
{
|
|
463
|
+
id: 1,
|
|
464
|
+
last_modified: syncTimeStamp.valueOf() + 1,
|
|
465
|
+
},
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
expect(resp.status).toBe(200);
|
|
470
|
+
const { syncDir } = resp._body;
|
|
471
|
+
const error = await getResult(app, loginCookie, syncDir);
|
|
472
|
+
await cleanSyncDir(app, loginCookie, syncDir);
|
|
473
|
+
expect(error).toBeDefined();
|
|
474
|
+
expect(error).toEqual({
|
|
475
|
+
message: "Unable to delete in 'books': Some rows were not deleted",
|
|
476
|
+
});
|
|
477
|
+
books.min_role_write = 100;
|
|
478
|
+
await books.update(books);
|
|
479
|
+
});
|
|
480
|
+
} else
|
|
481
|
+
it("only pq support", () => {
|
|
482
|
+
expect(true).toBe(true);
|
|
483
|
+
});
|
|
108
484
|
});
|
package/tests/view.test.js
CHANGED
package/tests/viewedit.test.js
CHANGED
package/wrapper.js
CHANGED
|
@@ -312,7 +312,8 @@ module.exports = (version_tag) =>
|
|
|
312
312
|
|
|
313
313
|
if (req.xhr) {
|
|
314
314
|
const renderToHtml = layout.renderBody
|
|
315
|
-
? (h, role) =>
|
|
315
|
+
? (h, role, req) =>
|
|
316
|
+
layout.renderBody({ title, body: h, role, alerts, req })
|
|
316
317
|
: defaultRenderToHtml;
|
|
317
318
|
res.header(
|
|
318
319
|
"Cache-Control",
|
|
@@ -322,8 +323,8 @@ module.exports = (version_tag) =>
|
|
|
322
323
|
res.set("Page-Title", encodeURIComponent(title));
|
|
323
324
|
res.send(
|
|
324
325
|
html.length === 1
|
|
325
|
-
? renderToHtml(html[0], role)
|
|
326
|
-
: html.map((h) => renderToHtml(h, role)).join("")
|
|
326
|
+
? renderToHtml(html[0], role, req)
|
|
327
|
+
: html.map((h) => renderToHtml(h, role, req)).join("")
|
|
327
328
|
);
|
|
328
329
|
return;
|
|
329
330
|
}
|
|
@@ -356,11 +357,12 @@ module.exports = (version_tag) =>
|
|
|
356
357
|
* @param role
|
|
357
358
|
* @returns {string|string|*}
|
|
358
359
|
*/
|
|
359
|
-
const defaultRenderToHtml = (s, role) =>
|
|
360
|
+
const defaultRenderToHtml = (s, role, req) =>
|
|
360
361
|
typeof s === "string"
|
|
361
362
|
? s
|
|
362
363
|
: renderLayout({
|
|
363
364
|
blockDispatch: {},
|
|
364
365
|
role,
|
|
366
|
+
req,
|
|
365
367
|
layout: s,
|
|
366
368
|
});
|