Explorar o código

Merge pull request #6026 from weseek/support/95275-test-forceCreateBySystem

support: create isGrantNormalized test
cao %!s(int64=3) %!d(string=hai) anos
pai
achega
dcc9cdf041

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

@@ -143,79 +143,6 @@ describe('Page', () => {
       },
     ]);
 
-    const pageIdCreate1 = new mongoose.Types.ObjectId();
-    const pageIdCreate2 = new mongoose.Types.ObjectId();
-    const pageIdCreate3 = new mongoose.Types.ObjectId();
-    const pageIdCreate4 = new mongoose.Types.ObjectId();
-
-    /**
-     * create
-     * mc_ => model create
-     * emp => empty => page with isEmpty: true
-     * pub => public => GRANT_PUBLIC
-     */
-    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,
-      },
-      {
-        _id: pageIdCreate2,
-        path: '/mc4_top/mc1_emp',
-        grant: Page.GRANT_PUBLIC,
-        creator: dummyUser1,
-        lastUpdateUser: dummyUser1._id,
-        parent: rootPage._id,
-        isEmpty: true,
-      },
-      {
-        path: '/mc4_top/mc1_emp/mc2_pub',
-        grant: Page.GRANT_PUBLIC,
-        creator: dummyUser1,
-        lastUpdateUser: dummyUser1._id,
-        parent: pageIdCreate2,
-        isEmpty: false,
-      },
-      {
-        path: '/mc5_top/mc3_awl',
-        grant: Page.GRANT_RESTRICTED,
-        creator: dummyUser1,
-        lastUpdateUser: dummyUser1._id,
-        isEmpty: false,
-      },
-      {
-        _id: pageIdCreate3,
-        path: '/mc4_top',
-        grant: Page.GRANT_PUBLIC,
-        creator: dummyUser1,
-        lastUpdateUser: dummyUser1._id,
-        isEmpty: false,
-        parent: rootPage._id,
-        descendantCount: 1,
-      },
-      {
-        _id: pageIdCreate4,
-        path: '/mc5_top',
-        grant: Page.GRANT_PUBLIC,
-        creator: dummyUser1,
-        lastUpdateUser: dummyUser1._id,
-        isEmpty: false,
-        parent: rootPage._id,
-        descendantCount: 0,
-      },
-    ]);
-
     /**
      * update
      * mup_ => model update
@@ -421,96 +348,6 @@ describe('Page', () => {
       },
     ]);
 
-  });
-  describe('create', () => {
-
-    test('Should create single page', async() => {
-      const page = await crowi.pageService.create('/v5_create1', 'create1', dummyUser1, {});
-      expect(page).toBeTruthy();
-      expect(page.parent).toStrictEqual(rootPage._id);
-    });
-
-    test('Should create empty-child and non-empty grandchild', async() => {
-      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);
-    });
-
-    test('Should create on empty page', async() => {
-      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);
-    });
-
-    describe('Creating a page using existing path', () => {
-      test('with grant RESTRICTED should only create the page and change nothing else', async() => {
-        const pathT = '/mc4_top';
-        const path1 = '/mc4_top/mc1_emp';
-        const path2 = '/mc4_top/mc1_emp/mc2_pub';
-        const pageT = await Page.findOne({ path: pathT, descendantCount: 1 });
-        const page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
-        const page2 = await Page.findOne({ path: path2 });
-        const page3 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
-        expect(pageT).toBeTruthy();
-        expect(page1).toBeTruthy();
-        expect(page2).toBeTruthy();
-        expect(page3).toBeNull();
-
-        // use existing path
-        await crowi.pageService.create(path1, 'new body', dummyUser1, { grant: Page.GRANT_RESTRICTED });
-
-        const _pageT = await Page.findOne({ path: pathT });
-        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
-        const _page2 = await Page.findOne({ path: path2 });
-        const _page3 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
-        expect(_pageT).toBeTruthy();
-        expect(_page1).toBeTruthy();
-        expect(_page2).toBeTruthy();
-        expect(_page3).toBeTruthy();
-        expect(_pageT.descendantCount).toBe(1);
-      });
-    });
-    describe('Creating a page under a page with grant RESTRICTED', () => {
-      test('will create a new empty page with the same path as the grant RESTRECTED page and become a parent', async() => {
-        const pathT = '/mc5_top';
-        const path1 = '/mc5_top/mc3_awl';
-        const pathN = '/mc5_top/mc3_awl/mc4_pub'; // used to create
-        const pageT = await Page.findOne({ path: pathT });
-        const page1 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
-        const page2 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
-        expect(pageT).toBeTruthy();
-        expect(page1).toBeTruthy();
-        expect(page2).toBeNull();
-
-        await crowi.pageService.create(pathN, 'new body', dummyUser1, { grant: Page.GRANT_PUBLIC });
-
-        const _pageT = await Page.findOne({ path: pathT });
-        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
-        const _page2 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC, isEmpty: true });
-        const _pageN = await Page.findOne({ path: pathN, grant: Page.GRANT_PUBLIC }); // newly crated
-        expect(_pageT).toBeTruthy();
-        expect(_page1).toBeTruthy();
-        expect(_page2).toBeTruthy();
-        expect(_pageN).toBeTruthy();
-        expect(_pageN.parent).toStrictEqual(_page2._id);
-        expect(_pageT.descendantCount).toStrictEqual(1);
-      });
-    });
-
   });
 
   describe('update', () => {

+ 242 - 0
packages/app/test/integration/service/v5.non-public-page.test.ts

@@ -201,6 +201,118 @@ describe('PageService page operations with non-public pages', () => {
       rootPage = pages[0];
     }
 
+    /**
+     * create
+     * mc_ => model create
+     * emp => empty => page with isEmpty: true
+     * pub => public => GRANT_PUBLIC
+     */
+    const pageIdCreate1 = new mongoose.Types.ObjectId();
+    const pageIdCreate2 = new mongoose.Types.ObjectId();
+    const pageIdCreate3 = new mongoose.Types.ObjectId();
+    await Page.insertMany([
+      {
+        _id: pageIdCreate1,
+        path: '/mc4_top/mc1_emp',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        parent: rootPage._id,
+        isEmpty: true,
+      },
+      {
+        path: '/mc4_top/mc1_emp/mc2_pub',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        parent: pageIdCreate1,
+        isEmpty: false,
+      },
+      {
+        path: '/mc5_top/mc3_awl',
+        grant: Page.GRANT_RESTRICTED,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+      },
+      {
+        _id: pageIdCreate2,
+        path: '/mc4_top',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 1,
+      },
+      {
+        _id: pageIdCreate3,
+        path: '/mc5_top',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 0,
+      },
+    ]);
+
+    /**
+     * create
+     * mc_ => model create
+     * emp => empty => page with isEmpty: true
+     * pub => public => GRANT_PUBLIC
+     */
+    const pageIdCreateBySystem1 = new mongoose.Types.ObjectId();
+    const pageIdCreateBySystem2 = new mongoose.Types.ObjectId();
+    const pageIdCreateBySystem3 = new mongoose.Types.ObjectId();
+    await Page.insertMany([
+      {
+        _id: pageIdCreateBySystem1,
+        path: '/mc4_top_by_system/mc1_emp_by_system',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        parent: rootPage._id,
+        isEmpty: true,
+      },
+      {
+        path: '/mc4_top_by_system/mc1_emp_by_system/mc2_pub_by_system',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        parent: pageIdCreateBySystem1,
+        isEmpty: false,
+      },
+      {
+        path: '/mc5_top_by_system/mc3_awl_by_system',
+        grant: Page.GRANT_RESTRICTED,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+      },
+      {
+        _id: pageIdCreateBySystem2,
+        path: '/mc4_top_by_system',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 1,
+      },
+      {
+        _id: pageIdCreateBySystem3,
+        path: '/mc5_top_by_system',
+        grant: Page.GRANT_PUBLIC,
+        creator: dummyUser1,
+        lastUpdateUser: dummyUser1._id,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 0,
+      },
+    ]);
+
     /*
      * Rename
      */
