Răsfoiți Sursa

Merge pull request #5696 from weseek/imprv/improve-test-for-updatepage

imprv: Adding tests for updating page's grant to GRANT_OWNER
Yohei Shiina 4 ani în urmă
părinte
comite
1e1a2490e2

+ 11 - 9
packages/app/src/server/models/page.ts

@@ -10,10 +10,10 @@ import mongoose, {
 import mongoosePaginate from 'mongoose-paginate-v2';
 import mongoosePaginate from 'mongoose-paginate-v2';
 import uniqueValidator from 'mongoose-unique-validator';
 import uniqueValidator from 'mongoose-unique-validator';
 
 
-
+import { IUserHasId } from '~/interfaces/user';
 import { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
 import { ObjectIdLike } from '~/server/interfaces/mongoose-utils';
 
 
-import { IPage } from '../../interfaces/page';
+import { IPage, IPageHasId } from '../../interfaces/page';
 import loggerFactory from '../../utils/logger';
 import loggerFactory from '../../utils/logger';
 import Crowi from '../crowi';
 import Crowi from '../crowi';
 
 
@@ -24,8 +24,6 @@ const { addTrailingSlash } = pathUtils;
 const { isTopPage, collectAncestorPaths } = pagePathUtils;
 const { isTopPage, collectAncestorPaths } = pagePathUtils;
 
 
 const logger = loggerFactory('growi:models:page');
 const logger = loggerFactory('growi:models:page');
-
-
 /*
 /*
  * define schema
  * define schema
  */
  */
@@ -38,7 +36,7 @@ const PAGE_GRANT_ERROR = 1;
 const STATUS_PUBLISHED = 'published';
 const STATUS_PUBLISHED = 'published';
 const STATUS_DELETED = 'deleted';
 const STATUS_DELETED = 'deleted';
 
 
-export interface PageDocument extends IPage, Document {}
+export interface PageDocument extends IPage, Document { }
 
 
 
 
 type TargetAndAncestorsResult = {
 type TargetAndAncestorsResult = {
@@ -736,7 +734,7 @@ schema.statics.findAncestorsChildrenByPathAndViewer = async function(path: strin
     .lean()
     .lean()
     .exec();
     .exec();
   // mark target
   // mark target
-  const pages = _pages.map((page: PageDocument & {isTarget?: boolean}) => {
+  const pages = _pages.map((page: PageDocument & { isTarget?: boolean }) => {
     if (page.path === path) {
     if (page.path === path) {
       page.isTarget = true;
       page.isTarget = true;
     }
     }
@@ -784,7 +782,7 @@ schema.statics.incrementDescendantCountOfPageIds = async function(pageIds: Objec
 /**
 /**
  * recount descendantCount of a page with the provided id and return it
  * recount descendantCount of a page with the provided id and return it
  */
  */
-schema.statics.recountDescendantCount = async function(id: ObjectIdLike):Promise<number> {
+schema.statics.recountDescendantCount = async function(id: ObjectIdLike): Promise<number> {
   const res = await this.aggregate(
   const res = await this.aggregate(
     [
     [
       {
       {
@@ -1093,11 +1091,15 @@ export default (crowi: Crowi): any => {
     return savedPage;
     return savedPage;
   };
   };
 
 
-  const shouldUseUpdatePageV4 = (grant:number, isV5Compatible:boolean, isOnTree:boolean): boolean => {
+  const shouldUseUpdatePageV4 = (grant: number, isV5Compatible: boolean, isOnTree: boolean): boolean => {
     const isRestricted = grant === GRANT_RESTRICTED;
     const isRestricted = grant === GRANT_RESTRICTED;
     return !isRestricted && (!isV5Compatible || !isOnTree);
     return !isRestricted && (!isV5Compatible || !isOnTree);
   };
   };
 
 
+  schema.statics.emitPageEventUpdate = (page: IPageHasId, user: IUserHasId): void => {
+    pageEvent.emit('update', page, user);
+  };
+
   schema.statics.updatePage = async function(pageData, body, previousBody, user, options = {}) {
   schema.statics.updatePage = async function(pageData, body, previousBody, user, options = {}) {
     if (crowi.configManager == null || crowi.pageGrantService == null || crowi.pageService == null) {
     if (crowi.configManager == null || crowi.pageGrantService == null || crowi.pageService == null) {
       throw Error('Crowi is not set up');
       throw Error('Crowi is not set up');
@@ -1167,7 +1169,7 @@ export default (crowi: Crowi): any => {
       savedPage = await this.syncRevisionToHackmd(savedPage);
       savedPage = await this.syncRevisionToHackmd(savedPage);
     }
     }
 
 
-    pageEvent.emit('update', savedPage, user);
+    this.emitPageEventUpdate(savedPage, user);
 
 
     // Update ex children's parent
     // Update ex children's parent
     if (!wasOnTree && shouldBeOnTree) {
     if (!wasOnTree && shouldBeOnTree) {

+ 233 - 0
packages/app/test/integration/models/v5.page.test.js

@@ -13,10 +13,19 @@ describe('Page', () => {
   let Comment;
   let Comment;
   let ShareLink;
   let ShareLink;
   let PageRedirect;
   let PageRedirect;
+  let UserGroup;
+  let UserGroupRelation;
   let xssSpy;
   let xssSpy;
 
 
   let rootPage;
   let rootPage;
   let dummyUser1;
   let dummyUser1;
+  let pModelUser1;
+  let pModelUser2;
+  let pModelUser3;
+  let groupIdIsolate;
+  let groupIdA;
+  let groupIdB;
+  let groupIdC;
 
 
   beforeAll(async() => {
   beforeAll(async() => {
     crowi = await getInstance();
     crowi = await getInstance();
@@ -32,11 +41,100 @@ describe('Page', () => {
     Comment = mongoose.model('Comment');
     Comment = mongoose.model('Comment');
     ShareLink = mongoose.model('ShareLink');
     ShareLink = mongoose.model('ShareLink');
     PageRedirect = mongoose.model('PageRedirect');
     PageRedirect = mongoose.model('PageRedirect');
+    UserGroup = mongoose.model('UserGroup');
+    UserGroupRelation = mongoose.model('UserGroupRelation');
 
 
     dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
     dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
 
 
     rootPage = await Page.findOne({ path: '/' });
     rootPage = await Page.findOne({ path: '/' });
 
 
+    const pModelUserId1 = new mongoose.Types.ObjectId();
+    const pModelUserId2 = new mongoose.Types.ObjectId();
+    const pModelUserId3 = new mongoose.Types.ObjectId();
+    await User.insertMany([
+      {
+        _id: pModelUserId1, name: 'pmodelUser1', username: 'pmodelUser1', email: 'pmodelUser1@example.com',
+      },
+      {
+        _id: pModelUserId2, name: 'pmodelUser2', username: 'pmodelUser2', email: 'pmodelUser2@example.com',
+      },
+      {
+        _id: pModelUserId3, name: 'pModelUser3', username: 'pModelUser3', email: 'pModelUser3@example.com',
+      },
+    ]);
+    pModelUser1 = await User.findOne({ _id: pModelUserId1 });
+    pModelUser2 = await User.findOne({ _id: pModelUserId2 });
+    pModelUser3 = await User.findOne({ _id: pModelUserId3 });
+
+
+    groupIdIsolate = new mongoose.Types.ObjectId();
+    groupIdA = new mongoose.Types.ObjectId();
+    groupIdB = new mongoose.Types.ObjectId();
+    groupIdC = new mongoose.Types.ObjectId();
+    await UserGroup.insertMany([
+      {
+        _id: groupIdIsolate,
+        name: 'pModel_groupIsolate',
+      },
+      {
+        _id: groupIdA,
+        name: 'pModel_groupA',
+      },
+      {
+        _id: groupIdB,
+        name: 'pModel_groupB',
+        parent: groupIdA,
+      },
+      {
+        _id: groupIdC,
+        name: 'pModel_groupC',
+        parent: groupIdB,
+      },
+    ]);
+
+    await UserGroupRelation.insertMany([
+      {
+        relatedGroup: groupIdIsolate,
+        relatedUser: pModelUserId1,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdIsolate,
+        relatedUser: pModelUserId2,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: pModelUserId1,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: pModelUserId2,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: pModelUserId3,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdB,
+        relatedUser: pModelUserId2,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdB,
+        relatedUser: pModelUserId3,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdC,
+        relatedUser: pModelUserId3,
+        createdAt: new Date(),
+      },
+    ]);
+
     const pageIdCreate1 = new mongoose.Types.ObjectId();
     const pageIdCreate1 = new mongoose.Types.ObjectId();
     const pageIdCreate2 = new mongoose.Types.ObjectId();
     const pageIdCreate2 = new mongoose.Types.ObjectId();
     const pageIdCreate3 = new mongoose.Types.ObjectId();
     const pageIdCreate3 = new mongoose.Types.ObjectId();
@@ -129,6 +227,7 @@ describe('Page', () => {
     const pageIdUpd10 = new mongoose.Types.ObjectId();
     const pageIdUpd10 = new mongoose.Types.ObjectId();
     const pageIdUpd11 = new mongoose.Types.ObjectId();
     const pageIdUpd11 = new mongoose.Types.ObjectId();
     const pageIdUpd12 = new mongoose.Types.ObjectId();
     const pageIdUpd12 = new mongoose.Types.ObjectId();
+    const pageIdUpd13 = new mongoose.Types.ObjectId();
 
 
     await Page.insertMany([
     await Page.insertMany([
       {
       {
@@ -255,6 +354,63 @@ describe('Page', () => {
         parent: rootPage._id,
         parent: rootPage._id,
         descendantCount: 1,
         descendantCount: 1,
       },
       },
+      {
+        path: '/mup19',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 0,
+      },
+      {
+        path: '/mup20',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        creator: pModelUserId1,
+        lastUpdateUser: pModelUserId1,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 0,
+      },
+      {
+        path: '/mup21',
+        grant: Page.GRANT_RESTRICTED,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        descendantCount: 0,
+      },
+      {
+        _id: pageIdUpd13,
+        path: '/mup22',
+        grant: Page.GRANT_PUBLIC,
+        creator: pModelUser1,
+        lastUpdateUser: pModelUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 1,
+      },
+      {
+        path: '/mup22/mup23',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        creator: pModelUserId1,
+        lastUpdateUser: pModelUserId1,
+        isEmpty: false,
+        parent: pageIdUpd13,
+        descendantCount: 0,
+      },
+      {
+        path: '/mup24',
+        grant: Page.GRANT_OWNER,
+        grantedUsers: [dummyUser1._id],
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 0,
+      },
     ]);
     ]);
 
 
     /**
     /**
@@ -413,6 +569,13 @@ describe('Page', () => {
 
 
   describe('update', () => {
   describe('update', () => {
 
 
+    const updatePage = async(page, newRevisionBody, oldRevisionBody, user, options = {}) => {
+      const mockedRenameSubOperation = jest.spyOn(Page, 'emitPageEventUpdate').mockReturnValue(null);
+      const savedPage = await Page.updatePage(page, newRevisionBody, oldRevisionBody, user, options);
+      mockedRenameSubOperation.mockRestore();
+      return savedPage;
+    };
+
     describe('Changing grant from PUBLIC to RESTRICTED of', () => {
     describe('Changing grant from PUBLIC to RESTRICTED of', () => {
       test('an only-child page will delete its empty parent page', async() => {
       test('an only-child page will delete its empty parent page', async() => {
         const pathT = '/mup13_top';
         const pathT = '/mup13_top';
@@ -485,6 +648,22 @@ describe('Page', () => {
         expect(_pageT.descendantCount).toBe(0);
         expect(_pageT.descendantCount).toBe(0);
       });
       });
     });
     });
+
+    describe('Changing grant to GRANT_RESTRICTED', () => {
+      test('successfully change to GRANT_RESTRICTED from GRANT_OWNER', async() => {
+        const path = '/mup24';
+        const _page = await Page.findOne({ path, grant: Page.GRANT_OWNER, grantedUsers: [dummyUser1._id] });
+        expect(_page).toBeTruthy();
+
+        await updatePage(_page, 'newRevisionBody', 'oldRevisionBody', dummyUser1, { grant: Page.GRANT_RESTRICTED });
+
+        const page = await Page.findOne({ path });
+        expect(page).toBeTruthy();
+        expect(page.grant).toBe(Page.GRANT_RESTRICTED);
+        expect(page.grantedUsers).toStrictEqual([]);
+      });
+    });
+
     describe('Changing grant from RESTRICTED to PUBLIC of', () => {
     describe('Changing grant from RESTRICTED to PUBLIC of', () => {
       test('a page will create ancestors if they do not exist', async() => {
       test('a page will create ancestors if they do not exist', async() => {
         const pathT = '/mup16_top';
         const pathT = '/mup16_top';
@@ -544,6 +723,60 @@ describe('Page', () => {
       });
       });
     });
     });
 
 
+    describe('Changing grant to GRANT_OWNER(onlyme)', () => {
+      test('successfully change to GRANT_OWNER from GRANT_PUBLIC', async() => {
+        const path = '/mup19';
+        const _page = await Page.findOne({ path, grant: Page.GRANT_PUBLIC });
+        expect(_page).toBeTruthy();
+
+        await updatePage(_page, 'newRevisionBody', 'oldRevisionBody', dummyUser1, { grant: Page.GRANT_OWNER });
+
+        const page = await Page.findOne({ path });
+        expect(page.grant).toBe(Page.GRANT_OWNER);
+        expect(page.grantedUsers).toStrictEqual([dummyUser1._id]);
+
+      });
+      test('successfully change to GRANT_OWNER from GRANT_USER_GROUP', async() => {
+        const path = '/mup20';
+        const _page = await Page.findOne({ path, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        expect(_page).toBeTruthy();
+
+        await updatePage(_page, 'newRevisionBody', 'oldRevisionBody', pModelUser1, { grant: Page.GRANT_OWNER });
+
+        const page = await Page.findOne({ path });
+        expect(page.grant).toBe(Page.GRANT_OWNER);
+        expect(page.grantedUsers).toStrictEqual([pModelUser1._id]);
+        expect(page.grantedGroup).toBeNull();
+      });
+      test('successfully change to GRANT_OWNER from GRANT_RESTRICTED', async() => {
+        const path = '/mup21';
+        const _page = await Page.findOne({ path, grant: Page.GRANT_RESTRICTED });
+        expect(_page).toBeTruthy();
+
+        await updatePage(_page, 'newRevisionBody', 'oldRevisionBody', dummyUser1, { grant: Page.GRANT_OWNER });
+
+        const page = await Page.findOne({ path });
+        expect(page.grant).toBe(Page.GRANT_OWNER);
+        expect(page.grantedUsers).toStrictEqual([dummyUser1._id]);
+      });
+      test('Failed to change to GRANT_OWNER if one of the ancestors is GRANT_USER_GROUP page', async() => {
+        const path1 = '/mup22';
+        const path2 = '/mup22/mup23';
+        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        const _page2 = await Page.findOne({ path: path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+
+        await expect(updatePage(_page1, 'newRevisionBody', 'oldRevisionBody', dummyUser1, { grant: Page.GRANT_OWNER }))
+          .rejects.toThrow(new Error('The selected grant or grantedGroup is not assignable to this page.'));
+
+        const page1 = await Page.findOne({ path1 });
+        expect(page1).toBeTruthy();
+        expect(page1.grant).toBe(Page.GRANT_PUBLIC);
+        expect(page1.grantedUsers).not.toStrictEqual([dummyUser1._id]);
+      });
+    });
+
   });
   });
 
 
   describe('getParentAndFillAncestors', () => {
   describe('getParentAndFillAncestors', () => {