@saltcorn/server 0.9.4-beta.9 → 0.9.5-beta.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.
Files changed (183) hide show
  1. package/app.js +16 -1
  2. package/auth/admin.js +19 -3
  3. package/auth/routes.js +16 -4
  4. package/auth/testhelp.js +17 -1
  5. package/load_plugins.js +8 -2
  6. package/locales/en.json +24 -2
  7. package/markup/admin.js +22 -18
  8. package/package.json +10 -9
  9. package/public/dayjslocales/af.js +1 -0
  10. package/public/dayjslocales/am.js +1 -0
  11. package/public/dayjslocales/ar-dz.js +1 -0
  12. package/public/dayjslocales/ar-iq.js +1 -0
  13. package/public/dayjslocales/ar-kw.js +1 -0
  14. package/public/dayjslocales/ar-ly.js +1 -0
  15. package/public/dayjslocales/ar-ma.js +1 -0
  16. package/public/dayjslocales/ar-sa.js +1 -0
  17. package/public/dayjslocales/ar-tn.js +1 -0
  18. package/public/dayjslocales/ar.js +1 -0
  19. package/public/dayjslocales/az.js +1 -0
  20. package/public/dayjslocales/be.js +1 -0
  21. package/public/dayjslocales/bg.js +1 -0
  22. package/public/dayjslocales/bi.js +1 -0
  23. package/public/dayjslocales/bm.js +1 -0
  24. package/public/dayjslocales/bn-bd.js +1 -0
  25. package/public/dayjslocales/bn.js +1 -0
  26. package/public/dayjslocales/bo.js +1 -0
  27. package/public/dayjslocales/br.js +1 -0
  28. package/public/dayjslocales/bs.js +1 -0
  29. package/public/dayjslocales/ca.js +1 -0
  30. package/public/dayjslocales/cs.js +1 -0
  31. package/public/dayjslocales/cv.js +1 -0
  32. package/public/dayjslocales/cy.js +1 -0
  33. package/public/dayjslocales/da.js +1 -0
  34. package/public/dayjslocales/de-at.js +1 -0
  35. package/public/dayjslocales/de-ch.js +1 -0
  36. package/public/dayjslocales/de.js +1 -0
  37. package/public/dayjslocales/dv.js +1 -0
  38. package/public/dayjslocales/el.js +1 -0
  39. package/public/dayjslocales/en-au.js +1 -0
  40. package/public/dayjslocales/en-ca.js +1 -0
  41. package/public/dayjslocales/en-gb.js +1 -0
  42. package/public/dayjslocales/en-ie.js +1 -0
  43. package/public/dayjslocales/en-il.js +1 -0
  44. package/public/dayjslocales/en-in.js +1 -0
  45. package/public/dayjslocales/en-nz.js +1 -0
  46. package/public/dayjslocales/en-sg.js +1 -0
  47. package/public/dayjslocales/en-tt.js +1 -0
  48. package/public/dayjslocales/en.js +1 -0
  49. package/public/dayjslocales/eo.js +1 -0
  50. package/public/dayjslocales/es-do.js +1 -0
  51. package/public/dayjslocales/es-mx.js +1 -0
  52. package/public/dayjslocales/es-pr.js +1 -0
  53. package/public/dayjslocales/es-us.js +1 -0
  54. package/public/dayjslocales/es.js +1 -0
  55. package/public/dayjslocales/et.js +1 -0
  56. package/public/dayjslocales/eu.js +1 -0
  57. package/public/dayjslocales/fa.js +1 -0
  58. package/public/dayjslocales/fi.js +1 -0
  59. package/public/dayjslocales/fo.js +1 -0
  60. package/public/dayjslocales/fr-ca.js +1 -0
  61. package/public/dayjslocales/fr-ch.js +1 -0
  62. package/public/dayjslocales/fr.js +1 -0
  63. package/public/dayjslocales/fy.js +1 -0
  64. package/public/dayjslocales/ga.js +1 -0
  65. package/public/dayjslocales/gd.js +1 -0
  66. package/public/dayjslocales/gl.js +1 -0
  67. package/public/dayjslocales/gom-latn.js +1 -0
  68. package/public/dayjslocales/gu.js +1 -0
  69. package/public/dayjslocales/he.js +1 -0
  70. package/public/dayjslocales/hi.js +1 -0
  71. package/public/dayjslocales/hr.js +1 -0
  72. package/public/dayjslocales/ht.js +1 -0
  73. package/public/dayjslocales/hu.js +1 -0
  74. package/public/dayjslocales/hy-am.js +1 -0
  75. package/public/dayjslocales/id.js +1 -0
  76. package/public/dayjslocales/is.js +1 -0
  77. package/public/dayjslocales/it-ch.js +1 -0
  78. package/public/dayjslocales/it.js +1 -0
  79. package/public/dayjslocales/ja.js +1 -0
  80. package/public/dayjslocales/jv.js +1 -0
  81. package/public/dayjslocales/ka.js +1 -0
  82. package/public/dayjslocales/kk.js +1 -0
  83. package/public/dayjslocales/km.js +1 -0
  84. package/public/dayjslocales/kn.js +1 -0
  85. package/public/dayjslocales/ko.js +1 -0
  86. package/public/dayjslocales/ku.js +1 -0
  87. package/public/dayjslocales/ky.js +1 -0
  88. package/public/dayjslocales/lb.js +1 -0
  89. package/public/dayjslocales/lo.js +1 -0
  90. package/public/dayjslocales/lt.js +1 -0
  91. package/public/dayjslocales/lv.js +1 -0
  92. package/public/dayjslocales/me.js +1 -0
  93. package/public/dayjslocales/mi.js +1 -0
  94. package/public/dayjslocales/mk.js +1 -0
  95. package/public/dayjslocales/ml.js +1 -0
  96. package/public/dayjslocales/mn.js +1 -0
  97. package/public/dayjslocales/mr.js +1 -0
  98. package/public/dayjslocales/ms-my.js +1 -0
  99. package/public/dayjslocales/ms.js +1 -0
  100. package/public/dayjslocales/mt.js +1 -0
  101. package/public/dayjslocales/my.js +1 -0
  102. package/public/dayjslocales/nb.js +1 -0
  103. package/public/dayjslocales/ne.js +1 -0
  104. package/public/dayjslocales/nl-be.js +1 -0
  105. package/public/dayjslocales/nl.js +1 -0
  106. package/public/dayjslocales/nn.js +1 -0
  107. package/public/dayjslocales/oc-lnc.js +1 -0
  108. package/public/dayjslocales/pa-in.js +1 -0
  109. package/public/dayjslocales/pl.js +1 -0
  110. package/public/dayjslocales/pt-br.js +1 -0
  111. package/public/dayjslocales/pt.js +1 -0
  112. package/public/dayjslocales/rn.js +1 -0
  113. package/public/dayjslocales/ro.js +1 -0
  114. package/public/dayjslocales/ru.js +1 -0
  115. package/public/dayjslocales/rw.js +1 -0
  116. package/public/dayjslocales/sd.js +1 -0
  117. package/public/dayjslocales/se.js +1 -0
  118. package/public/dayjslocales/si.js +1 -0
  119. package/public/dayjslocales/sk.js +1 -0
  120. package/public/dayjslocales/sl.js +1 -0
  121. package/public/dayjslocales/sq.js +1 -0
  122. package/public/dayjslocales/sr-cyrl.js +1 -0
  123. package/public/dayjslocales/sr.js +1 -0
  124. package/public/dayjslocales/ss.js +1 -0
  125. package/public/dayjslocales/sv-fi.js +1 -0
  126. package/public/dayjslocales/sv.js +1 -0
  127. package/public/dayjslocales/sw.js +1 -0
  128. package/public/dayjslocales/ta.js +1 -0
  129. package/public/dayjslocales/te.js +1 -0
  130. package/public/dayjslocales/tet.js +1 -0
  131. package/public/dayjslocales/tg.js +1 -0
  132. package/public/dayjslocales/th.js +1 -0
  133. package/public/dayjslocales/tk.js +1 -0
  134. package/public/dayjslocales/tl-ph.js +1 -0
  135. package/public/dayjslocales/tlh.js +1 -0
  136. package/public/dayjslocales/tr.js +1 -0
  137. package/public/dayjslocales/tzl.js +1 -0
  138. package/public/dayjslocales/tzm-latn.js +1 -0
  139. package/public/dayjslocales/tzm.js +1 -0
  140. package/public/dayjslocales/ug-cn.js +1 -0
  141. package/public/dayjslocales/uk.js +1 -0
  142. package/public/dayjslocales/ur.js +1 -0
  143. package/public/dayjslocales/uz-latn.js +1 -0
  144. package/public/dayjslocales/uz.js +1 -0
  145. package/public/dayjslocales/vi.js +1 -0
  146. package/public/dayjslocales/x-pseudo.js +1 -0
  147. package/public/dayjslocales/yo.js +1 -0
  148. package/public/dayjslocales/zh-cn.js +1 -0
  149. package/public/dayjslocales/zh-hk.js +1 -0
  150. package/public/dayjslocales/zh-tw.js +1 -0
  151. package/public/dayjslocales/zh.js +1 -0
  152. package/public/gridedit.js +2 -2
  153. package/public/log_viewer_utils.js +156 -0
  154. package/public/saltcorn-builder.css +43 -2
  155. package/public/saltcorn-common.js +39 -29
  156. package/public/saltcorn.js +27 -17
  157. package/public/tabulator_bootstrap5.min.css +1 -0
  158. package/restart_watcher.js +1 -0
  159. package/routes/actions.js +158 -16
  160. package/routes/admin.js +83 -9
  161. package/routes/common_lists.js +40 -17
  162. package/routes/fields.js +18 -3
  163. package/routes/homepage.js +2 -1
  164. package/routes/page.js +30 -13
  165. package/routes/page_groupedit.js +104 -83
  166. package/routes/pageedit.js +4 -3
  167. package/routes/tables.js +34 -0
  168. package/routes/tag_entries.js +6 -1
  169. package/routes/tags.js +4 -0
  170. package/routes/utils.js +23 -2
  171. package/routes/view.js +5 -1
  172. package/routes/viewedit.js +9 -0
  173. package/serve.js +177 -10
  174. package/tests/admin.test.js +17 -11
  175. package/tests/api.test.js +27 -0
  176. package/tests/fields.test.js +132 -5
  177. package/tests/help.test.js +37 -0
  178. package/tests/page_group.test.js +1 -0
  179. package/tests/plugins.test.js +0 -12
  180. package/tests/table.test.js +1 -5
  181. package/tests/view.test.js +12 -0
  182. package/tests/viewedit.test.js +52 -8
  183. package/wrapper.js +9 -1
