فهرست منبع

add tests for canDeleteCompletely

Futa Arai 2 سال پیش
والد
کامیت
cc2de3ab8f
2فایلهای تغییر یافته به همراه188 افزوده شده و 53 حذف شده
  1. 8 5
      apps/app/src/server/service/page.ts
  2. 180 48
      apps/app/test/integration/service/page.test.js

+ 8 - 5
apps/app/src/server/service/page.ts

@@ -20,7 +20,7 @@ import ExternalUserGroupRelation from '~/features/external-user-group/server/mod
 import { SupportedAction } from '~/interfaces/activity';
 import { SupportedAction } from '~/interfaces/activity';
 import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
 import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
 import {
 import {
-  PageDeleteConfigValue, IPageDeleteConfigValueToProcessValidation,
+  PageDeleteConfigValue, IPageDeleteConfigValueToProcessValidation, PageSingleDeleteCompConfigValue,
 } from '~/interfaces/page-delete-config';
 } from '~/interfaces/page-delete-config';
 import {
 import {
   type IPageOperationProcessInfo, type IPageOperationProcessData, PageActionStage, PageActionType,
   type IPageOperationProcessInfo, type IPageOperationProcessData, PageActionStage, PageActionType,
@@ -181,20 +181,23 @@ class PageService {
   async canDeleteCompletely(page: PageDocument, operator: any | null, isRecursively: boolean): Promise<boolean> {
   async canDeleteCompletely(page: PageDocument, operator: any | null, isRecursively: boolean): Promise<boolean> {
     if (operator == null || isTopPage(page.path) || isUsersTopPage(page.path)) return false;
     if (operator == null || isTopPage(page.path) || isUsersTopPage(page.path)) return false;
 
 
+    const pageCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority');
+    const pageRecursiveCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority');
     const isAllGroupMembershipRequiredForPageCompleteDeletion = this.crowi.configManager.getConfig(
     const isAllGroupMembershipRequiredForPageCompleteDeletion = this.crowi.configManager.getConfig(
       'crowi', 'security:isAllGroupMembershipRequiredForPageCompleteDeletion',
       'crowi', 'security:isAllGroupMembershipRequiredForPageCompleteDeletion',
     );
     );
 
 
-    if (isAllGroupMembershipRequiredForPageCompleteDeletion) {
+    const isAdmin = operator?.admin ?? false;
+    const isAuthor = operator?._id == null ? false : operator._id.equals(page.creator);
+    const isAdminOrAuthor = isAdmin || isAuthor;
+
+    if (!isAdminOrAuthor && pageCompleteDeletionAuthority === PageSingleDeleteCompConfigValue.Anyone && isAllGroupMembershipRequiredForPageCompleteDeletion) {
       const userRelatedGrantedGroups = await this.getUserRelatedGrantedGroups(page, operator);
       const userRelatedGrantedGroups = await this.getUserRelatedGrantedGroups(page, operator);
       if (userRelatedGrantedGroups.length !== page.grantedGroups.length) {
       if (userRelatedGrantedGroups.length !== page.grantedGroups.length) {
         return false;
         return false;
       }
       }
     }
     }
 
 
-    const pageCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageCompleteDeletionAuthority');
-    const pageRecursiveCompleteDeletionAuthority = this.crowi.configManager.getConfig('crowi', 'security:pageRecursiveCompleteDeletionAuthority');
-
     const [singleAuthority, recursiveAuthority] = prepareDeleteConfigValuesForCalc(pageCompleteDeletionAuthority, pageRecursiveCompleteDeletionAuthority);
     const [singleAuthority, recursiveAuthority] = prepareDeleteConfigValuesForCalc(pageCompleteDeletionAuthority, pageRecursiveCompleteDeletionAuthority);
 
 
     return this.canDeleteLogic(page.creator, operator, isRecursively, singleAuthority, recursiveAuthority);
     return this.canDeleteLogic(page.creator, operator, isRecursively, singleAuthority, recursiveAuthority);

+ 180 - 48
apps/app/test/integration/service/page.test.js

@@ -1,7 +1,12 @@
 /* eslint-disable no-unused-vars */
 /* eslint-disable no-unused-vars */
+import { GroupType } from '@growi/core';
 import { advanceTo } from 'jest-date-mock';
 import { advanceTo } from 'jest-date-mock';
 
 
+import { PageSingleDeleteCompConfigValue, PageRecursiveDeleteCompConfigValue } from '~/interfaces/page-delete-config';
 import Tag from '~/server/models/tag';
 import Tag from '~/server/models/tag';
+import UserGroup from '~/server/models/user-group';
+import UserGroupRelation from '~/server/models/user-group-relation';
+
 
 
 const mongoose = require('mongoose');
 const mongoose = require('mongoose');
 
 
@@ -12,6 +17,7 @@ let rootPage;
 let dummyUser1;
 let dummyUser1;
 let testUser1;
 let testUser1;
 let testUser2;
 let testUser2;
+let testUser3;
 let parentTag;
 let parentTag;
 let childTag;
 let childTag;
 
 
@@ -39,6 +45,7 @@ let parentForDelete2;
 
 
 let childForDelete;
 let childForDelete;
 
 
+let canDeleteCompletelyTestPage;
 let parentForDeleteCompletely;
 let parentForDeleteCompletely;
 
 
 let parentForRevert1;
 let parentForRevert1;
@@ -76,13 +83,47 @@ describe('PageService', () => {
     await User.insertMany([
     await User.insertMany([
       { name: 'someone1', username: 'someone1', email: 'someone1@example.com' },
       { name: 'someone1', username: 'someone1', email: 'someone1@example.com' },
       { name: 'someone2', username: 'someone2', email: 'someone2@example.com' },
       { name: 'someone2', username: 'someone2', email: 'someone2@example.com' },
+      { name: 'someone3', username: 'someone3', email: 'someone3@example.com' },
     ]);
     ]);
 
 
     testUser1 = await User.findOne({ username: 'someone1' });
     testUser1 = await User.findOne({ username: 'someone1' });
     testUser2 = await User.findOne({ username: 'someone2' });
     testUser2 = await User.findOne({ username: 'someone2' });
+    testUser3 = await User.findOne({ username: 'someone3' });
 
 
     dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
     dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
 
 
+    await UserGroup.insertMany([
+      {
+        name: 'userGroupForCanDeleteCompletelyTest1',
+        parent: null,
+      },
+      {
+        name: 'userGroupForCanDeleteCompletelyTest2',
+        parent: null,
+      },
+    ]);
+    const userGroupForCanDeleteCompletelyTest1 = await UserGroup.findOne({ name: 'userGroupForCanDeleteCompletelyTest1' });
+    const userGroupForCanDeleteCompletelyTest2 = await UserGroup.findOne({ name: 'userGroupForCanDeleteCompletelyTest2' });
+
+    await UserGroupRelation.insertMany([
+      {
+        relatedGroup: userGroupForCanDeleteCompletelyTest1._id,
+        relatedUser: testUser1._id,
+      },
+      {
+        relatedGroup: userGroupForCanDeleteCompletelyTest2._id,
+        relatedUser: testUser2._id,
+      },
+      {
+        relatedGroup: userGroupForCanDeleteCompletelyTest1._id,
+        relatedUser: testUser3._id,
+      },
+      {
+        relatedGroup: userGroupForCanDeleteCompletelyTest2._id,
+        relatedUser: testUser3._id,
+      },
+    ]);
+
     rootPage = await Page.findOne({ path: '/' });
     rootPage = await Page.findOne({ path: '/' });
 
 
     await Page.insertMany([
     await Page.insertMany([
@@ -170,6 +211,16 @@ describe('PageService', () => {
         creator: testUser1,
         creator: testUser1,
         lastUpdateUser: testUser1,
         lastUpdateUser: testUser1,
       },
       },
+      {
+        path: '/canDeleteCompletelyTestPage',
+        grant: Page.GRANT_USER_GROUP,
+        creator: testUser2,
+        grantedGroups: [
+          { item: userGroupForCanDeleteCompletelyTest1._id, type: GroupType.userGroup },
+          { item: userGroupForCanDeleteCompletelyTest2._id, type: GroupType.userGroup },
+        ],
+        lastUpdateUser: testUser1,
+      },
       {
       {
         path: '/parentForDuplicate',
         path: '/parentForDuplicate',
         grant: Page.GRANT_PUBLIC,
         grant: Page.GRANT_PUBLIC,
@@ -255,6 +306,7 @@ describe('PageService', () => {
     parentForDelete1 = await Page.findOne({ path: '/parentForDelete1' });
     parentForDelete1 = await Page.findOne({ path: '/parentForDelete1' });
     parentForDelete2 = await Page.findOne({ path: '/parentForDelete2' });
     parentForDelete2 = await Page.findOne({ path: '/parentForDelete2' });
 
 
+    canDeleteCompletelyTestPage = await Page.findOne({ path: '/canDeleteCompletelyTestPage' });
     parentForDeleteCompletely = await Page.findOne({ path: '/parentForDeleteCompletely' });
     parentForDeleteCompletely = await Page.findOne({ path: '/parentForDeleteCompletely' });
     parentForRevert1 = await Page.findOne({ path: '/trash/parentForRevert1' });
     parentForRevert1 = await Page.findOne({ path: '/trash/parentForRevert1' });
     parentForRevert2 = await Page.findOne({ path: '/trash/parentForRevert2' });
     parentForRevert2 = await Page.findOne({ path: '/trash/parentForRevert2' });
@@ -703,67 +755,147 @@ describe('PageService', () => {
   });
   });
 
 
   describe('delete page completely', () => {
   describe('delete page completely', () => {
-    let pageEventSpy;
-    let deleteCompletelyOperationSpy;
-    let deleteCompletelyDescendantsWithStreamSpy;
-
-    let deleteManyBookmarkSpy;
-    let deleteManyCommentSpy;
-    let deleteManyPageTagRelationSpy;
-    let deleteManyShareLinkSpy;
-    let deleteManyRevisionSpy;
-    let deleteManyPageSpy;
-    let removeAllAttachmentsSpy;
+    describe('canDeleteCompletely', () => {
+      describe(`when user is not admin or author,
+        pageCompleteDeletionAuthority is 'anyone',
+        user is not related to all granted groups,
+        and isAllGroupMembershipRequiredForPageCompleteDeletion is true`, () => {
+        beforeEach(async() => {
+          const config = {
+            'security:isAllGroupMembershipRequiredForPageCompleteDeletion': true,
+            'security:pageCompleteDeletionAuthority': PageSingleDeleteCompConfigValue.Anyone,
+            'security:pageRecursiveCompleteDeletionAuthority': PageRecursiveDeleteCompConfigValue.Anyone,
+          };
+          await crowi.configManager.updateConfigsInTheSameNamespace('crowi', config);
+        });
+
+        test('is not deletable', async() => {
+          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser1);
+          expect(isDeleteable).toBe(false);
+        });
+      });
 
 
-    beforeEach(async() => {
-      pageEventSpy = jest.spyOn(crowi.pageService.pageEvent, 'emit');
-      deleteCompletelyOperationSpy = jest.spyOn(crowi.pageService, 'deleteCompletelyOperation');
-      deleteCompletelyDescendantsWithStreamSpy = jest.spyOn(crowi.pageService, 'deleteCompletelyDescendantsWithStream').mockImplementation();
-
-      deleteManyBookmarkSpy = jest.spyOn(Bookmark, 'deleteMany').mockImplementation();
-      deleteManyCommentSpy = jest.spyOn(Comment, 'deleteMany').mockImplementation();
-      deleteManyPageTagRelationSpy = jest.spyOn(PageTagRelation, 'deleteMany').mockImplementation();
-      deleteManyShareLinkSpy = jest.spyOn(ShareLink, 'deleteMany').mockImplementation();
-      deleteManyRevisionSpy = jest.spyOn(Revision, 'deleteMany').mockImplementation();
-      deleteManyPageSpy = jest.spyOn(Page, 'deleteMany').mockImplementation();
-      removeAllAttachmentsSpy = jest.spyOn(crowi.attachmentService, 'removeAllAttachments').mockImplementation();
-    });
+      describe(`when user is not admin or author,
+        pageCompleteDeletionAuthority is 'anyone',
+        user is related to all granted groups,
+        and isAllGroupMembershipRequiredForPageCompleteDeletion is true`, () => {
+        beforeEach(async() => {
+          const config = {
+            'security:isAllGroupMembershipRequiredForPageCompleteDeletion': true,
+            'security:pageCompleteDeletionAuthority': PageSingleDeleteCompConfigValue.Anyone,
+            'security:pageRecursiveCompleteDeletionAuthority': PageRecursiveDeleteCompConfigValue.Anyone,
+          };
+          await crowi.configManager.updateConfigsInTheSameNamespace('crowi', config);
+        });
+
+        test('is not deletable', async() => {
+          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser3);
+          expect(isDeleteable).toBe(true);
+        });
+      });
 
 
-    test('deleteCompletelyOperation', async() => {
-      await crowi.pageService.deleteCompletelyOperation([parentForDeleteCompletely._id], [parentForDeleteCompletely.path], { });
+      describe(`when user is not admin or author,
+        pageCompleteDeletionAuthority is 'anyone',
+        user is not related to all granted groups,
+        and isAllGroupMembershipRequiredForPageCompleteDeletion is false`, () => {
+        beforeEach(async() => {
+          const config = {
+            'security:isAllGroupMembershipRequiredForPageCompleteDeletion': false,
+            'security:pageCompleteDeletionAuthority': PageSingleDeleteCompConfigValue.Anyone,
+            'security:pageRecursiveCompleteDeletionAuthority': PageRecursiveDeleteCompConfigValue.Anyone,
+          };
+          await crowi.configManager.updateConfigsInTheSameNamespace('crowi', config);
+        });
+
+        test('is deletable', async() => {
+          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser1);
+          expect(isDeleteable).toBe(true);
+        });
+      });
 
 
-      expect(deleteManyBookmarkSpy).toHaveBeenCalledWith({ page: { $in: [parentForDeleteCompletely._id] } });
-      expect(deleteManyCommentSpy).toHaveBeenCalledWith({ page: { $in: [parentForDeleteCompletely._id] } });
-      expect(deleteManyPageTagRelationSpy).toHaveBeenCalledWith({ relatedPage: { $in: [parentForDeleteCompletely._id] } });
-      expect(deleteManyShareLinkSpy).toHaveBeenCalledWith({ relatedPage: { $in: [parentForDeleteCompletely._id] } });
-      expect(deleteManyRevisionSpy).toHaveBeenCalledWith({ pageId: { $in: [parentForDeleteCompletely._id] } });
-      expect(deleteManyPageSpy).toHaveBeenCalledWith({ _id: { $in: [parentForDeleteCompletely._id] } });
-      expect(removeAllAttachmentsSpy).toHaveBeenCalled();
+      describe(`when user is author,
+        pageCompleteDeletionAuthority is 'anyone',
+        user is not related to all granted groups,
+        and isAllGroupMembershipRequiredForPageCompleteDeletion is true`, () => {
+        beforeEach(async() => {
+          const config = {
+            'security:isAllGroupMembershipRequiredForPageCompleteDeletion': false,
+            'security:pageCompleteDeletionAuthority': PageSingleDeleteCompConfigValue.Anyone,
+            'security:pageRecursiveCompleteDeletionAuthority': PageRecursiveDeleteCompConfigValue.Anyone,
+          };
+          await crowi.configManager.updateConfigsInTheSameNamespace('crowi', config);
+        });
+
+        test('is deletable', async() => {
+          const isDeleteable = await crowi.pageService.canDeleteCompletely(canDeleteCompletelyTestPage, testUser2);
+          expect(isDeleteable).toBe(true);
+        });
+      });
     });
     });
 
 
-    test('delete completely without options', async() => {
-      await crowi.pageService.deleteCompletely(parentForDeleteCompletely, testUser2, { }, false, false, {
-        ip: '::ffff:127.0.0.1',
-        endpoint: '/_api/v3/pages/deletecompletely',
+    describe('actual delete process', () => {
+      let pageEventSpy;
+      let deleteCompletelyOperationSpy;
+      let deleteCompletelyDescendantsWithStreamSpy;
+
+      let deleteManyBookmarkSpy;
+      let deleteManyCommentSpy;
+      let deleteManyPageTagRelationSpy;
+      let deleteManyShareLinkSpy;
+      let deleteManyRevisionSpy;
+      let deleteManyPageSpy;
+      let removeAllAttachmentsSpy;
+
+      beforeEach(async() => {
+        pageEventSpy = jest.spyOn(crowi.pageService.pageEvent, 'emit');
+        deleteCompletelyOperationSpy = jest.spyOn(crowi.pageService, 'deleteCompletelyOperation');
+        deleteCompletelyDescendantsWithStreamSpy = jest.spyOn(crowi.pageService, 'deleteCompletelyDescendantsWithStream').mockImplementation();
+
+        deleteManyBookmarkSpy = jest.spyOn(Bookmark, 'deleteMany').mockImplementation();
+        deleteManyCommentSpy = jest.spyOn(Comment, 'deleteMany').mockImplementation();
+        deleteManyPageTagRelationSpy = jest.spyOn(PageTagRelation, 'deleteMany').mockImplementation();
+        deleteManyShareLinkSpy = jest.spyOn(ShareLink, 'deleteMany').mockImplementation();
+        deleteManyRevisionSpy = jest.spyOn(Revision, 'deleteMany').mockImplementation();
+        deleteManyPageSpy = jest.spyOn(Page, 'deleteMany').mockImplementation();
+        removeAllAttachmentsSpy = jest.spyOn(crowi.attachmentService, 'removeAllAttachments').mockImplementation();
       });
       });
 
 
-      expect(deleteCompletelyOperationSpy).toHaveBeenCalled();
-      expect(deleteCompletelyDescendantsWithStreamSpy).not.toHaveBeenCalled();
+      test('deleteCompletelyOperation', async() => {
+        await crowi.pageService.deleteCompletelyOperation([parentForDeleteCompletely._id], [parentForDeleteCompletely.path], { });
 
 
-      expect(pageEventSpy).toHaveBeenCalledWith('deleteCompletely', parentForDeleteCompletely, testUser2);
-    });
+        expect(deleteManyBookmarkSpy).toHaveBeenCalledWith({ page: { $in: [parentForDeleteCompletely._id] } });
+        expect(deleteManyCommentSpy).toHaveBeenCalledWith({ page: { $in: [parentForDeleteCompletely._id] } });
+        expect(deleteManyPageTagRelationSpy).toHaveBeenCalledWith({ relatedPage: { $in: [parentForDeleteCompletely._id] } });
+        expect(deleteManyShareLinkSpy).toHaveBeenCalledWith({ relatedPage: { $in: [parentForDeleteCompletely._id] } });
+        expect(deleteManyRevisionSpy).toHaveBeenCalledWith({ pageId: { $in: [parentForDeleteCompletely._id] } });
+        expect(deleteManyPageSpy).toHaveBeenCalledWith({ _id: { $in: [parentForDeleteCompletely._id] } });
+        expect(removeAllAttachmentsSpy).toHaveBeenCalled();
+      });
 
 
+      test('delete completely without options', async() => {
+        await crowi.pageService.deleteCompletely(parentForDeleteCompletely, testUser2, { }, false, false, {
+          ip: '::ffff:127.0.0.1',
+          endpoint: '/_api/v3/pages/deletecompletely',
+        });
 
 
-    test('delete completely with isRecursively', async() => {
-      await crowi.pageService.deleteCompletely(parentForDeleteCompletely, testUser2, { }, true, false, {
-        ip: '::ffff:127.0.0.1',
-        endpoint: '/_api/v3/pages/deletecompletely',
+        expect(deleteCompletelyOperationSpy).toHaveBeenCalled();
+        expect(deleteCompletelyDescendantsWithStreamSpy).not.toHaveBeenCalled();
+
+        expect(pageEventSpy).toHaveBeenCalledWith('deleteCompletely', parentForDeleteCompletely, testUser2);
       });
       });
 
 
-      expect(deleteCompletelyOperationSpy).toHaveBeenCalled();
-      expect(deleteCompletelyDescendantsWithStreamSpy).toHaveBeenCalled();
 
 
-      expect(pageEventSpy).toHaveBeenCalledWith('deleteCompletely', parentForDeleteCompletely, testUser2);
+      test('delete completely with isRecursively', async() => {
+        await crowi.pageService.deleteCompletely(parentForDeleteCompletely, testUser2, { }, true, false, {
+          ip: '::ffff:127.0.0.1',
+          endpoint: '/_api/v3/pages/deletecompletely',
+        });
+
+        expect(deleteCompletelyOperationSpy).toHaveBeenCalled();
+        expect(deleteCompletelyDescendantsWithStreamSpy).toHaveBeenCalled();
+
+        expect(pageEventSpy).toHaveBeenCalledWith('deleteCompletely', parentForDeleteCompletely, testUser2);
+      });
     });
     });
   });
   });