@@ -616,6 +728,136 @@ describe('PageService page operations with non-public pages', () => {
     ]);
   });
 
+  describe('create', () => {
+
+    describe('Creating a page using existing path', () => {
+      test('with grant RESTRICTED should only create the page and change nothing else', async() => {
+        const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized');
+        const pathT = '/mc4_top';
+        const path1 = '/mc4_top/mc1_emp';
+        const path2 = '/mc4_top/mc1_emp/mc2_pub';
+        const pageT = await Page.findOne({ path: pathT, descendantCount: 1 });
+        const page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        const page2 = await Page.findOne({ path: path2 });
+        const page3 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        expect(pageT).toBeTruthy();
+        expect(page1).toBeTruthy();
+        expect(page2).toBeTruthy();
+        expect(page3).toBeNull();
+
+        // use existing path
+        await crowi.pageService.create(path1, 'new body', dummyUser1, { grant: Page.GRANT_RESTRICTED });
+
+        const _pageT = await Page.findOne({ path: pathT });
+        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        const _page2 = await Page.findOne({ path: path2 });
+        const _page3 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        expect(_pageT).toBeTruthy();
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+        expect(_page3).toBeTruthy();
+        expect(_pageT.descendantCount).toBe(1);
+        // isGrantNormalized is not called when GRANT RESTRICTED
+        expect(isGrantNormalizedSpy).toBeCalledTimes(0);
+      });
+    });
+    describe('Creating a page under a page with grant RESTRICTED', () => {
+      test('will create a new empty page with the same path as the grant RESTRECTED page and become a parent', async() => {
+        const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized');
+        const pathT = '/mc5_top';
+        const path1 = '/mc5_top/mc3_awl';
+        const pathN = '/mc5_top/mc3_awl/mc4_pub'; // used to create
+        const pageT = await Page.findOne({ path: pathT });
+        const page1 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        const page2 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        expect(pageT).toBeTruthy();
+        expect(page1).toBeTruthy();
+        expect(page2).toBeNull();
+
+        await crowi.pageService.create(pathN, 'new body', dummyUser1, { grant: Page.GRANT_PUBLIC });
+
+        const _pageT = await Page.findOne({ path: pathT });
+        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        const _page2 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC, isEmpty: true });
+        const _pageN = await Page.findOne({ path: pathN, grant: Page.GRANT_PUBLIC }); // newly crated
+        expect(_pageT).toBeTruthy();
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+        expect(_pageN).toBeTruthy();
+        expect(_pageN.parent).toStrictEqual(_page2._id);
+        expect(_pageT.descendantCount).toStrictEqual(1);
+        // isGrantNormalized is called when GRANT PUBLIC
+        expect(isGrantNormalizedSpy).toBeCalledTimes(1);
+      });
+    });
+
+  });
+
+  describe('create by system', () => {
+
+    describe('Creating a page using existing path', () => {
+      test('with grant RESTRICTED should only create the page and change nothing else', async() => {
+        const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized');
+        const pathT = '/mc4_top_by_system';
+        const path1 = '/mc4_top_by_system/mc1_emp_by_system';
+        const path2 = '/mc4_top_by_system/mc1_emp_by_system/mc2_pub_by_system';
+        const pageT = await Page.findOne({ path: pathT, descendantCount: 1 });
+        const page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        const page2 = await Page.findOne({ path: path2 });
+        const page3 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        expect(pageT).toBeTruthy();
+        expect(page1).toBeTruthy();
+        expect(page2).toBeTruthy();
+        expect(page3).toBeNull();
+
+        // use existing path
+        await crowi.pageService.forceCreateBySystem(path1, 'new body', { grant: Page.GRANT_RESTRICTED });
+
+        const _pageT = await Page.findOne({ path: pathT });
+        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        const _page2 = await Page.findOne({ path: path2 });
+        const _page3 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        expect(_pageT).toBeTruthy();
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+        expect(_page3).toBeTruthy();
+        expect(_pageT.descendantCount).toBe(1);
+        // isGrantNormalized is not called when create by ststem
+        expect(isGrantNormalizedSpy).toBeCalledTimes(0);
+      });
+    });
+    describe('Creating a page under a page with grant RESTRICTED', () => {
+      test('will create a new empty page with the same path as the grant RESTRECTED page and become a parent', async() => {
+        const isGrantNormalizedSpy = jest.spyOn(crowi.pageGrantService, 'isGrantNormalized');
+        const pathT = '/mc5_top_by_system';
+        const path1 = '/mc5_top_by_system/mc3_awl_by_system';
+        const pathN = '/mc5_top_by_system/mc3_awl_by_system/mc4_pub_by_system'; // used to create
+        const pageT = await Page.findOne({ path: pathT });
+        const page1 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        const page2 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC });
+        expect(pageT).toBeTruthy();
+        expect(page1).toBeTruthy();
+        expect(page2).toBeNull();
+
+        await crowi.pageService.forceCreateBySystem(pathN, 'new body', { grant: Page.GRANT_PUBLIC });
+
+        const _pageT = await Page.findOne({ path: pathT });
+        const _page1 = await Page.findOne({ path: path1, grant: Page.GRANT_RESTRICTED });
+        const _page2 = await Page.findOne({ path: path1, grant: Page.GRANT_PUBLIC, isEmpty: true });
+        const _pageN = await Page.findOne({ path: pathN, grant: Page.GRANT_PUBLIC }); // newly crated
+        expect(_pageT).toBeTruthy();
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+        expect(_pageN).toBeTruthy();
+        expect(_pageN.parent).toStrictEqual(_page2._id);
+        expect(_pageT.descendantCount).toStrictEqual(1);
+        // isGrantNormalized is not called when create by ststem
+        expect(isGrantNormalizedSpy).toBeCalledTimes(0);
+      });
+    });
+
+  });
+
   describe('Rename', () => {
     const renamePage = async(page, newPagePath, user, options) => {
       // mock return value

+ 140 - 0
packages/app/test/integration/service/v5.public-page.test.ts

@@ -51,6 +51,56 @@ describe('PageService page operations with only public pages', () => {
       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
      */
@@ -885,6 +935,96 @@ describe('PageService page operations with only public pages', () => {
 
   });
 
+  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) => {