package/serve.js CHANGED
@@ -112,6 +112,10 @@ const initMaster = async ({ disableMigrate }, useClusterAdaptor = true) => {
112
112
  const tenants = await getAllTenants();
113
113
  await init_multi_tenant(loadAllPlugins, disableMigrate, tenants);
114
114
  }
115
+ eachTenant(async () => {
116
+ const state = getState();
117
+ if (state) await state.setConfig("joined_log_socket_ids", []);
118
+ });
115
119
  if (useClusterAdaptor) setupPrimary();
116
120
  };
117
121
 
@@ -283,7 +287,7 @@ module.exports =
283
287
  })
284
288
  .ready((glx) => {
285
289
  const httpsServer = glx.httpsServer();
286
- setupSocket(httpsServer);
290
+ setupSocket(appargs?.subdomainOffset, httpsServer);
287
291
  httpsServer.setTimeout(timeout * 1000);
288
292
  process.on("message", workerDispatchMsg);
289
293
  glx.serveApp(app);
@@ -350,7 +354,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
350
354
  // todo timeout to config
351
355
  httpServer.setTimeout(timeout * 1000);
352
356
  httpsServer.setTimeout(timeout * 1000);
353
- setupSocket(httpServer, httpsServer);
357
+ setupSocket(appargs?.subdomainOffset, httpServer, httpsServer);
354
358
  httpServer.listen(port, () => {
355
359
  console.log("HTTP Server running on port 80");
356
360
  });
@@ -363,7 +367,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
363
367
  // server with http only
364
368
  const http = require("http");
365
369
  const httpServer = http.createServer(app);
366
- setupSocket(httpServer);
370
+ setupSocket(appargs?.subdomainOffset, httpServer);
367
371
 
368
372
  // todo timeout to config
369
373
  // todo refer in doc to httpserver doc
@@ -380,7 +384,7 @@ const nonGreenlockWorkerSetup = async (appargs, port) => {
380
384
  *
381
385
  * @param {...*} servers
382
386
  */
383
- const setupSocket = (...servers) => {
387
+ const setupSocket = (subdomainOffset, ...servers) => {
384
388
  // https://socket.io/docs/v4/middlewares/
385
389
  const wrap = (middleware) => (socket, next) =>
386
390
  middleware(socket.request, {}, next);
@@ -390,15 +394,29 @@ const setupSocket = (...servers) => {
390
394
  io.attach(server);
391
395
  }
392
396
 
393
- //io.use(wrap(setTenant));
394
- io.use(wrap(getSessionStore()));
395
- io.use(wrap(passport.initialize()));
396
- io.use(wrap(passport.authenticate(["jwt", "session"])));
397
+ const passportInit = passport.initialize();
398
+ const sessionStore = getSessionStore();
399
+ const setupNamespace = (namespace) => {
400
+ //io.of(namespace).use(wrap(setTenant));
401
+ io.of(namespace).use(wrap(sessionStore));
402
+ io.of(namespace).use(wrap(passportInit));
403
+ io.of(namespace).use(wrap(passport.authenticate(["jwt", "session"])));
404
+ };
405
+ setupNamespace("/");
406
+ setupNamespace("/datastream");
397
407
  if (process.send && !cluster.isMaster) io.adapter(createAdapter());
398
408
  getState().setRoomEmitter((tenant, viewname, room_id, msg) => {
399
- io.to(`${tenant}_${viewname}_${room_id}`).emit("message", msg);
409
+ io.of("/").to(`${tenant}_${viewname}_${room_id}`).emit("message", msg);
400
410
  });
401
- io.on("connection", (socket) => {
411
+
412
+ getState().setLogEmitter((tenant, level, msg) => {
413
+ const time = new Date().valueOf();
414
+ io.of("/")
415
+ .to(`_logs_${tenant}_`)
416
+ .emit("log_msg", { text: msg, time, level });
417
+ });
418
+
419
+ io.of("/").on("connection", (socket) => {
402
420
  socket.on("join_room", ([viewname, room_id]) => {
403
421
  const ten = get_tenant_from_req(socket.request) || "public";
404
422
  const f = () => {
@@ -418,5 +436,154 @@ const setupSocket = (...servers) => {
418
436
  if (ten && ten !== "public") db.runWithTenant(ten, f);
419
437
  else f();
420
438
  });
439
+
440
+ socket.on("join_log_room", async (callback) => {
441
+ const tenant =
442
+ get_tenant_from_req(socket.request, subdomainOffset) || "public";
443
+ const f = async () => {
444
+ try {
445
+ const user = socket.request.user;
446
+ if (!user || user.role_id !== 1) throw new Error("Not authorized");
447
+ else {
448
+ socket.join(`_logs_${tenant}_`);
449
+ const socketIds = await getState().getConfig(
450
+ "joined_log_socket_ids"
451
+ );
452
+ socketIds.push(socket.id);
453
+ await getState().setConfig("joined_log_socket_ids", [...socketIds]);
454
+ callback({ status: "ok" });
455
+ }
456
+ } catch (err) {
457
+ getState().log(1, `Socket join_logs stream: ${err.stack}`);
458
+ callback({ status: "error", msg: err.message || "unknown error" });
459
+ }
460
+ };
461
+ if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
462
+ else await f();
463
+ });
464
+
465
+ socket.on("disconnect", async () => {
466
+ const tenant =
467
+ get_tenant_from_req(socket.request, subdomainOffset) || "public";
468
+ const f = async () => {
469
+ const socketIds = await getState().getConfig("joined_log_socket_ids");
470
+ const newSocketIds = socketIds.filter((id) => id !== socket.id);
471
+ await getState().setConfig("joined_log_socket_ids", newSocketIds);
472
+ };
473
+ if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
474
+ else f();
475
+ });
476
+ });
477
+
478
+ io.of("/datastream").on("connection", (socket) => {
479
+ let dataStream = null;
480
+ let dataTarget = null;
481
+ socket.on(
482
+ "open_data_stream",
483
+ async ([viewName, id, fieldName, fieldView, targetOpts], callback) => {
484
+ const tenant =
485
+ get_tenant_from_req(socket.request, subdomainOffset) || "public";
486
+ const f = async () => {
487
+ try {
488
+ const user = socket.request.user;
489
+ const view = View.findOne({ name: viewName });
490
+ if (view.viewtemplateObj.authorizeDataStream) {
491
+ const authorized = await view.viewtemplateObj.authorizeDataStream(
492
+ view,
493
+ id,
494
+ fieldName,
495
+ user,
496
+ targetOpts
497
+ );
498
+ if (!authorized) throw new Error("Not authorized");
499
+ }
500
+ const { stream, target } = await view.openDataStream(
501
+ id,
502
+ fieldName,
503
+ fieldView,
504
+ user,
505
+ targetOpts
506
+ );
507
+ dataStream = stream;
508
+ dataTarget = target;
509
+ getState().log(
510
+ 5,
511
+ `opened data stram to: ${JSON.stringify(dataTarget)}`
512
+ );
513
+ callback({ status: "ok", target });
514
+ } catch (err) {
515
+ getState().log(
516
+ 1,
517
+ `Socket open_data_stream: ${err.message || "unknown error"}`
518
+ );
519
+ callback({ status: "error", msg: err.message || "unknown error" });
520
+ }
521
+ };
522
+ if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
523
+ else f();
524
+ }
525
+ );
526
+ socket.on("write_to_stream", async (data, callback) => {
527
+ if (!dataStream) {
528
+ getState().log(1, "Socket write_to_stream: No stream available");
529
+ callback({ status: "error", msg: "No stream available" });
530
+ } else
531
+ dataStream.write(data, (err) => {
532
+ if (err) {
533
+ getState().log(1, "Socket write_to_stream: No stream available");
534
+ callback({ status: "error", msg: err.message || "unknown error" });
535
+ } else callback({ status: "ok" });
536
+ });
537
+ });
538
+
539
+ socket.on("close_data_stream", async (callback) => {
540
+ if (!dataStream) {
541
+ getState().log(1, "Socket close_data_stream: No stream available");
542
+ callback({ status: "error", msg: "No stream available" });
543
+ } else {
544
+ dataStream.close((err) => {
545
+ if (err) {
546
+ getState().log(
547
+ 1,
548
+ `Socket close_data_stream: ${err.message || "unknown error"}`
549
+ );
550
+ callback({ status: "error", msg: err.message || "unknown error" });
551
+ } else {
552
+ getState().log(
553
+ 5,
554
+ `closed data stram of: ${JSON.stringify(dataTarget)}`
555
+ );
556
+ callback({ status: "ok" });
557
+ dataStream = null;
558
+ }
559
+ });
560
+ }
561
+ });
562
+
563
+ socket.on("disconnect", async () => {
564
+ const tenant =
565
+ get_tenant_from_req(socket.request, subdomainOffset) || "public";
566
+ const f = async () => {
567
+ if (dataStream)
568
+ dataStream.close((err) => {
569
+ if (err) {
570
+ getState().log(
571
+ 1,
572
+ `Socket disconnect close_data_stream: ${
573
+ err.message || "unknown error"
574
+ }`
575
+ );
576
+ } else {
577
+ getState().log(
578
+ 5,
579
+ `closed data stram of: ${JSON.stringify(dataTarget)}`
580
+ );
581
+ dataStream = null;
582
+ }
583
+ });
584
+ };
585
+ if (tenant && tenant !== "public") db.runWithTenant(tenant, f);
586
+ else f();
587
+ });
421
588
  });
422
589
  };
@@ -382,13 +382,13 @@ describe("actions", () => {
382
382
  .send("table_id=2")
383
383
  .send("name=mynewaction")
384
384
  .send("when_trigger=Insert")
385
- .expect(toRedirect("/actions/configure/1"));
385
+ .expect(toRedirect("/actions/configure/3"));
386
386
  });
387
387
  it("show edit", async () => {
388
388
  const app = await getApp({ disableCsrf: true });
389
389
  const loginCookie = await getAdminLoginCookie();
390
390
  await request(app)
391
- .get("/actions/edit/1")
391
+ .get("/actions/edit/3")
392
392
  .set("Cookie", loginCookie)
393
393
  .expect(toInclude("Edit trigger"))
394
394
  .expect(toInclude("run_js_code"));
@@ -397,7 +397,7 @@ describe("actions", () => {
397
397
  const app = await getApp({ disableCsrf: true });
398
398
  const loginCookie = await getAdminLoginCookie();
399
399
  await request(app)
400
- .get("/actions/configure/1")
400
+ .get("/actions/configure/3")
401
401
  .set("Cookie", loginCookie)
402
402
  .expect(toInclude("Configure trigger"))
403
403
  .expect(toInclude("Code"));
@@ -406,7 +406,7 @@ describe("actions", () => {
406
406
  const app = await getApp({ disableCsrf: true });
407
407
  const loginCookie = await getAdminLoginCookie();
408
408
  await request(app)
409
- .post("/actions/configure/1")
409
+ .post("/actions/configure/3")
410
410
  .set("Cookie", loginCookie)
411
411
  .send("code=console.log(12345678)")
412
412
  .expect(toRedirect("/actions/"));
@@ -415,7 +415,7 @@ describe("actions", () => {
415
415
  const app = await getApp({ disableCsrf: true });
416
416
  const loginCookie = await getAdminLoginCookie();
417
417
  await request(app)
418
- .get("/actions/testrun/1")
418
+ .get("/actions/testrun/3")
419
419
  .set("Cookie", loginCookie)
420
420
  .expect(toInclude("12345678"));
421
421
  });
@@ -423,7 +423,7 @@ describe("actions", () => {
423
423
  const app = await getApp({ disableCsrf: true });
424
424
  const loginCookie = await getAdminLoginCookie();
425
425
  await request(app)
426
- .post("/actions/configure/1")
426
+ .post("/actions/configure/3")
427
427
  .set("Cookie", loginCookie)
428
428
  .send("code=1")
429
429
  .expect(toRedirect("/actions/"));
@@ -432,7 +432,7 @@ describe("actions", () => {
432
432
  const app = await getApp({ disableCsrf: true });
433
433
  const loginCookie = await getAdminLoginCookie();
434
434
  await request(app)
435
- .get("/actions/testrun/1")
435
+ .get("/actions/testrun/2")
436
436
  .set("Cookie", loginCookie)
437
437
  .expect(toRedirect("/actions/"));
438
438
  });
@@ -446,9 +446,9 @@ describe("actions", () => {
446
446
  .send("action=run_js_code")
447
447
  .send("when_trigger=API+call")
448
448
  .send("min_role=1")
449
- .expect(toRedirect("/actions/configure/2"));
449
+ .expect(toRedirect("/actions/configure/4"));
450
450
  await request(app)
451
- .post("/actions/configure/2")
451
+ .post("/actions/configure/4")
452
452
  .set("Cookie", loginCookie)
453
453
  .send("code=return 27")
454
454
  .expect(toRedirect("/actions/"));
@@ -463,7 +463,7 @@ describe("actions", () => {
463
463
  const app = await getApp({ disableCsrf: true });
464
464
  const loginCookie = await getAdminLoginCookie();
465
465
  await request(app)
466
- .post("/actions/delete/1")
466
+ .post("/actions/delete/3")
467
467
  .set("Cookie", loginCookie)
468
468
  .expect(toRedirect("/actions/"));
469
469
  });
@@ -555,7 +555,7 @@ describe("diagram", () => {
555
555
  });
556
556
 
557
557
  /**
558
- * Diagram tests
558
+ * Tags tests
559
559
  */
560
560
  describe("tags", () => {
561
561
  itShouldRedirectUnauthToLogin("/tag");
@@ -604,6 +604,12 @@ describe("tags", () => {
604
604
  .expect(toRedirect("/tag"));
605
605
  });
606
606
  });
607
+
608
+ describe("server logs viewer", () => {
609
+ itShouldRedirectUnauthToLogin("/admin/dev/logs_viewer");
610
+ itShouldIncludeTextForAdmin("/admin/dev/logs_viewer", "Server logs");
611
+ });
612
+
607
613
  /**
608
614
  * Clear all tests
609
615
  */
package/tests/api.test.js CHANGED
@@ -161,6 +161,33 @@ describe("API read", () => {
161
161
  succeedJsonWith((rows) => rows.length == 2 && +rows[0]._versions === 0)
162
162
  );
163
163
  });
164
+ it("should get distinct authors for public", async () => {
165
+ const app = await getApp({ disableCsrf: true });
166
+ await request(app)
167
+ .get("/api/books/distinct/author")
168
+ .expect(
169
+ succeedJsonWith((vals) => {
170
+ return vals.length == 2 && vals.includes("Herman Melville");
171
+ })
172
+ );
173
+ });
174
+ it("should not allow public access to distinct patients", async () => {
175
+ const app = await getApp({ disableCsrf: true });
176
+ await request(app).get("/api/patients/distinct/name").expect(notAuthorized);
177
+ });
178
+ it("should allow staff access to distinct patients", async () => {
179
+ const loginCookie = await getStaffLoginCookie();
180
+
181
+ const app = await getApp({ disableCsrf: true });
182
+ await request(app)
183
+ .get("/api/patients/distinct/name")
184
+ .set("Cookie", loginCookie)
185
+ .expect(
186
+ succeedJsonWith(
187
+ (rows) => rows.length == 2 && rows.includes("Kirk Douglas")
188
+ )
189
+ );
190
+ });
164
191
  });
165
192
  describe("API post", () => {
166
193
  it("should post books", async () => {
@@ -8,6 +8,7 @@ const {
8
8
  getAdminLoginCookie,
9
9
  itShouldRedirectUnauthToLogin,
10
10
  toInclude,
11
+ toBeTrue,
11
12
  toNotInclude,
12
13
  toRedirect,
13
14
  resetToFixtures,
@@ -364,9 +365,12 @@ describe("Field Endpoints", () => {
364
365
  await request(app)
365
366
  .post("/field/show-calculated/books/pagesp1/show")
366
367
  .set("Cookie", loginCookie)
367
- .expect((r) => +r.body > 2);
368
+ .send({
369
+ id: 1,
370
+ })
371
+ .expect(toBeTrue((r) => +r.text > 500 && +r.text < 1500));
368
372
  });
369
- it("should show calculated with single joinfield", async () => {
373
+ it("should show calculated field with single joinfield", async () => {
370
374
  const loginCookie = await getAdminLoginCookie();
371
375
  const table = Table.findOne({ name: "patients" });
372
376
  await Field.create({
@@ -382,9 +386,12 @@ describe("Field Endpoints", () => {
382
386
  await request(app)
383
387
  .post("/field/show-calculated/patients/pagesp1/show")
384
388
  .set("Cookie", loginCookie)
385
- .expect((r) => +r.body > 2);
389
+ .send({
390
+ id: 1,
391
+ })
392
+ .expect(toBeTrue((r) => +r.text > 2));
386
393
  });
387
- it("should show calculated with double joinfield", async () => {
394
+ it("should show calculated field with double joinfield", async () => {
388
395
  const loginCookie = await getAdminLoginCookie();
389
396
  const table = Table.findOne({ name: "readings" });
390
397
  await Field.create({
@@ -400,7 +407,127 @@ describe("Field Endpoints", () => {
400
407
  await request(app)
401
408
  .post("/field/show-calculated/readings/pagesp1/show")
402
409
  .set("Cookie", loginCookie)
403
- .expect((r) => +r.body > 2);
410
+ .send({
411
+ id: 1,
412
+ })
413
+ .expect(toBeTrue((r) => +r.text > 2));
414
+ });
415
+ it("should show-calculated on join field value", async () => {
416
+ const loginCookie = await getAdminLoginCookie();
417
+
418
+ const app = await getApp({ disableCsrf: true });
419
+
420
+ await request(app)
421
+ .post("/field/show-calculated/books/publisher.name/as_text")
422
+ .set("Cookie", loginCookie)
423
+ .send({
424
+ publisher: 1,
425
+ })
426
+ .expect(toBeTrue((r) => r.text === "AK Press"));
427
+ });
428
+ it("should show-calculated on join field value", async () => {
429
+ const loginCookie = await getAdminLoginCookie();
430
+
431
+ const app = await getApp({ disableCsrf: true });
432
+
433
+ await request(app)
434
+ .post("/field/show-calculated/books/publisher.name/code")
435
+ .set("Cookie", loginCookie)
436
+ .send({
437
+ publisher: 1,
438
+ })
439
+ .expect(toBeTrue((r) => r.text === "<pre><code>AK Press</code></pre>"));
440
+ });
441
+ it("should show-calculated on join field value", async () => {
442
+ const loginCookie = await getAdminLoginCookie();
443
+
444
+ const app = await getApp({ disableCsrf: true });
445
+
446
+ await request(app)
447
+ .post("/field/show-calculated/books/publisher.name/code")
448
+ .set("Cookie", loginCookie)
449
+ .send({
450
+ publisher: 1,
451
+ })
452
+ .expect(toBeTrue((r) => r.text === "<pre><code>AK Press</code></pre>"));
453
+ });
454
+ it("should show-calculated on join field value", async () => {
455
+ const loginCookie = await getAdminLoginCookie();
456
+
457
+ const app = await getApp({ disableCsrf: true });
458
+
459
+ await request(app)
460
+ .post(
461
+ "/field/show-calculated/books/publisher.name/show_with_html?code=pub%3A%7B%7Bit.toLowerCase()%7D%7D"
462
+ )
463
+ .set("Cookie", loginCookie)
464
+ .send({
465
+ publisher: 1,
466
+ })
467
+ .expect(toBeTrue((r) => r.text === "pub:ak press"));
468
+ });
469
+ it("should show-calculated on double-join field value", async () => {
470
+ const loginCookie = await getAdminLoginCookie();
471
+
472
+ const app = await getApp({ disableCsrf: true });
473
+
474
+ await request(app)
475
+ .post("/field/show-calculated/patients/favbook.publisher.name/as_text")
476
+ .set("Cookie", loginCookie)
477
+ .send({
478
+ favbook: 2,
479
+ })
480
+ .expect(toBeTrue((r) => r.text === "AK Press"));
481
+ });
482
+ it("should not show unauth show-calculated on double-join field value", async () => {
483
+ const app = await getApp({ disableCsrf: true });
484
+
485
+ await request(app)
486
+ .post("/field/show-calculated/patients/favbook.publisher.name/as_text")
487
+
488
+ .send({
489
+ favbook: 2,
490
+ })
491
+ .expect(401);
492
+ });
493
+ it("should preview field", async () => {
494
+ const loginCookie = await getAdminLoginCookie();
495
+
496
+ const app = await getApp({ disableCsrf: true });
497
+
498
+ await request(app)
499
+ .post("/field/preview/books/author/code")
500
+ .set("Cookie", loginCookie)
501
+ .send({})
502
+ .expect(
503
+ toBeTrue((r) => r.text === "<pre><code>Herman Melville</code></pre>")
504
+ );
505
+ });
506
+ it("should preview joinfield", async () => {
507
+ const loginCookie = await getAdminLoginCookie();
508
+
509
+ const app = await getApp({ disableCsrf: true });
510
+
511
+ await request(app)
512
+ .post("/field/preview/books/publisher.name/code")
513
+ .set("Cookie", loginCookie)
514
+ .send({})
515
+ .expect(toBeTrue((r) => r.text === "<pre><code>AK Press</code></pre>"));
516
+ });
517
+ it("should preview joinfield with cfg", async () => {
518
+ const loginCookie = await getAdminLoginCookie();
519
+
520
+ const app = await getApp({ disableCsrf: true });
521
+
522
+ await request(app)
523
+ .post("/field/preview/books/publisher.name/show_with_html")
524
+ .set("Cookie", loginCookie)
525
+ .send({
526
+ configuration: {
527
+ code: "pub:{{it.toLowerCase()}}",
528
+ },
529
+ })
530
+ .expect(toBeTrue((r) => r.text === "pub:ak press"));
404
531
  });
405
532
  });
406
533
 
@@ -0,0 +1,37 @@
1
+ const request = require("supertest");
2
+ const getApp = require("../app");
3
+ const {
4
+ toRedirect,
5
+ getAdminLoginCookie,
6
+ getStaffLoginCookie,
7
+ itShouldRedirectUnauthToLogin,
8
+ toInclude,
9
+ toNotInclude,
10
+ resetToFixtures,
11
+ succeedJsonWith,
12
+ } = require("../auth/testhelp");
13
+ const db = require("@saltcorn/data/db");
14
+ const View = require("@saltcorn/data/models/view");
15
+ const Table = require("@saltcorn/data/models/table");
16
+
17
+ beforeAll(async () => {
18
+ await resetToFixtures();
19
+ });
20
+ afterAll(db.close);
21
+
22
+ jest.setTimeout(30000);
23
+
24
+ describe("Help topics", () => {
25
+ itShouldRedirectUnauthToLogin("/admin/help/View%20patterns");
26
+
27
+ it("should show view patterns help", async () => {
28
+ const loginCookie = await getAdminLoginCookie();
29
+
30
+ const app = await getApp({ disableCsrf: true });
31
+ await request(app)
32
+ .get("/admin/help/View patterns")
33
+ .set("Cookie", loginCookie)
34
+ .expect(toInclude("<title>Help: View patterns</title>"))
35
+ .expect(toInclude("The view pattern is a fundamental concept"));
36
+ });
37
+ });
@@ -73,6 +73,7 @@ describe("edit Page groups", () => {
73
73
  name: nameAfterUpdate,
74
74
  description: null,
75
75
  min_role: 100,
76
+ random_allocation: false,
76
77
  });
77
78
  });
78
79
 
@@ -280,19 +280,7 @@ describe("Pack clash detection", () => {
280
280
  .set("Cookie", loginCookie)
281
281
  .expect(toRedirect("/"));
282
282
  });
283
- it("should install issues", async () => {
284
- const loginCookie = await getAdminLoginCookie();
285
283
 
286
- const app = await getApp({ disableCsrf: true });
287
- await request(app)
288
- .post("/packs/install-named/Blog")
289
- .set("Cookie", loginCookie)
290
- .expect(toRedirect("/plugins"));
291
- await request(app)
292
- .get("/plugins")
293
- .set("Cookie", loginCookie)
294
- .expect(toInclude("Tables already exist: comments"));
295
- });
296
284
  it("should reset again", async () => {
297
285
  await resetToFixtures();
298
286
  });
@@ -254,11 +254,7 @@ Gordon Kane, 218`;
254
254
  await request(app)
255
255
  .get("/table/")
256
256
  .set("Cookie", loginCookie)
257
- .expect(
258
- toInclude(
259
- "cannot drop table books because other objects depend on it"
260
- )
261
- );
257
+ .expect(toInclude("has views. Delete these first"));
262
258
  });
263
259
  });
264
260
  describe("deletion to table with row ownership", () => {
@@ -37,6 +37,18 @@ describe("view list endpoint", () => {
37
37
  describe("nonexisting view", () => {
38
38
  itShouldRedirectUnauthToLogin("/view/patlist", "/");
39
39
  });
40
+ describe("preview view", () => {
41
+ it("should show previewview to auth", async () => {
42
+ const loginCookie = await getAdminLoginCookie();
43
+
44
+ const app = await getApp({ disableCsrf: true });
45
+ await request(app)
46
+ .post("/view/authorlist/preview")
47
+ .set("Cookie", loginCookie)
48
+ .expect(toInclude("Tolstoy"))
49
+ .expect(toNotInclude(">728<"));
50
+ });
51
+ });
40
52
  describe("view patients list endpoint", () => {
41
53
  itShouldRedirectUnauthToLogin("/view/patientlist");
42
54