/* eslint-disable no-unused-vars */ import { advanceTo } from 'jest-date-mock'; import mongoose from 'mongoose'; import { PageActionType, PageActionStage } from '../../../src/interfaces/page-operation'; import Tag from '../../../src/server/models/tag'; import { getInstance } from '../setup-crowi'; describe('PageService page operations with only public pages', () => { let dummyUser1; let dummyUser2; let crowi; let Page; let Revision; let User; let PageTagRelation; let Bookmark; let Comment; let ShareLink; let PageRedirect; let PageOperation; let xssSpy; let rootPage; // page operation ids let pageOpId1; beforeAll(async() => { crowi = await getInstance(); await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'app:isV5Compatible': true }); User = mongoose.model('User'); Page = mongoose.model('Page'); Revision = mongoose.model('Revision'); PageTagRelation = mongoose.model('PageTagRelation'); Bookmark = mongoose.model('Bookmark'); Comment = mongoose.model('Comment'); ShareLink = mongoose.model('ShareLink'); PageRedirect = mongoose.model('PageRedirect'); PageOperation = mongoose.model('PageOperation'); /* * Common */ dummyUser1 = await User.findOne({ username: 'v5DummyUser1' }); dummyUser2 = await User.findOne({ username: 'v5DummyUser2' }); xssSpy = jest.spyOn(crowi.xss, 'process').mockImplementation(path => path); rootPage = await Page.findOne({ path: '/' }); if (rootPage == null) { const pages = await Page.insertMany([{ path: '/', grant: Page.GRANT_PUBLIC }]); rootPage = pages[0]; } /** * create * mc_ => model create * emp => empty => page with isEmpty: true * pub => public => GRANT_PUBLIC */ const pageIdCreate1 = new mongoose.Types.ObjectId(); await Page.insertMany([ { _id: pageIdCreate1, path: '/v5_empty_create_4', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { path: '/v5_empty_create_4/v5_create_5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdCreate1, isEmpty: false, }, ]); /** * create by system * mc_ => model create * emp => empty => page with isEmpty: true * pub => public => GRANT_PUBLIC */ const pageIdCreateBySystem1 = new mongoose.Types.ObjectId(); await Page.insertMany([ { _id: pageIdCreateBySystem1, path: '/v5_empty_create_by_system4', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { path: '/v5_empty_create_by_system4/v5_create_by_system5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdCreateBySystem1, isEmpty: false, }, ]); /* * Rename */ const pageIdForRename1 = new mongoose.Types.ObjectId(); const pageIdForRename2 = new mongoose.Types.ObjectId(); const pageIdForRename3 = new mongoose.Types.ObjectId(); const pageIdForRename4 = new mongoose.Types.ObjectId(); const pageIdForRename5 = new mongoose.Types.ObjectId(); const pageIdForRename7 = new mongoose.Types.ObjectId(); const pageIdForRename8 = new mongoose.Types.ObjectId(); const pageIdForRename9 = new mongoose.Types.ObjectId(); const pageIdForRename10 = new mongoose.Types.ObjectId(); const pageIdForRename11 = new mongoose.Types.ObjectId(); const pageIdForRename12 = new mongoose.Types.ObjectId(); const pageIdForRename13 = new mongoose.Types.ObjectId(); const pageIdForRename14 = new mongoose.Types.ObjectId(); const pageIdForRename16 = new mongoose.Types.ObjectId(); const pageIdForRename17 = new mongoose.Types.ObjectId(); const pageIdForRename18 = new mongoose.Types.ObjectId(); const pageIdForRename19 = new mongoose.Types.ObjectId(); const pageIdForRename20 = new mongoose.Types.ObjectId(); const pageIdForRename21 = new mongoose.Types.ObjectId(); const pageIdForRename22 = new mongoose.Types.ObjectId(); const pageIdForRename23 = new mongoose.Types.ObjectId(); const pageIdForRename24 = new mongoose.Types.ObjectId(); const pageIdForRename25 = new mongoose.Types.ObjectId(); const pageIdForRename26 = new mongoose.Types.ObjectId(); const pageIdForRename27 = new mongoose.Types.ObjectId(); const pageIdForRename28 = new mongoose.Types.ObjectId(); const pageIdForRename29 = new mongoose.Types.ObjectId(); const pageIdForRename30 = new mongoose.Types.ObjectId(); pageOpId1 = new mongoose.Types.ObjectId(); const pageOpRevisionId1 = new mongoose.Types.ObjectId(); // Create Pages await Page.insertMany([ { _id: pageIdForRename1, path: '/v5_ParentForRename1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename2, path: '/v5_ParentForRename2', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { // id not needed for this data path: '/v5_ParentForRename2/dummyChild1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename2, }, { _id: pageIdForRename3, path: '/v5_ParentForRename3', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename4, path: '/v5_ParentForRename4', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename5, path: '/v5_ParentForRename5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename7, path: '/v5_ParentForRename7', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename8, path: '/v5_ParentForRename8', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename9, path: '/v5_ParentForRename9', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename10, path: '/v5_ChildForRename1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename11, path: '/v5_ChildForRename2', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename12, path: '/v5_ChildForRename3', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, updatedAt: new Date('2021'), }, { _id: pageIdForRename13, path: '/v5_ChildForRename4', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename14, path: '/v5_ChildForRename5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename16, path: '/v5_ChildForRename7', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { path: '/v5_ChildForRename5/v5_GrandchildForRename5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename14, updatedAt: new Date('2021'), }, { path: '/v5_ChildForRename7/v5_GrandchildForRename7', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename16, }, { _id: pageIdForRename17, path: '/v5_pageForRename17', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename18, path: '/v5_pageForRename17/v5_pageForRename18', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename17, }, { _id: pageIdForRename19, path: '/v5_pageForRename19', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { _id: pageIdForRename20, path: '/v5_pageForRename19/v5_pageForRename20', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename19, }, { _id: pageIdForRename21, path: '/v5_pageForRename21', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, }, { _id: pageIdForRename22, path: '/v5_pageForRename21/v5_pageForRename22', grant: Page.GRANT_PUBLIC, isEmpty: true, parent: pageIdForRename21, }, { _id: pageIdForRename23, path: '/v5_pageForRename21/v5_pageForRename22/v5_pageForRename23', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename22, }, { _id: pageIdForRename24, path: '/v5_pageForRename24', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, descendantCount: 0, }, { _id: pageIdForRename25, path: '/v5_pageForRename25', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, descendantCount: 0, }, { _id: pageIdForRename26, path: '/v5_pageForRename26', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, descendantCount: 0, }, { _id: pageIdForRename27, path: '/v5_pageForRename27', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, descendantCount: 1, }, { _id: pageIdForRename28, path: '/v5_pageForRename27/v5_pageForRename28', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename27, descendantCount: 0, }, { _id: pageIdForRename29, path: '/v5_pageForRename29', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, descendantCount: 1, }, { _id: pageIdForRename30, path: '/v5_pageForRename29/v5_pageForRename30', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForRename29, descendantCount: 0, }, ]); await PageOperation.insertMany([ { _id: pageOpId1, actionType: 'Rename', actionStage: 'Sub', fromPath: '/v5_pageForRename30', toPath: '/v5_pageForRename29/v5_pageForRename30', page: { _id: pageIdForRename30, parent: rootPage._id, descendantCount: 0, isEmpty: false, path: '/v5_pageForRename30', revision: pageOpRevisionId1, status: 'published', grant: 1, grantedUsers: [], grantedGroup: null, creator: dummyUser1._id, lastUpdateUser: dummyUser1._id, }, user: { _id: dummyUser1._id, }, options: { createRedirectPage: false, updateMetadata: true, }, activityParameters: { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }, unprocessableExpiryDate: null, }, ]); /* * Duplicate */ // page ids const pageIdForDuplicate1 = new mongoose.Types.ObjectId(); const pageIdForDuplicate2 = new mongoose.Types.ObjectId(); const pageIdForDuplicate3 = new mongoose.Types.ObjectId(); const pageIdForDuplicate4 = new mongoose.Types.ObjectId(); const pageIdForDuplicate5 = new mongoose.Types.ObjectId(); const pageIdForDuplicate6 = new mongoose.Types.ObjectId(); const pageIdForDuplicate7 = new mongoose.Types.ObjectId(); const pageIdForDuplicate8 = new mongoose.Types.ObjectId(); const pageIdForDuplicate9 = new mongoose.Types.ObjectId(); const pageIdForDuplicate10 = new mongoose.Types.ObjectId(); const pageIdForDuplicate11 = new mongoose.Types.ObjectId(); const pageIdForDuplicate12 = new mongoose.Types.ObjectId(); const pageIdForDuplicate13 = new mongoose.Types.ObjectId(); const pageIdForDuplicate14 = new mongoose.Types.ObjectId(); const pageIdForDuplicate15 = new mongoose.Types.ObjectId(); // revision ids const revisionIdForDuplicate1 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate2 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate3 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate4 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate5 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate6 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate7 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate8 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate9 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate10 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate11 = new mongoose.Types.ObjectId(); const revisionIdForDuplicate12 = new mongoose.Types.ObjectId(); await Page.insertMany([ { _id: pageIdForDuplicate1, path: '/v5_PageForDuplicate1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, revision: revisionIdForDuplicate1, }, { _id: pageIdForDuplicate2, path: '/v5_PageForDuplicate2', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { _id: pageIdForDuplicate3, path: '/v5_PageForDuplicate2/v5_ChildForDuplicate2', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDuplicate2, revision: revisionIdForDuplicate2, }, { _id: pageIdForDuplicate4, path: '/v5_PageForDuplicate3', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, revision: revisionIdForDuplicate3, }, { _id: pageIdForDuplicate5, path: '/v5_PageForDuplicate3/v5_Child_1_ForDuplicate3', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDuplicate4, revision: revisionIdForDuplicate4, }, { _id: pageIdForDuplicate6, path: '/v5_PageForDuplicate3/v5_Child_2_ForDuplicate3', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDuplicate4, revision: revisionIdForDuplicate5, }, { _id: pageIdForDuplicate7, path: '/v5_PageForDuplicate4', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, revision: revisionIdForDuplicate6, }, { _id: pageIdForDuplicate8, path: '/v5_PageForDuplicate4/v5_empty_PageForDuplicate4', grant: Page.GRANT_PUBLIC, parent: pageIdForDuplicate7, isEmpty: true, }, { _id: pageIdForDuplicate9, path: '/v5_PageForDuplicate4/v5_empty_PageForDuplicate4/v5_grandchild_PageForDuplicate4', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDuplicate8, revision: revisionIdForDuplicate7, }, { _id: pageIdForDuplicate10, path: '/v5_PageForDuplicate5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, revision: revisionIdForDuplicate8, }, { _id: pageIdForDuplicate11, path: '/v5_PageForDuplicate6', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, revision: revisionIdForDuplicate9, }, { _id: pageIdForDuplicate13, path: '/v5_empty_PageForDuplicate7', grant: Page.GRANT_PUBLIC, parent: rootPage._id, isEmpty: true, }, { _id: pageIdForDuplicate14, path: '/v5_empty_PageForDuplicate7/v5_child_PageForDuplicate7', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDuplicate13, revision: revisionIdForDuplicate11, }, { _id: pageIdForDuplicate15, path: '/v5_empty_PageForDuplicate7/v5_child_PageForDuplicate7/v5_grandchild_PageForDuplicate7', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDuplicate14, revision: revisionIdForDuplicate12, }, ]); await Revision.insertMany([ { _id: revisionIdForDuplicate1, body: 'body1', format: 'markdown', pageId: pageIdForDuplicate1, author: dummyUser1, }, { _id: revisionIdForDuplicate2, body: 'body3', format: 'markdown', pageId: pageIdForDuplicate3, author: dummyUser1, }, { _id: revisionIdForDuplicate3, body: 'parent_page_body4', format: 'markdown', pageId: pageIdForDuplicate4, author: dummyUser1, }, { _id: revisionIdForDuplicate4, body: 'revision_id_4_child_page_body', format: 'markdown', pageId: pageIdForDuplicate5, author: dummyUser1, }, { _id: revisionIdForDuplicate5, body: 'revision_id_5_child_page_body', format: 'markdown', pageId: pageIdForDuplicate6, author: dummyUser1, }, { _id: revisionIdForDuplicate6, body: '/v5_PageForDuplicate4', format: 'markdown', pageId: pageIdForDuplicate7, author: dummyUser1, }, { _id: revisionIdForDuplicate7, body: '/v5_PageForDuplicate4/v5_empty_PageForDuplicate4/v5_grandchild_PageForDuplicate4', format: 'markdown', pageId: pageIdForDuplicate9, author: dummyUser1, }, { _id: revisionIdForDuplicate8, body: '/v5_PageForDuplicate5', format: 'markdown', pageId: pageIdForDuplicate10, author: dummyUser1, }, { _id: revisionIdForDuplicate9, body: '/v5_PageForDuplicate6', format: 'markdown', pageId: pageIdForDuplicate11, author: dummyUser1, }, { _id: revisionIdForDuplicate10, body: '/v5_PageForDuplicate6', format: 'comment', pageId: pageIdForDuplicate12, author: dummyUser1, }, { _id: revisionIdForDuplicate11, body: '/v5_child_PageForDuplicate7', format: 'markdown', pageId: pageIdForDuplicate14, author: dummyUser1, }, { _id: revisionIdForDuplicate12, body: '/v5_grandchild_PageForDuplicate7', format: 'markdown', pageId: pageIdForDuplicate15, author: dummyUser1, }, ]); const tagForDuplicate1 = new mongoose.Types.ObjectId(); const tagForDuplicate2 = new mongoose.Types.ObjectId(); await Tag.insertMany([ { _id: tagForDuplicate1, name: 'duplicate_Tag1' }, { _id: tagForDuplicate2, name: 'duplicate_Tag2' }, ]); await PageTagRelation.insertMany([ { relatedPage: pageIdForDuplicate10, relatedTag: tagForDuplicate1 }, { relatedPage: pageIdForDuplicate10._id, relatedTag: tagForDuplicate2 }, ]); await Comment.insertMany([ { commentPosition: -1, isMarkdown: true, page: pageIdForDuplicate11, creator: dummyUser1._id, revision: revisionIdForDuplicate10, comment: 'this is comment', }, ]); /** * Delete */ const pageIdForDelete1 = new mongoose.Types.ObjectId(); const pageIdForDelete2 = new mongoose.Types.ObjectId(); const pageIdForDelete3 = new mongoose.Types.ObjectId(); const pageIdForDelete4 = new mongoose.Types.ObjectId(); const pageIdForDelete5 = new mongoose.Types.ObjectId(); await Page.insertMany([ { path: '/trash/v5_PageForDelete1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, status: Page.STATUS_DELETED, }, { path: '/v5_PageForDelete2', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDelete1, path: '/v5_PageForDelete3', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDelete2, path: '/v5_PageForDelete3/v5_PageForDelete4', grant: Page.GRANT_PUBLIC, parent: pageIdForDelete1, status: Page.STATUS_PUBLISHED, isEmpty: true, }, { path: '/v5_PageForDelete3/v5_PageForDelete4/v5_PageForDelete5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDelete2, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDelete3, path: '/v5_PageForDelete6', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDelete4, path: '/user', grant: Page.GRANT_PUBLIC, parent: rootPage._id, status: Page.STATUS_PUBLISHED, isEmpty: true, }, { _id: pageIdForDelete5, path: '/user/v5DummyUser1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDelete4, status: Page.STATUS_PUBLISHED, }, ]); const tagIdForDelete1 = new mongoose.Types.ObjectId(); const tagIdForDelete2 = new mongoose.Types.ObjectId(); await Tag.insertMany([ { _id: tagIdForDelete1, name: 'TagForDelete1' }, { _id: tagIdForDelete2, name: 'TagForDelete2' }, ]); await PageTagRelation.insertMany([ { relatedPage: pageIdForDelete3, relatedTag: tagIdForDelete1 }, { relatedPage: pageIdForDelete3, relatedTag: tagIdForDelete2 }, ]); /** * Delete completely */ const pageIdForDeleteCompletely1 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely2 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely3 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely4 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely5 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely6 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely7 = new mongoose.Types.ObjectId(); const pageIdForDeleteCompletely8 = new mongoose.Types.ObjectId(); const revisionIdForDeleteCompletely1 = new mongoose.Types.ObjectId(); const revisionIdForDeleteCompletely2 = new mongoose.Types.ObjectId(); const revisionIdForDeleteCompletely3 = new mongoose.Types.ObjectId(); const revisionIdForDeleteCompletely4 = new mongoose.Types.ObjectId(); await Page.insertMany([ { _id: pageIdForDeleteCompletely1, path: '/v5_PageForDeleteCompletely1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDeleteCompletely2, path: '/v5_PageForDeleteCompletely2', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDeleteCompletely3, path: '/v5_PageForDeleteCompletely2/v5_PageForDeleteCompletely3', grant: Page.GRANT_PUBLIC, parent: pageIdForDeleteCompletely2, status: Page.STATUS_PUBLISHED, isEmpty: true, }, { _id: pageIdForDeleteCompletely4, path: '/v5_PageForDeleteCompletely2/v5_PageForDeleteCompletely3/v5_PageForDeleteCompletely4', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDeleteCompletely3, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDeleteCompletely5, path: '/trash/v5_PageForDeleteCompletely5', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_DELETED, }, { _id: pageIdForDeleteCompletely6, path: '/v5_PageForDeleteCompletely6', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: rootPage._id, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDeleteCompletely7, path: '/v5_PageForDeleteCompletely6/v5_PageForDeleteCompletely7', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDeleteCompletely6, status: Page.STATUS_PUBLISHED, }, { _id: pageIdForDeleteCompletely8, path: '/v5_PageForDeleteCompletely6/v5_PageForDeleteCompletely7/v5_PageForDeleteCompletely8', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, parent: pageIdForDeleteCompletely7, status: Page.STATUS_PUBLISHED, }, ]); await Revision.insertMany([ { _id: revisionIdForDeleteCompletely1, format: 'markdown', pageId: pageIdForDeleteCompletely2, body: 'pageIdForDeleteCompletely2', }, { _id: revisionIdForDeleteCompletely2, format: 'markdown', pageId: pageIdForDeleteCompletely4, body: 'pageIdForDeleteCompletely4', }, { _id: revisionIdForDeleteCompletely3, format: 'markdown', pageId: pageIdForDeleteCompletely5, body: 'pageIdForDeleteCompletely5', }, { _id: revisionIdForDeleteCompletely4, format: 'markdown', pageId: pageIdForDeleteCompletely2, body: 'comment_pageIdForDeleteCompletely3', }, ]); const tagForDeleteCompletely1 = new mongoose.Types.ObjectId(); const tagForDeleteCompletely2 = new mongoose.Types.ObjectId(); await Tag.insertMany([ { name: 'TagForDeleteCompletely1' }, { name: 'TagForDeleteCompletely2' }, ]); await PageTagRelation.insertMany([ { relatedPage: pageIdForDeleteCompletely2, relatedTag: tagForDeleteCompletely1 }, { relatedPage: pageIdForDeleteCompletely4, relatedTag: tagForDeleteCompletely2 }, ]); await Bookmark.insertMany([ { page: pageIdForDeleteCompletely2, user: dummyUser1._id, }, { page: pageIdForDeleteCompletely2, user: dummyUser2._id, }, ]); await Comment.insertMany([ { commentPosition: -1, isMarkdown: true, page: pageIdForDeleteCompletely2, creator: dummyUser1._id, revision: revisionIdForDeleteCompletely4, comment: 'comment_ForDeleteCompletely4', }, ]); await PageRedirect.insertMany([ { fromPath: '/from/v5_PageForDeleteCompletely2', toPath: '/v5_PageForDeleteCompletely2', }, { fromPath: '/from/v5_PageForDeleteCompletely2/v5_PageForDeleteCompletely3/v5_PageForDeleteCompletely4', toPath: '/v5_PageForDeleteCompletely2/v5_PageForDeleteCompletely3/v5_PageForDeleteCompletely4', }, ]); await ShareLink.insertMany([ { relatedPage: pageIdForDeleteCompletely2, expiredAt: null, description: 'sharlink_v5PageForDeleteCompletely2', }, { relatedPage: pageIdForDeleteCompletely4, expiredAt: null, description: 'sharlink_v5PageForDeleteCompletely4', }, ]); /** * Revert */ const pageIdForRevert1 = new mongoose.Types.ObjectId(); const pageIdForRevert2 = new mongoose.Types.ObjectId(); const pageIdForRevert3 = new mongoose.Types.ObjectId(); const revisionIdForRevert1 = new mongoose.Types.ObjectId(); const revisionIdForRevert2 = new mongoose.Types.ObjectId(); const revisionIdForRevert3 = new mongoose.Types.ObjectId(); await Page.insertMany([ { _id: pageIdForRevert1, path: '/trash/v5_revert1', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, revision: revisionIdForRevert1, status: Page.STATUS_DELETED, }, { _id: pageIdForRevert2, path: '/trash/v5_revert2', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, revision: revisionIdForRevert2, status: Page.STATUS_DELETED, }, { _id: pageIdForRevert3, path: '/trash/v5_revert2/v5_revert3/v5_revert4', grant: Page.GRANT_PUBLIC, creator: dummyUser1, lastUpdateUser: dummyUser1._id, revision: revisionIdForRevert3, status: Page.STATUS_DELETED, }, ]); await Revision.insertMany([ { _id: revisionIdForRevert1, pageId: pageIdForRevert1, body: 'revert1', format: 'comment', author: dummyUser1, }, { _id: revisionIdForRevert2, pageId: pageIdForRevert2, body: 'revert2', format: 'comment', author: dummyUser1, }, { _id: revisionIdForRevert3, pageId: pageIdForRevert3, body: 'revert3', format: 'comment', author: dummyUser1, }, ]); const tagIdRevert1 = new mongoose.Types.ObjectId(); await Tag.insertMany([ { _id: tagIdRevert1, name: 'revertTag1' }, ]); await PageTagRelation.insertMany([ { relatedPage: pageIdForRevert1, relatedTag: tagIdRevert1, isPageTrashed: true, }, ]); }); describe('create', () => { test('Should create single page', async() => { const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized'); const page = await crowi.pageService.create('/v5_create1', 'create1', dummyUser1, {}); expect(page).toBeTruthy(); expect(page.parent).toStrictEqual(rootPage._id); // isGrantNormalized is called when GRANT PUBLIC expect(isGrantNormalizedSpy).toBeCalledTimes(1); }); test('Should create empty-child and non-empty grandchild', async() => { const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized'); const grandchildPage = await crowi.pageService.create('/v5_empty_create2/v5_create_3', 'grandchild', dummyUser1, {}); const childPage = await Page.findOne({ path: '/v5_empty_create2' }); expect(childPage.isEmpty).toBe(true); expect(grandchildPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(childPage.parent).toStrictEqual(rootPage._id); expect(grandchildPage.parent).toStrictEqual(childPage._id); // isGrantNormalized is called when GRANT PUBLIC expect(isGrantNormalizedSpy).toBeCalledTimes(1); }); test('Should create on empty page', async() => { const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized'); const beforeCreatePage = await Page.findOne({ path: '/v5_empty_create_4' }); expect(beforeCreatePage.isEmpty).toBe(true); const childPage = await crowi.pageService.create('/v5_empty_create_4', 'body', dummyUser1, {}); const grandchildPage = await Page.findOne({ parent: childPage._id }); expect(childPage).toBeTruthy(); expect(childPage.isEmpty).toBe(false); expect(childPage.revision.body).toBe('body'); expect(grandchildPage).toBeTruthy(); expect(childPage.parent).toStrictEqual(rootPage._id); expect(grandchildPage.parent).toStrictEqual(childPage._id); // isGrantNormalized is called when GRANT PUBLIC expect(isGrantNormalizedSpy).toBeCalledTimes(1); }); }); describe('create by system', () => { test('Should create single page by system', async() => { const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized'); const page = await crowi.pageService.forceCreateBySystem('/v5_create_by_system1', 'create_by_system1', {}); expect(page).toBeTruthy(); expect(page.parent).toStrictEqual(rootPage._id); // isGrantNormalized is not called when create by system expect(isGrantNormalizedSpy).toBeCalledTimes(0); }); test('Should create empty-child and non-empty grandchild', async() => { const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized'); const grandchildPage = await crowi.pageService.forceCreateBySystem('/v5_empty_create_by_system2/v5_create_by_system3', 'grandchild', {}); const childPage = await Page.findOne({ path: '/v5_empty_create_by_system2' }); expect(childPage.isEmpty).toBe(true); expect(grandchildPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(childPage.parent).toStrictEqual(rootPage._id); expect(grandchildPage.parent).toStrictEqual(childPage._id); // isGrantNormalized is not called when create by system expect(isGrantNormalizedSpy).toBeCalledTimes(0); }); test('Should create on empty page', async() => { const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized'); const beforeCreatePage = await Page.findOne({ path: '/v5_empty_create_by_system4' }); expect(beforeCreatePage.isEmpty).toBe(true); const childPage = await crowi.pageService.forceCreateBySystem('/v5_empty_create_by_system4', 'body', {}); const grandchildPage = await Page.findOne({ parent: childPage._id }); expect(childPage).toBeTruthy(); expect(childPage.isEmpty).toBe(false); expect(childPage.revision.body).toBe('body'); expect(grandchildPage).toBeTruthy(); expect(childPage.parent).toStrictEqual(rootPage._id); expect(grandchildPage.parent).toStrictEqual(childPage._id); // isGrantNormalized is not called when create by system expect(isGrantNormalizedSpy).toBeCalledTimes(0); }); }); describe('Rename', () => { const renamePage = async(page, newPagePath, user, options, activityParameters?) => { // mock return value const mockedRenameSubOperation = jest.spyOn(crowi.pageService, 'renameSubOperation').mockReturnValue(null); const renamedPage = await crowi.pageService.renamePage(page, newPagePath, user, options, activityParameters); // retrieve the arguments passed when calling method renameSubOperation inside renamePage method const argsForRenameSubOperation = mockedRenameSubOperation.mock.calls[0]; // restores the original implementation mockedRenameSubOperation.mockRestore(); // rename descendants await crowi.pageService.renameSubOperation(...argsForRenameSubOperation); return renamedPage; }; /** * This function only execute renameMainOperation. renameSubOperation is basically omitted(only return null) */ const renameMainOperation = async(page, newPagePath, user, options, activityParameters?) => { // create page operation from target page const pageOp = await PageOperation.create({ actionType: PageActionType.Rename, actionStage: PageActionStage.Main, page, user, fromPath: page.path, toPath: newPagePath, options, }); // mock return value const mockedRenameSubOperation = jest.spyOn(crowi.pageService, 'renameSubOperation').mockReturnValue(null); const renamedPage = await crowi.pageService.renameMainOperation(page, newPagePath, user, options, pageOp._id, activityParameters); // restores the original implementation mockedRenameSubOperation.mockRestore(); return renamedPage; }; test('Should NOT rename top page', async() => { expect(rootPage).toBeTruthy(); let isThrown = false; try { await crowi.pageService.renamePage(rootPage, '/new_root', dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); } catch (err) { isThrown = true; } expect(isThrown).toBe(true); }); test('Should rename/move to under non-empty page', async() => { const parentPage = await Page.findOne({ path: '/v5_ParentForRename1' }); const childPage = await Page.findOne({ path: '/v5_ChildForRename1' }); expect(childPage).toBeTruthy(); expect(parentPage).toBeTruthy(); const newPath = '/v5_ParentForRename1/renamedChildForRename1'; const renamedPage = await renamePage(childPage, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const childPageBeforeRename = await Page.findOne({ path: '/v5_ChildForRename1' }); expect(xssSpy).toHaveBeenCalled(); expect(renamedPage.path).toBe(newPath); expect(renamedPage.parent).toStrictEqual(parentPage._id); expect(childPageBeforeRename).toBeNull(); }); test('Should rename/move to under empty page', async() => { const parentPage = await Page.findOne({ path: '/v5_ParentForRename2' }); const childPage = await Page.findOne({ path: '/v5_ChildForRename2' }); expect(childPage).toBeTruthy(); expect(parentPage).toBeTruthy(); expect(parentPage.isEmpty).toBe(true); const newPath = '/v5_ParentForRename2/renamedChildForRename2'; const renamedPage = await renamePage(childPage, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const childPageBeforeRename = await Page.findOne({ path: '/v5_ChildForRename2' }); expect(xssSpy).toHaveBeenCalled(); expect(renamedPage.path).toBe(newPath); expect(parentPage.isEmpty).toBe(true); expect(renamedPage.parent).toStrictEqual(parentPage._id); expect(childPageBeforeRename).toBeNull(); }); test('Should rename/move with option updateMetadata: true', async() => { const parentPage = await Page.findOne({ path: '/v5_ParentForRename3' }); const childPage = await Page.findOne({ path: '/v5_ChildForRename3' }); expect(childPage).toBeTruthy(); expect(parentPage).toBeTruthy(); expect(childPage.lastUpdateUser).toStrictEqual(dummyUser1._id); const newPath = '/v5_ParentForRename3/renamedChildForRename3'; const oldUpdateAt = childPage.updatedAt; const renamedPage = await renamePage(childPage, newPath, dummyUser2, { updateMetadata: true }, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); expect(xssSpy).toHaveBeenCalled(); expect(renamedPage.path).toBe(newPath); expect(renamedPage.parent).toStrictEqual(parentPage._id); expect(renamedPage.lastUpdateUser).toStrictEqual(dummyUser2._id); expect(renamedPage.updatedAt.getFullYear()).toBeGreaterThan(oldUpdateAt.getFullYear()); }); test('Should move with option createRedirectPage: true', async() => { const parentPage = await Page.findOne({ path: '/v5_ParentForRename4' }); const childPage = await Page.findOne({ path: '/v5_ChildForRename4' }); expect(parentPage).toBeTruthy(); expect(childPage).toBeTruthy(); const oldPath = childPage.path; const newPath = '/v5_ParentForRename4/renamedChildForRename4'; const renamedPage = await renamePage(childPage, newPath, dummyUser2, { createRedirectPage: true }, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const pageRedirect = await PageRedirect.findOne({ fromPath: oldPath, toPath: renamedPage.path }); expect(xssSpy).toHaveBeenCalled(); expect(renamedPage.path).toBe(newPath); expect(renamedPage.parent).toStrictEqual(parentPage._id); expect(pageRedirect).toBeTruthy(); }); test('Should rename/move with descendants', async() => { const parentPage = await Page.findOne({ path: '/v5_ParentForRename5' }); const childPage = await Page.findOne({ path: '/v5_ChildForRename5' }); const grandchild = await Page.findOne({ parent: childPage._id, path: '/v5_ChildForRename5/v5_GrandchildForRename5' }); expect(parentPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(grandchild).toBeTruthy(); const newPath = '/v5_ParentForRename5/renamedChildForRename5'; const renamedPage = await renamePage(childPage, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); // find child of renamed page const renamedGrandchild = await Page.findOne({ parent: renamedPage._id }); const childPageBeforeRename = await Page.findOne({ path: '/v5_ChildForRename5' }); const grandchildBeforeRename = await Page.findOne({ path: grandchild.path }); expect(xssSpy).toHaveBeenCalled(); expect(renamedPage.path).toBe(newPath); expect(renamedPage.parent).toStrictEqual(parentPage._id); expect(childPageBeforeRename).toBeNull(); expect(grandchildBeforeRename).toBeNull(); // grandchild's parent should be the renamed page expect(renamedGrandchild.parent).toStrictEqual(renamedPage._id); expect(renamedGrandchild.path).toBe('/v5_ParentForRename5/renamedChildForRename5/v5_GrandchildForRename5'); }); test('Should rename/move empty page', async() => { const parentPage = await Page.findOne({ path: '/v5_ParentForRename7' }); const childPage = await Page.findOne({ path: '/v5_ChildForRename7', isEmpty: true }); const grandchild = await Page.findOne({ parent: childPage._id, path: '/v5_ChildForRename7/v5_GrandchildForRename7' }); expect(parentPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(grandchild).toBeTruthy(); const newPath = '/v5_ParentForRename7/renamedChildForRename7'; const renamedPage = await renamePage(childPage, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const grandchildAfterRename = await Page.findOne({ parent: renamedPage._id }); const grandchildBeforeRename = await Page.findOne({ path: '/v5_ChildForRename7/v5_GrandchildForRename7' }); expect(xssSpy).toHaveBeenCalled(); expect(renamedPage.path).toBe(newPath); expect(renamedPage.isEmpty).toBe(true); expect(renamedPage.parent).toStrictEqual(parentPage._id); expect(grandchildBeforeRename).toBeNull(); // grandchild's parent should be renamed page expect(grandchildAfterRename.parent).toStrictEqual(renamedPage._id); expect(grandchildAfterRename.path).toBe('/v5_ParentForRename7/renamedChildForRename7/v5_GrandchildForRename7'); }); test('Should NOT rename/move with existing path', async() => { const page = await Page.findOne({ path: '/v5_ParentForRename8' }); expect(page).toBeTruthy(); const newPath = '/v5_ParentForRename9'; let isThrown; try { await renamePage(page, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); } catch (err) { isThrown = true; } expect(isThrown).toBe(true); }); test('Rename non-empty page path to its descendant non-empty page path', async() => { const initialPathForPage1 = '/v5_pageForRename17'; const initialPathForPage2 = '/v5_pageForRename17/v5_pageForRename18'; const page1 = await Page.findOne({ path: initialPathForPage1, isEmpty: false }); const page2 = await Page.findOne({ path: initialPathForPage2, isEmpty: false, parent: page1._id }); expect(page1).toBeTruthy(); expect(page2).toBeTruthy(); const newParentalPath = '/v5_pageForRename17/v5_pageForRename18'; const newPath = newParentalPath + page1.path; await renamePage(page1, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const renamedPage = await Page.findOne({ path: newParentalPath + initialPathForPage1 }); const renamedPageChild = await Page.findOne({ path: newParentalPath + initialPathForPage2 }); const newlyCreatedEmptyPage1 = await Page.findOne({ path: '/v5_pageForRename17' }); const newlyCreatedEmptyPage2 = await Page.findOne({ path: '/v5_pageForRename17/v5_pageForRename18' }); expect(renamedPage).toBeTruthy(); expect(renamedPageChild).toBeTruthy(); expect(newlyCreatedEmptyPage1).toBeTruthy(); expect(newlyCreatedEmptyPage2).toBeTruthy(); // check parent expect(newlyCreatedEmptyPage1.parent).toStrictEqual(rootPage._id); expect(newlyCreatedEmptyPage2.parent).toStrictEqual(newlyCreatedEmptyPage1._id); expect(renamedPage.parent).toStrictEqual(newlyCreatedEmptyPage2._id); expect(renamedPageChild.parent).toStrictEqual(renamedPage._id); // check isEmpty expect(newlyCreatedEmptyPage1.isEmpty).toBeTruthy(); expect(newlyCreatedEmptyPage2.isEmpty).toBeTruthy(); expect(renamedPage.isEmpty).toBe(false); expect(renamedPageChild.isEmpty).toBe(false); }); test('Rename empty page path to its descendant non-empty page path', async() => { const initialPathForPage1 = '/v5_pageForRename19'; const initialPathForPage2 = '/v5_pageForRename19/v5_pageForRename20'; const page1 = await Page.findOne({ path: initialPathForPage1, isEmpty: true }); const page2 = await Page.findOne({ path: initialPathForPage2, isEmpty: false, parent: page1._id }); expect(page1).toBeTruthy(); expect(page2).toBeTruthy(); const newParentalPath = '/v5_pageForRename19/v5_pageForRename20'; const newPath = newParentalPath + page1.path; await renamePage(page1, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const renamedPage = await Page.findOne({ path: newParentalPath + initialPathForPage1 }); const renamedPageChild = await Page.findOne({ path: newParentalPath + initialPathForPage2 }); const newlyCreatedEmptyPage1 = await Page.findOne({ path: '/v5_pageForRename19' }); const newlyCreatedEmptyPage2 = await Page.findOne({ path: '/v5_pageForRename19/v5_pageForRename20' }); expect(renamedPage).toBeTruthy(); expect(renamedPageChild).toBeTruthy(); expect(newlyCreatedEmptyPage1).toBeTruthy(); expect(newlyCreatedEmptyPage2).toBeTruthy(); // check parent expect(newlyCreatedEmptyPage1.parent).toStrictEqual(rootPage._id); expect(newlyCreatedEmptyPage2.parent).toStrictEqual(newlyCreatedEmptyPage1._id); expect(renamedPage.parent).toStrictEqual(newlyCreatedEmptyPage2._id); expect(renamedPageChild.parent).toStrictEqual(renamedPage._id); // check isEmpty expect(newlyCreatedEmptyPage1.isEmpty).toBeTruthy(); expect(newlyCreatedEmptyPage2.isEmpty).toBeTruthy(); expect(renamedPage.isEmpty).toBeTruthy(); expect(renamedPageChild.isEmpty).toBe(false); }); test('Rename the path of a non-empty page to its grandchild page path that has an empty parent', async() => { const initialPathForPage1 = '/v5_pageForRename21'; const initialPathForPage2 = '/v5_pageForRename21/v5_pageForRename22'; const initialPathForPage3 = '/v5_pageForRename21/v5_pageForRename22/v5_pageForRename23'; const page1 = await Page.findOne({ path: initialPathForPage1, isEmpty: false }); const page2 = await Page.findOne({ path: initialPathForPage2, isEmpty: true, parent: page1._id }); const page3 = await Page.findOne({ path: initialPathForPage3, isEmpty: false, parent: page2._id }); expect(page1).toBeTruthy(); expect(page2).toBeTruthy(); expect(page3).toBeTruthy(); const newParentalPath = '/v5_pageForRename21/v5_pageForRename22/v5_pageForRename23'; const newPath = newParentalPath + page1.path; await renamePage(page1, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const renamedPage = await Page.findOne({ path: newParentalPath + initialPathForPage1 }); const renamedPageChild = await Page.findOne({ path: newParentalPath + initialPathForPage2 }); const renamedPageGrandchild = await Page.findOne({ path: newParentalPath + initialPathForPage3 }); const newlyCreatedEmptyPage1 = await Page.findOne({ path: '/v5_pageForRename21' }); const newlyCreatedEmptyPage2 = await Page.findOne({ path: '/v5_pageForRename21/v5_pageForRename22' }); const newlyCreatedEmptyPage3 = await Page.findOne({ path: '/v5_pageForRename21/v5_pageForRename22/v5_pageForRename23' }); expect(renamedPage).toBeTruthy(); expect(renamedPageChild).toBeTruthy(); expect(renamedPageGrandchild).toBeTruthy(); expect(newlyCreatedEmptyPage1).toBeTruthy(); expect(newlyCreatedEmptyPage2).toBeTruthy(); expect(newlyCreatedEmptyPage3).toBeTruthy(); // check parent expect(newlyCreatedEmptyPage1.parent).toStrictEqual(rootPage._id); expect(newlyCreatedEmptyPage2.parent).toStrictEqual(newlyCreatedEmptyPage1._id); expect(newlyCreatedEmptyPage3.parent).toStrictEqual(newlyCreatedEmptyPage2._id); expect(renamedPage.parent).toStrictEqual(newlyCreatedEmptyPage3._id); expect(renamedPageChild.parent).toStrictEqual(renamedPage._id); expect(renamedPageGrandchild.parent).toStrictEqual(renamedPageChild._id); // check isEmpty expect(newlyCreatedEmptyPage1.isEmpty).toBeTruthy(); expect(newlyCreatedEmptyPage2.isEmpty).toBeTruthy(); expect(newlyCreatedEmptyPage3.isEmpty).toBeTruthy(); expect(renamedPage.isEmpty).toBe(false); expect(renamedPageChild.isEmpty).toBeTruthy(); expect(renamedPageGrandchild.isEmpty).toBe(false); }); test('should add 1 descendantCount to parent page in MainOperation', async() => { // paths before renaming const _path0 = '/v5_pageForRename24'; // out of renaming scope const _path1 = '/v5_pageForRename25'; // not renamed yet // paths after renaming const path0 = '/v5_pageForRename24'; const path1 = '/v5_pageForRename24/v5_pageForRename25'; // new path: same as path1 const newPath = '/v5_pageForRename24/v5_pageForRename25'; // pages const _page0 = await Page.findOne({ path: _path0 }); const _page1 = await Page.findOne({ path: _path1 }); expect(_page0).toBeTruthy(); expect(_page1).toBeTruthy(); expect(_page0.descendantCount).toBe(0); expect(_page1.descendantCount).toBe(0); await renameMainOperation(_page1, newPath, dummyUser1, {}); const page0 = await Page.findById(_page0._id); // new parent const page1 = await Page.findById(_page1._id); // renamed one expect(page0).toBeTruthy(); expect(page1).toBeTruthy(); expect(page0.path).toBe(path0); expect(page1.path).toBe(path1); // renamed expect(page0.descendantCount).toBe(1); // originally 0, +1 in Main. expect(page1.descendantCount).toBe(0); // cleanup await PageOperation.findOneAndDelete({ fromPath: _path1 }); }); test('should subtract 1 descendantCount from a new parent page in renameSubOperation', async() => { // paths before renaming const _path0 = '/v5_pageForRename29'; // out of renaming scope const _path1 = '/v5_pageForRename29/v5_pageForRename30'; // already renamed // paths after renaming const path0 = '/v5_pageForRename29'; const path1 = '/v5_pageForRename29/v5_pageForRename30'; // new path: same as path1 const newPath = '/v5_pageForRename29/v5_pageForRename30'; // page const _page0 = await Page.findOne({ path: _path0 }); const _page1 = await Page.findOne({ path: _path1 }); expect(_page0).toBeTruthy(); expect(_page1).toBeTruthy(); // page operation const fromPath = '/v5_pageForRename30'; const toPath = newPath; const pageOperation = await PageOperation.findOne({ _id: pageOpId1, fromPath, toPath, actionType: PageActionType.Rename, actionStage: PageActionStage.Sub, }); expect(pageOperation).toBeTruthy(); // descendantCount expect(_page0.descendantCount).toBe(1); expect(_page1.descendantCount).toBe(0); // renameSubOperation only await crowi.pageService.renameSubOperation(_page1, newPath, dummyUser1, {}, _page1, pageOperation._id, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', activityId: '62e291bc10e0ab61bd691794', }); // page const page0 = await Page.findById(_page0._id); // new parent const page1 = await Page.findById(_page1._id); // renamed one expect(page0).toBeTruthy(); expect(page1).toBeTruthy(); expect(page0.path).toBe(path0); expect(page1.path).toBe(path1); // renamed // descendantCount expect(page0.descendantCount).toBe(0); // originally 1, -1 in Sub. expect(page1.descendantCount).toBe(0); }); test(`should add 1 descendantCount to the a parent page in rename(Main)Operation and subtract 1 descendantCount from the the parent page in rename(Sub)Operation`, async() => { // paths before renaming const _path0 = '/v5_pageForRename26'; // out of renaming scope const _path1 = '/v5_pageForRename27'; // not renamed yet const _path2 = '/v5_pageForRename27/v5_pageForRename28'; // not renamed yet // paths after renaming const path0 = '/v5_pageForRename26'; const path1 = '/v5_pageForRename26/v5_pageForRename27'; const path2 = '/v5_pageForRename26/v5_pageForRename27/v5_pageForRename28'; // new path: same as path1 const newPath = '/v5_pageForRename26/v5_pageForRename27'; // page const _page0 = await Page.findOne({ path: _path0 }); const _page1 = await Page.findOne({ path: _path1 }); const _page2 = await Page.findOne({ path: _path2 }); expect(_page0).toBeTruthy(); expect(_page1).toBeTruthy(); expect(_page2).toBeTruthy(); expect(_page0.descendantCount).toBe(0); expect(_page1.descendantCount).toBe(1); expect(_page2.descendantCount).toBe(0); await renamePage(_page1, newPath, dummyUser1, {}, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/rename', }); const page0 = await Page.findById(_page0._id); // new parent const page1 = await Page.findById(_page1._id); // renamed const page2 = await Page.findById(_page2._id); // renamed expect(page0).toBeTruthy(); expect(page1).toBeTruthy(); expect(page2).toBeTruthy(); expect(page0.path).toBe(path0); expect(page1.path).toBe(path1); expect(page2.path).toBe(path2); expect(page0.descendantCount).toBe(2); // originally 0, +1 in Main, -1 in Sub, +2 for descendants. expect(page1.descendantCount).toBe(1); expect(page2.descendantCount).toBe(0); // cleanup await PageOperation.findOneAndDelete({ fromPath: _path1 }); }); }); describe('Duplicate', () => { const duplicate = async(page, newPagePath, user, isRecursively) => { // mock return value const mockedDuplicateRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'duplicateRecursivelyMainOperation').mockReturnValue(null); const duplicatedPage = await crowi.pageService.duplicate(page, newPagePath, user, isRecursively); // retrieve the arguments passed when calling method duplicateRecursivelyMainOperation inside duplicate method const argsForDuplicateRecursivelyMainOperation = mockedDuplicateRecursivelyMainOperation.mock.calls[0]; // restores the original implementation mockedDuplicateRecursivelyMainOperation.mockRestore(); // duplicate descendants if (isRecursively) { await crowi.pageService.duplicateRecursivelyMainOperation(...argsForDuplicateRecursivelyMainOperation); } return duplicatedPage; }; test('Should duplicate single page', async() => { const page = await Page.findOne({ path: '/v5_PageForDuplicate1' }); expect(page).toBeTruthy(); const newPagePath = '/duplicatedv5PageForDuplicate1'; const duplicatedPage = await duplicate(page, newPagePath, dummyUser1, false); const duplicatedRevision = await Revision.findOne({ pageId: duplicatedPage._id }); const baseRevision = await Revision.findOne({ pageId: page._id }); // new path expect(xssSpy).toHaveBeenCalled(); expect(duplicatedPage.path).toBe(newPagePath); expect(duplicatedPage._id).not.toStrictEqual(page._id); expect(duplicatedPage.revision).toStrictEqual(duplicatedRevision._id); expect(duplicatedRevision.body).toEqual(baseRevision.body); }); test('Should NOT duplicate single empty page', async() => { const page = await Page.findOne({ path: '/v5_PageForDuplicate2' }); expect(page).toBeTruthy(); let isThrown; let duplicatedPage; try { const newPagePath = '/duplicatedv5PageForDuplicate2'; duplicatedPage = await duplicate(page, newPagePath, dummyUser1, false); } catch (err) { isThrown = true; } expect(duplicatedPage).toBeUndefined(); expect(isThrown).toBe(true); }); test('Should duplicate multiple pages', async() => { const basePage = await Page.findOne({ path: '/v5_PageForDuplicate3' }); const revision = await Revision.findOne({ pageId: basePage._id }); const childPage1 = await Page.findOne({ path: '/v5_PageForDuplicate3/v5_Child_1_ForDuplicate3' }).populate({ path: 'revision', model: 'Revision' }); const childPage2 = await Page.findOne({ path: '/v5_PageForDuplicate3/v5_Child_2_ForDuplicate3' }).populate({ path: 'revision', model: 'Revision' }); const revisionForChild1 = childPage1.revision; const revisionForChild2 = childPage2.revision; expect(basePage).toBeTruthy(); expect(revision).toBeTruthy(); expect(childPage1).toBeTruthy(); expect(childPage2).toBeTruthy(); expect(revisionForChild1).toBeTruthy(); expect(revisionForChild2).toBeTruthy(); const newPagePath = '/duplicatedv5PageForDuplicate3'; const duplicatedPage = await duplicate(basePage, newPagePath, dummyUser1, true); const duplicatedChildPage1 = await Page.findOne({ parent: duplicatedPage._id, path: '/duplicatedv5PageForDuplicate3/v5_Child_1_ForDuplicate3' }) .populate({ path: 'revision', model: 'Revision' }); const duplicatedChildPage2 = await Page.findOne({ parent: duplicatedPage._id, path: '/duplicatedv5PageForDuplicate3/v5_Child_2_ForDuplicate3' }) .populate({ path: 'revision', model: 'Revision' }); const revisionForDuplicatedPage = await Revision.findOne({ pageId: duplicatedPage._id }); const revisionBodyForDupChild1 = duplicatedChildPage1.revision; const revisionBodyForDupChild2 = duplicatedChildPage2.revision; expect(duplicatedPage).toBeTruthy(); expect(duplicatedChildPage1).toBeTruthy(); expect(duplicatedChildPage2).toBeTruthy(); expect(revisionForDuplicatedPage).toBeTruthy(); expect(revisionBodyForDupChild1).toBeTruthy(); expect(revisionBodyForDupChild2).toBeTruthy(); expect(xssSpy).toHaveBeenCalled(); expect(duplicatedPage.path).toBe(newPagePath); expect(duplicatedChildPage1.path).toBe('/duplicatedv5PageForDuplicate3/v5_Child_1_ForDuplicate3'); expect(duplicatedChildPage2.path).toBe('/duplicatedv5PageForDuplicate3/v5_Child_2_ForDuplicate3'); }); test('Should duplicate multiple pages with empty child in it', async() => { const basePage = await Page.findOne({ path: '/v5_PageForDuplicate4' }); const baseChild = await Page.findOne({ parent: basePage._id, isEmpty: true }); const baseGrandchild = await Page.findOne({ parent: baseChild._id }); expect(basePage).toBeTruthy(); expect(baseChild).toBeTruthy(); expect(baseGrandchild).toBeTruthy(); const newPagePath = '/duplicatedv5PageForDuplicate4'; const duplicatedPage = await duplicate(basePage, newPagePath, dummyUser1, true); const duplicatedChild = await Page.findOne({ parent: duplicatedPage._id }); const duplicatedGrandchild = await Page.findOne({ parent: duplicatedChild._id }); expect(xssSpy).toHaveBeenCalled(); expect(duplicatedPage).toBeTruthy(); expect(duplicatedGrandchild).toBeTruthy(); expect(duplicatedPage.path).toBe(newPagePath); expect(duplicatedChild.path).toBe('/duplicatedv5PageForDuplicate4/v5_empty_PageForDuplicate4'); expect(duplicatedGrandchild.path).toBe('/duplicatedv5PageForDuplicate4/v5_empty_PageForDuplicate4/v5_grandchild_PageForDuplicate4'); expect(duplicatedChild.isEmpty).toBe(true); expect(duplicatedGrandchild.parent).toStrictEqual(duplicatedChild._id); expect(duplicatedChild.parent).toStrictEqual(duplicatedPage._id); }); test('Should duplicate tags', async() => { const basePage = await Page.findOne({ path: '/v5_PageForDuplicate5' }); const tag1 = await Tag.findOne({ name: 'duplicate_Tag1' }); const tag2 = await Tag.findOne({ name: 'duplicate_Tag2' }); const basePageTagRelation1 = await PageTagRelation.findOne({ relatedTag: tag1?._id }); const basePageTagRelation2 = await PageTagRelation.findOne({ relatedTag: tag2?._id }); expect(basePage).toBeTruthy(); expect(tag1).toBeTruthy(); expect(tag2).toBeTruthy(); expect(basePageTagRelation1).toBeTruthy(); expect(basePageTagRelation2).toBeTruthy(); const newPagePath = '/duplicatedv5PageForDuplicate5'; const duplicatedPage = await duplicate(basePage, newPagePath, dummyUser1, false); const duplicatedTagRelations = await PageTagRelation.find({ relatedPage: duplicatedPage._id }); expect(xssSpy).toHaveBeenCalled(); expect(duplicatedPage.path).toBe(newPagePath); expect(duplicatedTagRelations.length).toBeGreaterThanOrEqual(2); }); test('Should NOT duplicate comments', async() => { const basePage = await Page.findOne({ path: '/v5_PageForDuplicate6' }); const basePageComments = await Comment.find({ page: basePage._id }); expect(basePage).toBeTruthy(); expect(basePageComments.length).toBeGreaterThan(0); // length > 0 const newPagePath = '/duplicatedv5PageForDuplicate6'; const duplicatedPage = await duplicate(basePage, newPagePath, dummyUser1, false); const duplicatedComments = await Comment.find({ page: duplicatedPage._id }); expect(xssSpy).toHaveBeenCalled(); expect(duplicatedPage.path).toBe(newPagePath); expect(basePageComments.length).not.toBe(duplicatedComments.length); }); test('Should duplicate empty page with descendants', async() => { const basePage = await Page.findOne({ path: '/v5_empty_PageForDuplicate7' }); const basePageChild = await Page.findOne({ parent: basePage._id }).populate({ path: 'revision', model: 'Revision' }); const basePageGrandhild = await Page.findOne({ parent: basePageChild._id }).populate({ path: 'revision', model: 'Revision' }); expect(basePage).toBeTruthy(); expect(basePageChild).toBeTruthy(); expect(basePageGrandhild).toBeTruthy(); expect(basePageChild.revision).toBeTruthy(); expect(basePageGrandhild.revision).toBeTruthy(); const newPagePath = '/duplicatedv5EmptyPageForDuplicate7'; const duplicatedPage = await duplicate(basePage, newPagePath, dummyUser1, true); const duplicatedChild = await Page.findOne({ parent: duplicatedPage._id }).populate({ path: 'revision', model: 'Revision' }); const duplicatedGrandchild = await Page.findOne({ parent: duplicatedChild._id }).populate({ path: 'revision', model: 'Revision' }); expect(duplicatedPage).toBeTruthy(); expect(duplicatedChild).toBeTruthy(); expect(duplicatedGrandchild).toBeTruthy(); expect(duplicatedChild.revision).toBeTruthy(); expect(duplicatedGrandchild.revision).toBeTruthy(); expect(xssSpy).toHaveBeenCalled(); expect(duplicatedPage.path).toBe(newPagePath); expect(duplicatedPage.isEmpty).toBe(true); expect(duplicatedChild.revision.body).toBe(basePageChild.revision.body); expect(duplicatedGrandchild.revision.body).toBe(basePageGrandhild.revision.body); expect(duplicatedChild.path).toBe('/duplicatedv5EmptyPageForDuplicate7/v5_child_PageForDuplicate7'); expect(duplicatedGrandchild.path).toBe('/duplicatedv5EmptyPageForDuplicate7/v5_child_PageForDuplicate7/v5_grandchild_PageForDuplicate7'); expect(duplicatedGrandchild.parent).toStrictEqual(duplicatedChild._id); expect(duplicatedChild.parent).toStrictEqual(duplicatedPage._id); }); }); describe('Delete', () => { const deletePage = async(page, user, options, isRecursively, activityParameters?) => { const mockedDeleteRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'deleteRecursivelyMainOperation').mockReturnValue(null); const deletedPage = await crowi.pageService.deletePage(page, user, options, isRecursively, activityParameters); const argsForDeleteRecursivelyMainOperation = mockedDeleteRecursivelyMainOperation.mock.calls[0]; mockedDeleteRecursivelyMainOperation.mockRestore(); if (isRecursively) { await crowi.pageService.deleteRecursivelyMainOperation(...argsForDeleteRecursivelyMainOperation); } return deletedPage; }; test('Should NOT delete root page', async() => { let isThrown; expect(rootPage).toBeTruthy(); try { await deletePage(rootPage, dummyUser1, {}, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/delete', }); } catch (err) { isThrown = true } const page = await Page.findOne({ path: '/' }); expect(isThrown).toBe(true); expect(page).toBeTruthy(); }); test('Should NOT delete trashed page', async() => { const trashedPage = await Page.findOne({ path: '/trash/v5_PageForDelete1' }); expect(trashedPage).toBeTruthy(); let isThrown; try { await deletePage(trashedPage, dummyUser1, {}, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/delete', }); } catch (err) { isThrown = true } const page = await Page.findOne({ path: '/trash/v5_PageForDelete1' }); expect(page).toBeTruthy(); expect(isThrown).toBe(true); }); test('Should NOT delete /user/hoge page', async() => { const dummyUser1Page = await Page.findOne({ path: '/user/v5DummyUser1' }); expect(dummyUser1Page).toBeTruthy(); let isThrown; try { await deletePage(dummyUser1Page, dummyUser1, {}, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/delete', }); } catch (err) { isThrown = true } const page = await Page.findOne({ path: '/user/v5DummyUser1' }); expect(page).toBeTruthy(); expect(isThrown).toBe(true); }); test('Should delete single page', async() => { const pageToDelete = await Page.findOne({ path: '/v5_PageForDelete2' }); expect(pageToDelete).toBeTruthy(); const deletedPage = await deletePage(pageToDelete, dummyUser1, {}, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/delete', }); const page = await Page.findOne({ path: '/v5_PageForDelete2' }); expect(page).toBeNull(); expect(deletedPage.path).toBe(`/trash${pageToDelete.path}`); expect(deletedPage.parent).toBeNull(); expect(deletedPage.status).toBe(Page.STATUS_DELETED); }); test('Should delete multiple pages including empty child', async() => { const parentPage = await Page.findOne({ path: '/v5_PageForDelete3' }); const childPage = await Page.findOne({ path: '/v5_PageForDelete3/v5_PageForDelete4' }); const grandchildPage = await Page.findOne({ path: '/v5_PageForDelete3/v5_PageForDelete4/v5_PageForDelete5' }); expect(parentPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(grandchildPage).toBeTruthy(); const deletedParentPage = await deletePage(parentPage, dummyUser1, {}, true, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/delete', }); const deletedChildPage = await Page.findOne({ path: '/trash/v5_PageForDelete3/v5_PageForDelete4' }); const deletedGrandchildPage = await Page.findOne({ path: '/trash/v5_PageForDelete3/v5_PageForDelete4/v5_PageForDelete5' }); // originally NOT empty page should exist with status 'deleted' and parent set null expect(deletedParentPage._id).toStrictEqual(parentPage._id); expect(deletedParentPage.status).toBe(Page.STATUS_DELETED); expect(deletedParentPage.parent).toBeNull(); // originally empty page should NOT exist expect(deletedChildPage).toBeNull(); // originally NOT empty page should exist with status 'deleted' and parent set null expect(deletedGrandchildPage._id).toStrictEqual(grandchildPage._id); expect(deletedGrandchildPage.status).toBe(Page.STATUS_DELETED); expect(deletedGrandchildPage.parent).toBeNull(); }); test('Should delete page tag relation', async() => { const pageToDelete = await Page.findOne({ path: '/v5_PageForDelete6' }); const tag1 = await Tag.findOne({ name: 'TagForDelete1' }); const tag2 = await Tag.findOne({ name: 'TagForDelete2' }); const pageRelation1 = await PageTagRelation.findOne({ relatedTag: tag1?._id }); const pageRelation2 = await PageTagRelation.findOne({ relatedTag: tag2?._id }); expect(pageToDelete).toBeTruthy(); expect(tag1).toBeTruthy(); expect(tag2).toBeTruthy(); expect(pageRelation1).toBeTruthy(); expect(pageRelation2).toBeTruthy(); const deletedPage = await deletePage(pageToDelete, dummyUser1, {}, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/delete', }); const page = await Page.findOne({ path: '/v5_PageForDelete6' }); const deletedTagRelation1 = await PageTagRelation.findOne({ _id: pageRelation1._id }); const deletedTagRelation2 = await PageTagRelation.findOne({ _id: pageRelation2._id }); expect(page).toBe(null); expect(deletedPage.status).toBe(Page.STATUS_DELETED); expect(deletedTagRelation1.isPageTrashed).toBe(true); expect(deletedTagRelation2.isPageTrashed).toBe(true); }); }); describe('Delete completely', () => { const deleteCompletely = async(page, user, options = {}, isRecursively = false, preventEmitting = false, activityParameters?) => { const mockedDeleteCompletelyRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'deleteCompletelyRecursivelyMainOperation').mockReturnValue(null); await crowi.pageService.deleteCompletely(page, user, options, isRecursively, preventEmitting, activityParameters); const argsForDeleteCompletelyRecursivelyMainOperation = mockedDeleteCompletelyRecursivelyMainOperation.mock.calls[0]; mockedDeleteCompletelyRecursivelyMainOperation.mockRestore(); if (isRecursively) { await crowi.pageService.deleteCompletelyRecursivelyMainOperation(...argsForDeleteCompletelyRecursivelyMainOperation); } return; }; test('Should NOT completely delete root page', async() => { expect(rootPage).toBeTruthy(); let isThrown; try { await deleteCompletely(rootPage, dummyUser1, {}, false, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/deletecompletely', }); } catch (err) { isThrown = true } const page = await Page.findOne({ path: '/' }); expect(page).toBeTruthy(); expect(isThrown).toBe(true); }); test('Should completely delete single page', async() => { const page = await Page.findOne({ path: '/v5_PageForDeleteCompletely1' }); expect(page).toBeTruthy(); await deleteCompletely(page, dummyUser1, {}, false, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/deletecompletely', }); const deletedPage = await Page.findOne({ _id: page._id, path: '/v5_PageForDeleteCompletely1' }); expect(deletedPage).toBeNull(); }); test('Should completely delete multiple pages', async() => { const parentPage = await Page.findOne({ path: '/v5_PageForDeleteCompletely2' }); const childPage = await Page.findOne({ path: '/v5_PageForDeleteCompletely2/v5_PageForDeleteCompletely3' }); const grandchildPage = await Page.findOne({ path: '/v5_PageForDeleteCompletely2/v5_PageForDeleteCompletely3/v5_PageForDeleteCompletely4' }); const tag1 = await Tag.findOne({ name: 'TagForDeleteCompletely1' }); const tag2 = await Tag.findOne({ name: 'TagForDeleteCompletely2' }); const pageTagRelation1 = await PageTagRelation.findOne({ relatedPage: parentPage._id }); const pageTagRelation2 = await PageTagRelation.findOne({ relatedPage: grandchildPage._id }); const bookmark = await Bookmark.findOne({ page: parentPage._id }); const comment = await Comment.findOne({ page: parentPage._id }); const pageRedirect1 = await PageRedirect.findOne({ toPath: parentPage.path }); const pageRedirect2 = await PageRedirect.findOne({ toPath: grandchildPage.path }); const shareLink1 = await ShareLink.findOne({ relatedPage: parentPage._id }); const shareLink2 = await ShareLink.findOne({ relatedPage: grandchildPage._id }); expect(parentPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(grandchildPage).toBeTruthy(); expect(tag1).toBeTruthy(); expect(tag2).toBeTruthy(); expect(pageTagRelation1).toBeTruthy(); expect(pageTagRelation2).toBeTruthy(); expect(bookmark).toBeTruthy(); expect(comment).toBeTruthy(); expect(pageRedirect1).toBeTruthy(); expect(pageRedirect2).toBeTruthy(); expect(shareLink1).toBeTruthy(); expect(shareLink2).toBeTruthy(); await deleteCompletely(parentPage, dummyUser1, {}, true, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/deletecompletely', }); const deletedPages = await Page.find({ _id: { $in: [parentPage._id, childPage._id, grandchildPage._id] } }); const deletedRevisions = await Revision.find({ pageId: { $in: [parentPage._id, grandchildPage._id] } }); const tags = await Tag.find({ _id: { $in: [tag1?._id, tag2?._id] } }); const deletedPageTagRelations = await PageTagRelation.find({ _id: { $in: [pageTagRelation1._id, pageTagRelation2._id] } }); const deletedBookmarks = await Bookmark.find({ _id: bookmark._id }); const deletedComments = await Comment.find({ _id: comment._id }); const deletedPageRedirects = await PageRedirect.find({ _id: { $in: [pageRedirect1._id, pageRedirect2._id] } }); const deletedShareLinks = await ShareLink.find({ _id: { $in: [shareLink1._id, shareLink2._id] } }); // page should be null expect(deletedPages.length).toBe(0); // revision should be null expect(deletedRevisions.length).toBe(0); // tag should be Truthy expect(tags).toBeTruthy(); // pageTagRelation should be null expect(deletedPageTagRelations.length).toBe(0); // bookmark should be null expect(deletedBookmarks.length).toBe(0); // comment should be null expect(deletedComments.length).toBe(0); // pageRedirect should be null expect(deletedPageRedirects.length).toBe(0); // sharelink should be null expect(deletedShareLinks.length).toBe(0); }); test('Should completely delete trashed page', async() => { const page = await Page.findOne({ path: '/trash/v5_PageForDeleteCompletely5' }); const revision = await Revision.findOne({ pageId: page._id }); expect(page).toBeTruthy(); expect(revision).toBeTruthy(); await deleteCompletely(page, dummyUser1, {}, false, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/deletecompletely', }); const deltedPage = await Page.findOne({ _id: page._id }); const deltedRevision = await Revision.findOne({ _id: revision._id }); expect(deltedPage).toBeNull(); expect(deltedRevision).toBeNull(); }); test('Should completely deleting page in the middle results in having an empty page', async() => { const parentPage = await Page.findOne({ path: '/v5_PageForDeleteCompletely6' }); const childPage = await Page.findOne({ path: '/v5_PageForDeleteCompletely6/v5_PageForDeleteCompletely7' }); const grandchildPage = await Page.findOne({ path: '/v5_PageForDeleteCompletely6/v5_PageForDeleteCompletely7/v5_PageForDeleteCompletely8' }); expect(parentPage).toBeTruthy(); expect(childPage).toBeTruthy(); expect(grandchildPage).toBeTruthy(); await deleteCompletely(childPage, dummyUser1, {}, false, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/deletecompletely', }); const parentPageAfterDelete = await Page.findOne({ path: '/v5_PageForDeleteCompletely6' }); const childPageAfterDelete = await Page.findOne({ path: '/v5_PageForDeleteCompletely6/v5_PageForDeleteCompletely7' }); const grandchildPageAfterDelete = await Page.findOne({ path: '/v5_PageForDeleteCompletely6/v5_PageForDeleteCompletely7/v5_PageForDeleteCompletely8' }); const childOfDeletedPage = await Page.findOne({ parent: childPageAfterDelete._id }); expect(parentPageAfterDelete).toBeTruthy(); expect(childPageAfterDelete).toBeTruthy(); expect(grandchildPageAfterDelete).toBeTruthy(); expect(childPageAfterDelete._id).not.toStrictEqual(childPage._id); expect(childPageAfterDelete.isEmpty).toBe(true); expect(childPageAfterDelete.parent).toStrictEqual(parentPage._id); expect(childOfDeletedPage._id).toStrictEqual(grandchildPage._id); }); }); describe('revert', () => { const revertDeletedPage = async(page, user, options = {}, isRecursively = false, activityParameters?) => { // mock return value const mockedRevertRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'revertRecursivelyMainOperation').mockReturnValue(null); const revertedPage = await crowi.pageService.revertDeletedPage(page, user, options, isRecursively, activityParameters); const argsForRecursivelyMainOperation = mockedRevertRecursivelyMainOperation.mock.calls[0]; // restores the original implementation mockedRevertRecursivelyMainOperation.mockRestore(); if (isRecursively) { await crowi.pageService.revertRecursivelyMainOperation(...argsForRecursivelyMainOperation); } return revertedPage; }; test('revert single deleted page', async() => { const deletedPage = await Page.findOne({ path: '/trash/v5_revert1', status: Page.STATUS_DELETED }); const revision = await Revision.findOne({ pageId: deletedPage._id }); const tag = await Tag.findOne({ name: 'revertTag1' }); const deletedPageTagRelation = await PageTagRelation.findOne({ relatedPage: deletedPage._id, relatedTag: tag?._id, isPageTrashed: true }); expect(deletedPage).toBeTruthy(); expect(revision).toBeTruthy(); expect(tag).toBeTruthy(); expect(deletedPageTagRelation).toBeTruthy(); const revertedPage = await revertDeletedPage(deletedPage, dummyUser1, {}, false, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/revert', }); const pageTagRelation = await PageTagRelation.findOne({ relatedPage: deletedPage._id, relatedTag: tag?._id }); expect(revertedPage.parent).toStrictEqual(rootPage._id); expect(revertedPage.path).toBe('/v5_revert1'); expect(revertedPage.status).toBe(Page.STATUS_PUBLISHED); expect(pageTagRelation.isPageTrashed).toBe(false); }); test('revert multiple deleted page (has non existent page in the middle)', async() => { const deletedPage1 = await Page.findOne({ path: '/trash/v5_revert2', status: Page.STATUS_DELETED }); const deletedPage2 = await Page.findOne({ path: '/trash/v5_revert2/v5_revert3/v5_revert4', status: Page.STATUS_DELETED }); const revision1 = await Revision.findOne({ pageId: deletedPage1._id }); const revision2 = await Revision.findOne({ pageId: deletedPage2._id }); expect(deletedPage1).toBeTruthy(); expect(deletedPage2).toBeTruthy(); expect(revision1).toBeTruthy(); expect(revision2).toBeTruthy(); const revertedPage1 = await revertDeletedPage(deletedPage1, dummyUser1, {}, true, { ip: '::ffff:127.0.0.1', endpoint: '/_api/v3/pages/revert', }); const revertedPage2 = await Page.findOne({ _id: deletedPage2._id }); const newlyCreatedPage = await Page.findOne({ path: '/v5_revert2/v5_revert3' }); expect(revertedPage1).toBeTruthy(); expect(revertedPage2).toBeTruthy(); expect(newlyCreatedPage).toBeTruthy(); expect(revertedPage1.parent).toStrictEqual(rootPage._id); expect(revertedPage1.path).toBe('/v5_revert2'); expect(revertedPage2.path).toBe('/v5_revert2/v5_revert3/v5_revert4'); expect(newlyCreatedPage.parent).toStrictEqual(revertedPage1._id); expect(revertedPage2.parent).toStrictEqual(newlyCreatedPage._id); expect(revertedPage1.status).toBe(Page.STATUS_PUBLISHED); expect(revertedPage2.status).toBe(Page.STATUS_PUBLISHED); expect(newlyCreatedPage.status).toBe(Page.STATUS_PUBLISHED); }); }); });