v5.page.integ.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. import type { IPage } from '@growi/core';
  2. import { addSeconds } from 'date-fns/addSeconds';
  3. import mongoose from 'mongoose';
  4. import { beforeAll, describe, expect, it, vi } from 'vitest';
  5. import { getInstance } from '^/test/setup/crowi';
  6. import { PageActionStage, PageActionType } from '~/interfaces/page-operation';
  7. import type Crowi from '~/server/crowi';
  8. import type { PageDocument, PageModel } from '~/server/models/page';
  9. import type {
  10. IPageOperation,
  11. PageOperationModel,
  12. } from '~/server/models/page-operation';
  13. describe('Test page service methods', () => {
  14. let crowi: Crowi;
  15. let Page: PageModel;
  16. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  17. let User: any;
  18. let PageOperation: PageOperationModel;
  19. let rootPage: PageDocument;
  20. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  21. let dummyUser1: any;
  22. let pageOpId1: mongoose.Types.ObjectId;
  23. let pageOpId2: mongoose.Types.ObjectId;
  24. let pageOpId3: mongoose.Types.ObjectId;
  25. let pageOpId4: mongoose.Types.ObjectId;
  26. beforeAll(async () => {
  27. crowi = await getInstance();
  28. await crowi.configManager.updateConfig('app:isV5Compatible', true);
  29. User = mongoose.model('User');
  30. Page = mongoose.model<IPage, PageModel>('Page');
  31. PageOperation = mongoose.model<IPageOperation, PageOperationModel>(
  32. 'PageOperation',
  33. );
  34. // Ensure root page exists
  35. const existingRootPage = await Page.findOne({ path: '/' });
  36. if (existingRootPage == null) {
  37. const rootPageId = new mongoose.Types.ObjectId();
  38. rootPage = await Page.create({
  39. _id: rootPageId,
  40. path: '/',
  41. grant: Page.GRANT_PUBLIC,
  42. });
  43. } else {
  44. rootPage = existingRootPage;
  45. }
  46. // Create dummy user for tests
  47. const existingUser = await User.findOne({ username: 'v5DummyUser1' });
  48. if (existingUser == null) {
  49. await User.insertMany([
  50. {
  51. name: 'v5DummyUser1',
  52. username: 'v5DummyUser1',
  53. email: 'v5dummyuser1@example.com',
  54. },
  55. ]);
  56. }
  57. dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
  58. /**
  59. * pages
  60. */
  61. const pageId0 = new mongoose.Types.ObjectId();
  62. const pageId1 = new mongoose.Types.ObjectId();
  63. const pageId2 = new mongoose.Types.ObjectId();
  64. const pageId3 = new mongoose.Types.ObjectId();
  65. const pageId4 = new mongoose.Types.ObjectId();
  66. const pageId5 = new mongoose.Types.ObjectId();
  67. const pageId6 = new mongoose.Types.ObjectId();
  68. const pageId7 = new mongoose.Types.ObjectId();
  69. const pageId8 = new mongoose.Types.ObjectId();
  70. const pageId9 = new mongoose.Types.ObjectId();
  71. const pageId10 = new mongoose.Types.ObjectId();
  72. const pageId11 = new mongoose.Types.ObjectId();
  73. const pageId12 = new mongoose.Types.ObjectId();
  74. const pageId13 = new mongoose.Types.ObjectId();
  75. const pageId14 = new mongoose.Types.ObjectId();
  76. const pageId15 = new mongoose.Types.ObjectId();
  77. const pageId16 = new mongoose.Types.ObjectId();
  78. const pageId17 = new mongoose.Types.ObjectId();
  79. const pageId18 = new mongoose.Types.ObjectId();
  80. const pageId19 = new mongoose.Types.ObjectId();
  81. const pageId20 = new mongoose.Types.ObjectId();
  82. await Page.insertMany([
  83. {
  84. _id: pageId0,
  85. path: '/resume_rename_0',
  86. parent: rootPage._id,
  87. grant: Page.GRANT_PUBLIC,
  88. creator: dummyUser1,
  89. lastUpdateUser: dummyUser1._id,
  90. descendantCount: 1,
  91. isEmpty: false,
  92. },
  93. {
  94. _id: pageId1,
  95. path: '/resume_rename_0/resume_rename_1',
  96. parent: pageId0,
  97. grant: Page.GRANT_PUBLIC,
  98. creator: dummyUser1,
  99. lastUpdateUser: dummyUser1._id,
  100. descendantCount: 2,
  101. isEmpty: false,
  102. },
  103. {
  104. _id: pageId2,
  105. path: '/resume_rename_1/resume_rename_2',
  106. parent: pageId1,
  107. grant: Page.GRANT_PUBLIC,
  108. creator: dummyUser1,
  109. lastUpdateUser: dummyUser1._id,
  110. descendantCount: 1,
  111. isEmpty: false,
  112. },
  113. {
  114. _id: pageId3,
  115. path: '/resume_rename_1/resume_rename_2/resume_rename_3',
  116. parent: pageId2,
  117. grant: Page.GRANT_PUBLIC,
  118. creator: dummyUser1,
  119. lastUpdateUser: dummyUser1._id,
  120. descendantCount: 0,
  121. isEmpty: false,
  122. },
  123. {
  124. _id: pageId4,
  125. path: '/resume_rename_4',
  126. parent: rootPage._id,
  127. grant: Page.GRANT_PUBLIC,
  128. creator: dummyUser1,
  129. lastUpdateUser: dummyUser1._id,
  130. descendantCount: 0,
  131. isEmpty: false,
  132. },
  133. {
  134. _id: pageId5,
  135. path: '/resume_rename_4/resume_rename_5',
  136. parent: pageId4,
  137. grant: Page.GRANT_PUBLIC,
  138. creator: dummyUser1,
  139. lastUpdateUser: dummyUser1._id,
  140. descendantCount: 1,
  141. isEmpty: false,
  142. },
  143. {
  144. _id: pageId6,
  145. path: '/resume_rename_5/resume_rename_6',
  146. parent: pageId5,
  147. grant: Page.GRANT_PUBLIC,
  148. creator: dummyUser1,
  149. lastUpdateUser: dummyUser1._id,
  150. descendantCount: 0,
  151. isEmpty: false,
  152. },
  153. {
  154. _id: pageId7,
  155. path: '/resume_rename_7',
  156. parent: rootPage._id,
  157. grant: Page.GRANT_PUBLIC,
  158. creator: dummyUser1,
  159. lastUpdateUser: dummyUser1._id,
  160. descendantCount: 0,
  161. isEmpty: false,
  162. },
  163. {
  164. _id: pageId8,
  165. path: '/resume_rename_8',
  166. parent: rootPage._id,
  167. grant: Page.GRANT_PUBLIC,
  168. creator: dummyUser1,
  169. lastUpdateUser: dummyUser1._id,
  170. descendantCount: 1,
  171. isEmpty: false,
  172. },
  173. {
  174. _id: pageId9,
  175. path: '/resume_rename_8/resume_rename_9',
  176. parent: pageId8,
  177. grant: Page.GRANT_PUBLIC,
  178. creator: dummyUser1,
  179. lastUpdateUser: dummyUser1._id,
  180. descendantCount: 1,
  181. isEmpty: false,
  182. },
  183. {
  184. path: '/resume_rename_9/resume_rename_10',
  185. parent: pageId9,
  186. grant: Page.GRANT_PUBLIC,
  187. creator: dummyUser1,
  188. lastUpdateUser: dummyUser1._id,
  189. descendantCount: 0,
  190. isEmpty: false,
  191. },
  192. {
  193. _id: pageId10,
  194. path: '/resume_rename_11',
  195. parent: rootPage._id,
  196. grant: Page.GRANT_PUBLIC,
  197. creator: dummyUser1,
  198. lastUpdateUser: dummyUser1._id,
  199. descendantCount: 3,
  200. isEmpty: false,
  201. },
  202. {
  203. _id: pageId11,
  204. path: '/resume_rename_11/resume_rename_12',
  205. parent: pageId10,
  206. grant: Page.GRANT_PUBLIC,
  207. creator: dummyUser1,
  208. lastUpdateUser: dummyUser1._id,
  209. descendantCount: 2,
  210. isEmpty: false,
  211. },
  212. {
  213. _id: pageId12,
  214. path: '/resume_rename_11/resume_rename_12/resume_rename_13',
  215. parent: pageId11,
  216. grant: Page.GRANT_PUBLIC,
  217. creator: dummyUser1,
  218. lastUpdateUser: dummyUser1._id,
  219. descendantCount: 1,
  220. isEmpty: false,
  221. },
  222. {
  223. path: '/resume_rename_11/resume_rename_12/resume_rename_13/resume_rename_14',
  224. parent: pageId12,
  225. grant: Page.GRANT_PUBLIC,
  226. creator: dummyUser1,
  227. lastUpdateUser: dummyUser1._id,
  228. descendantCount: 0,
  229. isEmpty: false,
  230. },
  231. {
  232. _id: pageId13,
  233. path: '/resume_rename_15',
  234. parent: rootPage._id,
  235. grant: Page.GRANT_PUBLIC,
  236. creator: dummyUser1,
  237. lastUpdateUser: dummyUser1._id,
  238. descendantCount: 2,
  239. isEmpty: false,
  240. },
  241. {
  242. _id: pageId14,
  243. path: '/resume_rename_15/resume_rename_16',
  244. parent: pageId13,
  245. grant: Page.GRANT_PUBLIC,
  246. creator: dummyUser1,
  247. lastUpdateUser: dummyUser1._id,
  248. descendantCount: 0,
  249. isEmpty: false,
  250. },
  251. {
  252. _id: pageId15,
  253. path: '/resume_rename_15/resume_rename_17',
  254. parent: pageId13,
  255. grant: Page.GRANT_PUBLIC,
  256. creator: dummyUser1,
  257. lastUpdateUser: dummyUser1._id,
  258. descendantCount: 1,
  259. isEmpty: false,
  260. },
  261. {
  262. _id: pageId16,
  263. path: '/resume_rename_15/resume_rename_17/resume_rename_18',
  264. parent: pageId15,
  265. grant: Page.GRANT_PUBLIC,
  266. creator: dummyUser1,
  267. lastUpdateUser: dummyUser1._id,
  268. descendantCount: 1,
  269. isEmpty: false,
  270. },
  271. {
  272. _id: pageId17,
  273. path: '/resume_rename_15/resume_rename_17/resume_rename_18/resume_rename_19',
  274. parent: pageId16,
  275. grant: Page.GRANT_PUBLIC,
  276. creator: dummyUser1,
  277. lastUpdateUser: dummyUser1._id,
  278. descendantCount: 0,
  279. isEmpty: false,
  280. },
  281. {
  282. _id: pageId18,
  283. path: '/fix_descendantCount_1',
  284. parent: rootPage._id,
  285. grant: Page.GRANT_PUBLIC,
  286. creator: dummyUser1,
  287. lastUpdateUser: dummyUser1._id,
  288. descendantCount: 100, // broken
  289. isEmpty: false,
  290. },
  291. {
  292. _id: pageId19,
  293. path: '/fix_descendantCount_1/fix_descendantCount_2',
  294. parent: pageId18,
  295. grant: Page.GRANT_PUBLIC,
  296. creator: dummyUser1,
  297. lastUpdateUser: dummyUser1._id,
  298. descendantCount: 100, // broken
  299. isEmpty: true,
  300. },
  301. {
  302. path: '/fix_descendantCount_1/fix_descendantCount_2/fix_descendantCount_3',
  303. parent: pageId19,
  304. grant: Page.GRANT_PUBLIC,
  305. creator: dummyUser1,
  306. lastUpdateUser: dummyUser1._id,
  307. descendantCount: 100, // broken
  308. isEmpty: false,
  309. },
  310. {
  311. _id: pageId20,
  312. path: '/fix_descendantCount_4',
  313. parent: rootPage._id,
  314. grant: Page.GRANT_PUBLIC,
  315. creator: dummyUser1,
  316. lastUpdateUser: dummyUser1._id,
  317. descendantCount: 100, // broken
  318. isEmpty: false,
  319. },
  320. {
  321. path: '/fix_descendantCount_4/fix_descendantCount_5',
  322. parent: pageId20,
  323. grant: Page.GRANT_PUBLIC,
  324. creator: dummyUser1,
  325. lastUpdateUser: dummyUser1._id,
  326. descendantCount: 100, // broken
  327. isEmpty: false,
  328. },
  329. ]);
  330. /**
  331. * PageOperation
  332. */
  333. pageOpId1 = new mongoose.Types.ObjectId();
  334. pageOpId2 = new mongoose.Types.ObjectId();
  335. pageOpId3 = new mongoose.Types.ObjectId();
  336. pageOpId4 = new mongoose.Types.ObjectId();
  337. const pageOpRevisionId1 = new mongoose.Types.ObjectId();
  338. const pageOpRevisionId2 = new mongoose.Types.ObjectId();
  339. const pageOpRevisionId3 = new mongoose.Types.ObjectId();
  340. const pageOpRevisionId4 = new mongoose.Types.ObjectId();
  341. await PageOperation.insertMany([
  342. {
  343. _id: pageOpId1,
  344. actionType: 'Rename',
  345. actionStage: 'Sub',
  346. fromPath: '/resume_rename_1',
  347. toPath: '/resume_rename_0/resume_rename_1',
  348. page: {
  349. _id: pageId1,
  350. parent: rootPage._id,
  351. descendantCount: 2,
  352. isEmpty: false,
  353. path: '/resume_rename_1',
  354. revision: pageOpRevisionId1,
  355. status: 'published',
  356. grant: 1,
  357. grantedUsers: [],
  358. grantedGroups: [],
  359. creator: dummyUser1._id,
  360. lastUpdateUser: dummyUser1._id,
  361. },
  362. user: {
  363. _id: dummyUser1._id,
  364. },
  365. options: {
  366. createRedirectPage: false,
  367. updateMetadata: true,
  368. },
  369. unprocessableExpiryDate: null,
  370. },
  371. {
  372. _id: pageOpId2,
  373. actionType: 'Rename',
  374. actionStage: 'Sub',
  375. fromPath: '/resume_rename_5',
  376. toPath: '/resume_rename_4/resume_rename_5',
  377. page: {
  378. _id: pageId5,
  379. parent: rootPage._id,
  380. descendantCount: 2,
  381. isEmpty: false,
  382. path: '/resume_rename_5',
  383. revision: pageOpRevisionId2,
  384. status: 'published',
  385. grant: 1,
  386. grantedUsers: [],
  387. grantedGroups: [],
  388. creator: dummyUser1._id,
  389. lastUpdateUser: dummyUser1._id,
  390. },
  391. user: {
  392. _id: dummyUser1._id,
  393. },
  394. options: {
  395. createRedirectPage: false,
  396. updateMetadata: true,
  397. },
  398. unprocessableExpiryDate: new Date(),
  399. },
  400. {
  401. _id: pageOpId3,
  402. actionType: 'Rename',
  403. actionStage: 'Sub',
  404. fromPath: '/resume_rename_7',
  405. // toPath NOT exist
  406. page: {
  407. _id: pageId7,
  408. parent: rootPage._id,
  409. descendantCount: 2,
  410. isEmpty: false,
  411. path: '/resume_rename_7',
  412. revision: pageOpRevisionId3,
  413. status: 'published',
  414. grant: 1,
  415. grantedUsers: [],
  416. grantedGroups: [],
  417. creator: dummyUser1._id,
  418. lastUpdateUser: dummyUser1._id,
  419. },
  420. user: {
  421. _id: dummyUser1._id,
  422. },
  423. options: {
  424. createRedirectPage: false,
  425. updateMetadata: true,
  426. },
  427. unprocessableExpiryDate: new Date(),
  428. },
  429. {
  430. _id: pageOpId4,
  431. actionType: 'Rename',
  432. actionStage: 'Sub',
  433. fromPath: '/resume_rename_9',
  434. toPath: '/resume_rename_8/resume_rename_9',
  435. page: {
  436. _id: pageId9,
  437. parent: rootPage._id,
  438. descendantCount: 1,
  439. isEmpty: false,
  440. path: '/resume_rename_9',
  441. revision: pageOpRevisionId4,
  442. status: 'published',
  443. grant: Page.GRANT_PUBLIC,
  444. grantedUsers: [],
  445. grantedGroups: [],
  446. creator: dummyUser1._id,
  447. lastUpdateUser: dummyUser1._id,
  448. },
  449. user: {
  450. _id: dummyUser1._id,
  451. },
  452. options: {
  453. createRedirectPage: false,
  454. updateMetadata: true,
  455. },
  456. unprocessableExpiryDate: null,
  457. },
  458. ]);
  459. });
  460. describe('restart renameOperation', () => {
  461. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  462. const resumeRenameSubOperation = async (
  463. renamePage: any,
  464. pageOp: any,
  465. activity?: any,
  466. ) => {
  467. // Access private method for testing purposes
  468. const pageServiceWithPrivate = crowi.pageService as unknown as Record<
  469. string,
  470. (...args: unknown[]) => unknown
  471. >;
  472. const mockedPathsAndDescendantCountOfAncestors = vi
  473. .spyOn(pageServiceWithPrivate, 'fixPathsAndDescendantCountOfAncestors')
  474. .mockReturnValue(null);
  475. await crowi.pageService.resumeRenameSubOperation(
  476. renamePage,
  477. pageOp,
  478. activity,
  479. );
  480. const argsForRenameSubOperation =
  481. mockedPathsAndDescendantCountOfAncestors.mock.calls[0];
  482. mockedPathsAndDescendantCountOfAncestors.mockRestore();
  483. await pageServiceWithPrivate.fixPathsAndDescendantCountOfAncestors(
  484. ...argsForRenameSubOperation,
  485. );
  486. };
  487. it('it should successfully restart rename operation', async () => {
  488. // paths before renaming
  489. const _path0 = '/resume_rename_0'; // out of renaming scope
  490. const _path1 = '/resume_rename_0/resume_rename_1'; // renamed already
  491. const _path2 = '/resume_rename_1/resume_rename_2'; // not renamed yet
  492. const _path3 = '/resume_rename_1/resume_rename_2/resume_rename_3'; // not renamed yet
  493. // paths after renaming
  494. const path0 = '/resume_rename_0';
  495. const path1 = '/resume_rename_0/resume_rename_1';
  496. const path2 = '/resume_rename_0/resume_rename_1/resume_rename_2';
  497. const path3 =
  498. '/resume_rename_0/resume_rename_1/resume_rename_2/resume_rename_3';
  499. // activity options
  500. const activity = 'randomActivityId';
  501. // page
  502. const _page0 = await Page.findOne({ path: _path0 });
  503. const _page1 = await Page.findOne({ path: _path1 });
  504. const _page2 = await Page.findOne({ path: _path2 });
  505. const _page3 = await Page.findOne({ path: _path3 });
  506. expect(_page0).toBeTruthy();
  507. expect(_page1).toBeTruthy();
  508. expect(_page2).toBeTruthy();
  509. expect(_page3).toBeTruthy();
  510. expect(_page0?.descendantCount).toBe(1);
  511. expect(_page1?.descendantCount).toBe(2);
  512. expect(_page2?.descendantCount).toBe(1);
  513. expect(_page3?.descendantCount).toBe(0);
  514. // page operation
  515. const fromPath = '/resume_rename_1';
  516. const toPath = '/resume_rename_0/resume_rename_1';
  517. const _pageOperation = await PageOperation.findOne({
  518. _id: pageOpId1,
  519. fromPath,
  520. toPath,
  521. 'page._id': _page1?._id,
  522. actionType: PageActionType.Rename,
  523. actionStage: PageActionStage.Sub,
  524. });
  525. expect(_pageOperation).toBeTruthy();
  526. // rename
  527. await resumeRenameSubOperation(_page1, _pageOperation, activity);
  528. // page
  529. const page0 = await Page.findById(_page0?._id);
  530. const page1 = await Page.findById(_page1?._id);
  531. const page2 = await Page.findById(_page2?._id);
  532. const page3 = await Page.findById(_page3?._id);
  533. expect(page0).toBeTruthy();
  534. expect(page1).toBeTruthy();
  535. expect(page2).toBeTruthy();
  536. expect(page3).toBeTruthy();
  537. // check paths after renaming
  538. expect(page0?.path).toBe(path0);
  539. expect(page1?.path).toBe(path1);
  540. expect(page2?.path).toBe(path2);
  541. expect(page3?.path).toBe(path3);
  542. // page operation
  543. const pageOperation = await PageOperation.findById(_pageOperation?._id);
  544. expect(pageOperation).toBeNull(); // should not exist
  545. expect(page0?.descendantCount).toBe(3);
  546. expect(page1?.descendantCount).toBe(2);
  547. expect(page2?.descendantCount).toBe(1);
  548. expect(page3?.descendantCount).toBe(0);
  549. });
  550. it('it should successfully restart rename operation when unprocessableExpiryDate is null', async () => {
  551. // paths before renaming
  552. const _path0 = '/resume_rename_8'; // out of renaming scope
  553. const _path1 = '/resume_rename_8/resume_rename_9'; // renamed already
  554. const _path2 = '/resume_rename_9/resume_rename_10'; // not renamed yet
  555. // paths after renaming
  556. const path0 = '/resume_rename_8';
  557. const path1 = '/resume_rename_8/resume_rename_9';
  558. const path2 = '/resume_rename_8/resume_rename_9/resume_rename_10';
  559. // activity options
  560. const activityParameters = {
  561. ip: '::ffff:127.0.0.1',
  562. endpoint: '/_api/v3/pages/rename',
  563. activityId: '62e291bc10e0ab61bd691794',
  564. };
  565. // page
  566. const _page0 = await Page.findOne({ path: _path0 });
  567. const _page1 = await Page.findOne({ path: _path1 });
  568. const _page2 = await Page.findOne({ path: _path2 });
  569. expect(_page0).toBeTruthy();
  570. expect(_page1).toBeTruthy();
  571. expect(_page2).toBeTruthy();
  572. expect(_page0?.descendantCount).toBe(1);
  573. expect(_page1?.descendantCount).toBe(1);
  574. expect(_page2?.descendantCount).toBe(0);
  575. // page operation
  576. const fromPath = '/resume_rename_9';
  577. const toPath = '/resume_rename_8/resume_rename_9';
  578. const _pageOperation = await PageOperation.findOne({
  579. _id: pageOpId4,
  580. fromPath,
  581. toPath,
  582. 'page._id': _page1?._id,
  583. actionType: PageActionType.Rename,
  584. actionStage: PageActionStage.Sub,
  585. });
  586. expect(_pageOperation).toBeTruthy();
  587. // rename
  588. await resumeRenameSubOperation(
  589. _page1,
  590. _pageOperation,
  591. activityParameters,
  592. );
  593. // page
  594. const page0 = await Page.findById(_page0?._id);
  595. const page1 = await Page.findById(_page1?._id);
  596. const page2 = await Page.findById(_page2?._id);
  597. expect(page0).toBeTruthy();
  598. expect(page1).toBeTruthy();
  599. expect(page2).toBeTruthy();
  600. // check paths after renaming
  601. expect(page0?.path).toBe(path0);
  602. expect(page1?.path).toBe(path1);
  603. expect(page2?.path).toBe(path2);
  604. // page operation
  605. const pageOperation = await PageOperation.findById(_pageOperation?._id);
  606. expect(pageOperation).toBeNull(); // should not exist
  607. // others
  608. expect(page1?.parent).toStrictEqual(page0?._id);
  609. expect(page2?.parent).toStrictEqual(page1?._id);
  610. expect(page0?.descendantCount).toBe(2);
  611. expect(page1?.descendantCount).toBe(1);
  612. expect(page2?.descendantCount).toBe(0);
  613. });
  614. it('it should fail and throw error if the current time is behind unprocessableExpiryDate', async () => {
  615. // path before renaming
  616. const _path0 = '/resume_rename_4'; // out of renaming scope
  617. const _path1 = '/resume_rename_4/resume_rename_5'; // renamed already
  618. const _path2 = '/resume_rename_5/resume_rename_6'; // not renamed yet
  619. // page
  620. const _page0 = await Page.findOne({ path: _path0 });
  621. const _page1 = await Page.findOne({ path: _path1 });
  622. const _page2 = await Page.findOne({ path: _path2 });
  623. expect(_page0).toBeTruthy();
  624. expect(_page1).toBeTruthy();
  625. expect(_page2).toBeTruthy();
  626. // page operation
  627. const fromPath = '/resume_rename_5';
  628. const toPath = '/resume_rename_4/resume_rename_5';
  629. const _pageOperation = await PageOperation.findOne({
  630. _id: pageOpId2,
  631. fromPath,
  632. toPath,
  633. 'page._id': _page1?._id,
  634. actionType: PageActionType.Rename,
  635. actionStage: PageActionStage.Sub,
  636. });
  637. expect(_pageOperation).toBeTruthy();
  638. // Make `unprocessableExpiryDate` 15 seconds ahead of current time.
  639. // The number 15 seconds has no meaning other than placing time in the furue.
  640. const pageOperation = await PageOperation.findByIdAndUpdate(
  641. _pageOperation?._id,
  642. { unprocessableExpiryDate: addSeconds(new Date(), 15) },
  643. { new: true },
  644. );
  645. expect(pageOperation).toBeTruthy();
  646. await expect(
  647. resumeRenameSubOperation(_page1, pageOperation),
  648. ).rejects.toThrow(
  649. new Error('This page operation is currently being processed'),
  650. );
  651. // cleanup
  652. await PageOperation.findByIdAndDelete(pageOperation?._id);
  653. });
  654. it('Missing property(toPath) for PageOperation should throw error', async () => {
  655. // page
  656. const _path1 = '/resume_rename_7';
  657. const _page1 = await Page.findOne({ path: _path1 });
  658. expect(_page1).toBeTruthy();
  659. // page operation
  660. const pageOperation = await PageOperation.findOne({
  661. _id: pageOpId3,
  662. 'page._id': _page1?._id,
  663. actionType: PageActionType.Rename,
  664. actionStage: PageActionStage.Sub,
  665. });
  666. expect(pageOperation).toBeTruthy();
  667. const promise = resumeRenameSubOperation(_page1, pageOperation);
  668. await expect(promise).rejects.toThrow(
  669. new Error(
  670. `Property toPath is missing which is needed to resume rename operation(${pageOperation?._id})`,
  671. ),
  672. );
  673. // cleanup
  674. await PageOperation.findByIdAndDelete(pageOperation?._id);
  675. });
  676. });
  677. describe('updateDescendantCountOfPagesWithPaths', () => {
  678. it('should fix descendantCount of pages with one of the given paths', async () => {
  679. // path
  680. const _path1 = '/fix_descendantCount_1';
  681. const _path2 = '/fix_descendantCount_1/fix_descendantCount_2'; // empty
  682. const _path3 =
  683. '/fix_descendantCount_1/fix_descendantCount_2/fix_descendantCount_3';
  684. const _path4 = '/fix_descendantCount_4';
  685. const _path5 = '/fix_descendantCount_4/fix_descendantCount_5';
  686. // page
  687. const _page1 = await Page.findOne({ path: _path1 });
  688. const _page2 = await Page.findOne({ path: _path2 });
  689. const _page3 = await Page.findOne({ path: _path3 });
  690. const _page4 = await Page.findOne({ path: _path4 });
  691. const _page5 = await Page.findOne({ path: _path5 });
  692. // check existance
  693. expect(_page1).toBeTruthy();
  694. expect(_page2).toBeTruthy();
  695. expect(_page3).toBeTruthy();
  696. expect(_page4).toBeTruthy();
  697. expect(_page5).toBeTruthy();
  698. // check descendantCount (all broken)
  699. expect(_page1?.descendantCount).toBe(100);
  700. expect(_page2?.descendantCount).toBe(100);
  701. expect(_page3?.descendantCount).toBe(100);
  702. expect(_page4?.descendantCount).toBe(100);
  703. expect(_page5?.descendantCount).toBe(100);
  704. // check isEmpty
  705. expect(_page1?.isEmpty).toBe(false);
  706. expect(_page2?.isEmpty).toBe(true);
  707. expect(_page3?.isEmpty).toBe(false);
  708. expect(_page4?.isEmpty).toBe(false);
  709. expect(_page5?.isEmpty).toBe(false);
  710. // check parent
  711. expect(_page1?.parent).toStrictEqual(rootPage._id);
  712. expect(_page2?.parent).toStrictEqual(_page1?._id);
  713. expect(_page3?.parent).toStrictEqual(_page2?._id);
  714. expect(_page4?.parent).toStrictEqual(rootPage._id);
  715. expect(_page5?.parent).toStrictEqual(_page4?._id);
  716. await crowi.pageService.updateDescendantCountOfPagesWithPaths([
  717. _path1,
  718. _path2,
  719. _path3,
  720. _path4,
  721. _path5,
  722. ]);
  723. // page
  724. const page1 = await Page.findById(_page1?._id);
  725. const page2 = await Page.findById(_page2?._id);
  726. const page3 = await Page.findById(_page3?._id);
  727. const page4 = await Page.findById(_page4?._id);
  728. const page5 = await Page.findById(_page5?._id);
  729. // check existance
  730. expect(page1).toBeTruthy();
  731. expect(page2).toBeTruthy();
  732. expect(page3).toBeTruthy();
  733. expect(page4).toBeTruthy();
  734. expect(page5).toBeTruthy();
  735. // check descendantCount (all fixed)
  736. expect(page1?.descendantCount).toBe(1);
  737. expect(page2?.descendantCount).toBe(1);
  738. expect(page3?.descendantCount).toBe(0);
  739. expect(page4?.descendantCount).toBe(1);
  740. expect(page5?.descendantCount).toBe(0);
  741. // check isEmpty
  742. expect(page1?.isEmpty).toBe(false);
  743. expect(page2?.isEmpty).toBe(true);
  744. expect(page3?.isEmpty).toBe(false);
  745. expect(page4?.isEmpty).toBe(false);
  746. expect(page5?.isEmpty).toBe(false);
  747. // check parent
  748. expect(page1?.parent).toStrictEqual(rootPage._id);
  749. expect(page2?.parent).toStrictEqual(page1?._id);
  750. expect(page3?.parent).toStrictEqual(page2?._id);
  751. expect(page4?.parent).toStrictEqual(rootPage._id);
  752. expect(page5?.parent).toStrictEqual(page4?._id);
  753. });
  754. });
  755. });