store.test.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. import request from 'supertest';
  2. import express from 'express';
  3. import path from 'path';
  4. import { jest } from '@jest/globals';
  5. import { getDirname } from '../utils.js';
  6. import store from '../store';
  7. import { MemoryBackend } from '../store/backends';
  8. import { MemoryFileBackend } from '../fileStore/backends';
  9. /*jest.mock('nanoid', () => {
  10. let count = 0;
  11. return {
  12. customAlphabet: () =>
  13. jest.fn(() => {
  14. return 'nanoid_' + count++;
  15. }),
  16. };
  17. });*/
  18. const __dirname = getDirname(import.meta.url);
  19. let delta = 0;
  20. // Fake date
  21. jest.spyOn(global.Date, 'now').mockImplementation(() => {
  22. delta += 1;
  23. const second = delta < 10 ? '0' + delta : '' + delta;
  24. return new Date(`2020-03-14T11:01:${second}.135Z`).valueOf();
  25. });
  26. describe('Store Test', () => {
  27. let query;
  28. let backend;
  29. beforeAll(() => {
  30. const app = express();
  31. app.use(express.json());
  32. app.use(express.urlencoded({ extended: true }));
  33. backend = MemoryBackend();
  34. app.use(
  35. '/:siteId',
  36. (req, _, next) => {
  37. req.siteId = req.params.siteId;
  38. next();
  39. },
  40. store({
  41. backend,
  42. })
  43. );
  44. query = request(app);
  45. });
  46. it('should get empty box', async () => {
  47. const box = 'myboxid_test1';
  48. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  49. security: 'public',
  50. });
  51. await query
  52. .get(`/fakeSiteId/store/${box}/`)
  53. .expect(200, [])
  54. .expect('Content-Type', /json/);
  55. });
  56. it('should add resource', async () => {
  57. const box = 'myboxid_test2';
  58. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  59. security: 'public',
  60. });
  61. const res = await query
  62. .post(`/fakeSiteId/store/${box}/`)
  63. .send({ test: true, value: 42 })
  64. .expect(200);
  65. expect(res.body).toEqual(
  66. expect.objectContaining({ test: true, value: 42 })
  67. );
  68. expect(typeof res.body._id).toEqual('string');
  69. expect(res.body._createdOn).toBeGreaterThanOrEqual(1584183661135);
  70. const res2 = await query
  71. .get(`/fakeSiteId/store/${box}/`)
  72. .expect(200)
  73. .expect('Content-Type', /json/);
  74. expect(res.body).toEqual(res2.body[0]);
  75. // Test object creation with id
  76. const resWithId = await query
  77. .post(`/fakeSiteId/store/${box}/myid`)
  78. .send({ foo: 'bar', bar: 'foo' })
  79. .expect(200);
  80. expect(resWithId.body._id).toBe('myid');
  81. });
  82. it('should get a resource', async () => {
  83. const box = 'myboxid_test3';
  84. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  85. security: 'public',
  86. });
  87. const res = await query
  88. .post(`/fakeSiteId/store/${box}/`)
  89. .send({ test: true, value: 42 })
  90. .expect(200);
  91. let resourceId = res.body._id;
  92. const res2 = await query
  93. .get(`/fakeSiteId/store/${box}/${resourceId}`)
  94. .expect(200)
  95. .expect('Content-Type', /json/);
  96. expect(res.body).toEqual(res2.body);
  97. });
  98. it('should update a resource', async () => {
  99. const box = 'myboxid_test4';
  100. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  101. security: 'public',
  102. });
  103. const res = await query
  104. .post(`/fakeSiteId/store/${box}/`)
  105. .send({ test: true, value: 40 })
  106. .expect(200);
  107. let resourceId = res.body._id;
  108. const res2 = await query
  109. .put(`/fakeSiteId/store/${box}/${resourceId}`)
  110. .send({ value: 42 })
  111. .expect(200);
  112. const res3 = await query
  113. .get(`/fakeSiteId/store/${box}/${resourceId}`)
  114. .expect(200);
  115. expect(res3.body.value).toEqual(42);
  116. const replaceWithId = await query
  117. .post(`/fakeSiteId/store/${box}/${resourceId}`)
  118. .send({ value: 52 })
  119. .expect(200);
  120. expect(replaceWithId.body).not.toEqual(
  121. expect.objectContaining({ test: true })
  122. );
  123. });
  124. it('should delete a resource', async () => {
  125. const box = 'myboxid_test5';
  126. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  127. security: 'public',
  128. });
  129. const res = await query
  130. .post(`/fakeSiteId/store/${box}/`)
  131. .send({ test: true, value: 40 })
  132. .expect(200);
  133. let resourceId = res.body._id;
  134. const res2 = await query
  135. .del(`/fakeSiteId/store/${box}/${resourceId}`)
  136. .expect(200)
  137. .expect('Content-Type', /json/);
  138. const res3 = await query.get(`/fakeSiteId/store/${box}/`).expect(200, []);
  139. });
  140. it('should return 404', async () => {
  141. const box = 'boxId_400';
  142. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  143. security: 'public',
  144. });
  145. await query.get(`/fakeSiteId/store/${box}/noresource`).expect(404);
  146. await query.delete(`/fakeSiteId/store/${box}/noresource`).expect(404);
  147. });
  148. it('should return 403', async () => {
  149. let box = 'boxId_500';
  150. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  151. security: 'readOnly',
  152. });
  153. await query.get(`/fakeSiteId/store/${box}/`).expect(200);
  154. await query
  155. .post(`/fakeSiteId/store/${box}/`)
  156. .send({ test: true, value: 40 })
  157. .expect(403);
  158. box = 'boxId_550';
  159. await backend.createOrUpdateBox(box);
  160. await query.get(`/fakeSiteId/store/${box}/`).expect(403);
  161. });
  162. it('should store and get a file', async () => {
  163. let box = 'boxId_600';
  164. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  165. security: 'public',
  166. });
  167. await query
  168. .post(`/fakeSiteId/store/${box}/1234`)
  169. .send({ test: true, value: 42 })
  170. .expect(200);
  171. const res = await query
  172. .post(`/fakeSiteId/store/${box}/1234/file/`)
  173. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  174. .expect(200);
  175. const fileUrl = res.text;
  176. const fileRes = await query
  177. .get(`/${fileUrl}`)
  178. .buffer(false)
  179. .redirects(1)
  180. .expect(200);
  181. });
  182. });
  183. describe('Store Hook Tests', () => {
  184. let query;
  185. let backend;
  186. let hooks;
  187. beforeAll(() => {
  188. const app = express();
  189. app.use(express.json());
  190. app.use(express.urlencoded({ extended: true }));
  191. backend = MemoryBackend();
  192. hooks = {};
  193. app.use(
  194. '/:siteId',
  195. (req, _, next) => {
  196. req.siteId = req.params.siteId;
  197. next();
  198. },
  199. store({
  200. backend,
  201. hooks,
  202. })
  203. );
  204. query = request(app);
  205. });
  206. it('should call hooks for list', async () => {
  207. let box = 'boxId_1000';
  208. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  209. security: 'public',
  210. });
  211. hooks.before = [
  212. jest.fn((context) => context),
  213. jest.fn((context) => context),
  214. ];
  215. hooks.after = [
  216. jest.fn((context) => context),
  217. jest.fn((context) => context),
  218. ];
  219. await query.get(`/fakeSiteId/store/${box}/`).expect(200);
  220. expect(hooks.before[0]).toHaveBeenCalled();
  221. expect(hooks.before[1]).toHaveBeenCalled();
  222. expect(hooks.after[0]).toHaveBeenCalled();
  223. expect(hooks.after[1]).toHaveBeenCalled();
  224. });
  225. it('should call hooks for post & get & delete', async () => {
  226. let box = 'boxId_1100';
  227. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  228. security: 'public',
  229. });
  230. hooks.before = [
  231. jest.fn((context) => context),
  232. jest.fn((context) => context),
  233. ];
  234. hooks.after = [
  235. jest.fn((context) => context),
  236. jest.fn((context) => context),
  237. ];
  238. await query
  239. .post(`/fakeSiteId/store/${box}/1234`)
  240. .send({ test: true, value: 42 })
  241. .expect(200);
  242. expect(hooks.before[0]).toHaveBeenCalledTimes(1);
  243. expect(hooks.before[1]).toHaveBeenCalledTimes(1);
  244. expect(hooks.after[0]).toHaveBeenCalledTimes(1);
  245. expect(hooks.after[1]).toHaveBeenCalledTimes(1);
  246. jest.clearAllMocks();
  247. await query.get(`/fakeSiteId/store/${box}/1234`).expect(200);
  248. expect(hooks.before[0]).toHaveBeenCalled();
  249. expect(hooks.before[1]).toHaveBeenCalled();
  250. expect(hooks.after[0]).toHaveBeenCalled();
  251. expect(hooks.after[1]).toHaveBeenCalled();
  252. jest.clearAllMocks();
  253. await query.delete(`/fakeSiteId/store/${box}/1234`).expect(200);
  254. expect(hooks.before[0]).toHaveBeenCalled();
  255. expect(hooks.before[1]).toHaveBeenCalled();
  256. expect(hooks.after[0]).toHaveBeenCalled();
  257. expect(hooks.after[1]).toHaveBeenCalled();
  258. });
  259. it('hooks should modify post', async () => {
  260. let box = 'boxId_1200';
  261. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  262. security: 'public',
  263. });
  264. hooks.before = [
  265. jest.fn((context) => ({ ...context, body: { value: 256 } })),
  266. jest.fn((context) => ({
  267. ...context,
  268. body: { ...context.body, foo: 'bar' },
  269. })),
  270. ];
  271. hooks.after = [
  272. jest.fn((context) => context),
  273. jest.fn((context) => context),
  274. ];
  275. await query
  276. .post(`/fakeSiteId/store/${box}/1234`)
  277. .send({ test: true, value: 42 })
  278. .expect(200);
  279. const result = await query.get(`/fakeSiteId/store/${box}/1234`).expect(200);
  280. expect(result.body).toEqual(
  281. expect.objectContaining({ value: 256, foo: 'bar' })
  282. );
  283. });
  284. it('hooks should modify get', async () => {
  285. let box = 'boxId_1300';
  286. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  287. security: 'public',
  288. });
  289. hooks.before = [
  290. jest.fn((context) => context),
  291. jest.fn((context) => context),
  292. ];
  293. hooks.after = [
  294. jest.fn((context) => ({ ...context, response: { value: 256 } })),
  295. jest.fn((context) => ({
  296. ...context,
  297. response: { ...context.response, foo: 'bar' },
  298. })),
  299. ];
  300. await query
  301. .post(`/fakeSiteId/store/${box}/1234`)
  302. .send({ test: true, value: 42 })
  303. .expect(200);
  304. const result = await query.get(`/fakeSiteId/store/${box}/1234`).expect(200);
  305. expect(result.body).toEqual(
  306. expect.objectContaining({ value: 256, foo: 'bar' })
  307. );
  308. });
  309. it('hooks should force access to private store', async () => {
  310. let box = 'boxId_1400';
  311. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  312. security: 'private',
  313. });
  314. hooks.before = [
  315. jest.fn((context) => ({ ...context, allow: true })),
  316. jest.fn((context) => context),
  317. ];
  318. await query
  319. .post(`/fakeSiteId/store/${box}/1234`)
  320. .send({ test: true, value: 42 })
  321. .expect(200);
  322. await query.get(`/fakeSiteId/store/${box}`).expect(200);
  323. await query.get(`/fakeSiteId/store/${box}/1234`).expect(200);
  324. await query.delete(`/fakeSiteId/store/${box}/1234`).expect(200);
  325. });
  326. it('should store even if private box', async () => {
  327. let box = 'boxId_1500';
  328. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  329. security: 'private',
  330. });
  331. await query
  332. .post(`/fakeSiteId/store/${box}/1234/file/`)
  333. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  334. .expect(403);
  335. hooks.beforeFile = [
  336. jest.fn((context) => ({ ...context, allow: true })),
  337. jest.fn((context) => context),
  338. ];
  339. await query
  340. .post(`/fakeSiteId/store/${box}/1234/file/`)
  341. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  342. .expect(200);
  343. });
  344. });
  345. describe('Store File Test', () => {
  346. let query;
  347. let backend;
  348. let fileBackend;
  349. beforeAll(() => {
  350. const app = express();
  351. app.use(express.json());
  352. app.use(express.urlencoded({ extended: true }));
  353. backend = MemoryBackend();
  354. fileBackend = MemoryFileBackend();
  355. app.use(
  356. '/:siteId',
  357. (req, _, next) => {
  358. req.siteId = req.params.siteId;
  359. next();
  360. },
  361. store({
  362. backend,
  363. fileBackend,
  364. })
  365. );
  366. query = request(app);
  367. });
  368. it('should store even if resource missing', async () => {
  369. let box = 'boxId_600';
  370. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  371. security: 'public',
  372. });
  373. const res = await query
  374. .post(`/fakeSiteId/store/${box}/1234/file/`)
  375. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  376. .expect(200);
  377. const fileUrl = res.text;
  378. await query.get(`/${fileUrl}`).buffer(false).redirects(1).expect(200);
  379. });
  380. it('should store and get a file', async () => {
  381. let box = 'boxId_600';
  382. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  383. security: 'public',
  384. });
  385. await query
  386. .post(`/fakeSiteId/store/${box}/1234`)
  387. .send({ test: true, value: 42 })
  388. .expect(200);
  389. const res = await query
  390. .post(`/fakeSiteId/store/${box}/1234/file/`)
  391. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  392. .expect(200);
  393. const fileUrl = res.text;
  394. await query.get(`/${fileUrl}`).buffer(false).redirects(1).expect(200);
  395. });
  396. it('should not allow to store a file on readOnly store', async () => {
  397. let box = 'boxId_600';
  398. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  399. security: 'readOnly',
  400. });
  401. const fakeFile = { filename: 'test.txt', mimetype: 'text/plain' };
  402. await query
  403. .post(`/fakeSiteId/store/${box}/1234/file/`)
  404. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  405. .expect(403);
  406. const fileName = await fileBackend.store(
  407. 'fakeSiteId',
  408. box,
  409. '1234',
  410. fakeFile
  411. );
  412. await query
  413. .get(`/fakeSiteId/store/${box}/1234/file/${fileName}`)
  414. .buffer(false)
  415. .redirects(1)
  416. .expect(200);
  417. });
  418. it('should not allow to store and get a file on private store', async () => {
  419. let box = 'boxId_600';
  420. await backend.createOrUpdateBox(`_fakeSiteId__${box}`, {
  421. security: 'private',
  422. });
  423. const fakeFile = { filename: 'test.txt', mimetype: 'text/plain' };
  424. await query
  425. .post(`/fakeSiteId/store/${box}/1234/file/`)
  426. .attach('file', path.resolve(__dirname, 'testFile.txt'))
  427. .expect(403);
  428. const fileName = await fileBackend.store(
  429. 'fakeSiteId',
  430. box,
  431. '1234',
  432. fakeFile
  433. );
  434. await query
  435. .get(`/fakeSiteId/store/${box}/1234/file/${fileName}`)
  436. .buffer(false)
  437. .redirects(1)
  438. .expect(403);
  439. });
  440. });