From b6d5ab0ac8d17ed96b582281165f0fd0c82e5e2a Mon Sep 17 00:00:00 2001 From: Basile Spaenlehauer Date: Thu, 21 Mar 2024 07:23:04 +0100 Subject: [PATCH] fix: permision function (#1086) --- .github/workflows/ci.yml | 3 + package.json | 8 +- src/utils/item.test.js | 84 ------------------ src/utils/item.test.ts | 181 +++++++++++++++++++++++++++++++++++++++ src/utils/item.ts | 4 +- yarn.lock | 120 ++++++++++++++++++++++++++ 6 files changed, 311 insertions(+), 89 deletions(-) delete mode 100644 src/utils/item.test.js create mode 100644 src/utils/item.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ca78f685..229cb6b7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,3 +26,6 @@ jobs: - name: Build run: yarn build + + - name: Unit tests + run: yarn test:unit diff --git a/package.json b/package.json index 0e278c5b3..13040a4e5 100644 --- a/package.json +++ b/package.json @@ -90,9 +90,10 @@ "hooks:uninstall": "husky uninstall", "hooks:install": "husky install", "cypress:open": "env-cmd -f ./.env.test cypress open --browser chrome", - "test": "yarn build:test && concurrently -k -s first \"yarn preview:test\" \"yarn cypress:run\"", + "test": "yarn test:unit && yarn build:test && concurrently -k -s first \"yarn preview:test\" \"yarn cypress:run\"", "cypress:run": "env-cmd -f ./.env.test cypress run --browser chrome", - "postinstall": "husky install" + "postinstall": "husky install", + "test:unit": "yarn vitest" }, "browserslist": { "production": [ @@ -148,7 +149,8 @@ "typescript": "5.3.3", "vite": "5.1.4", "vite-plugin-checker": "0.6.4", - "vite-plugin-istanbul": "5.0.0" + "vite-plugin-istanbul": "5.0.0", + "vitest": "1.4.0" }, "packageManager": "yarn@4.1.1" } diff --git a/src/utils/item.test.js b/src/utils/item.test.js deleted file mode 100644 index 6db67b6e3..000000000 --- a/src/utils/item.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import { getParentsIdsFromPath, isUrlValid, transformIdForPath } from './item'; - -describe('item utils', () => { - it('isUrlValid', () => { - expect(isUrlValid(null)).toBeFalsy(); - expect(isUrlValid(undefined)).toBeFalsy(); - expect(isUrlValid('somelink')).toBeFalsy(); - expect(isUrlValid('graasp.eu')).toBeFalsy(); - expect(isUrlValid('https://graasp')).toBeFalsy(); - - expect(isUrlValid('https://graasp.eu')).toBeTruthy(); - expect(isUrlValid('http://graasp.eu')).toBeTruthy(); - expect(isUrlValid('https://www.youtube.com/')).toBeTruthy(); - }); - - it('transformIdForPath', () => { - expect(transformIdForPath('someid')).toEqual('someid'); - expect(transformIdForPath('some-id')).toEqual('some_id'); - const id = 'ecafbd2a-5688-11eb-ae93-0242ac130002'; - const path = 'ecafbd2a_5688_11eb_ae93_0242ac130002'; - expect(transformIdForPath(id)).toEqual(path); - - expect(() => transformIdForPath(null)).toThrow(); - expect(() => transformIdForPath(undefined)).toThrow(); - }); - - describe('getParentsIdsFromPath', () => { - it('default', () => { - expect(getParentsIdsFromPath('someid')).toEqual(['someid']); - expect(getParentsIdsFromPath('parent.child')).toEqual([ - 'parent', - 'child', - ]); - expect( - getParentsIdsFromPath( - 'ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', - ), - ).toEqual([ - 'ecafbd2a-5688-11eb-ae93-0242ac130002', - 'ecafbd2a-5688-11eb-ae93-0242ac130001', - ]); - expect( - getParentsIdsFromPath( - 'ecafbd2a_5688_11eb_ae93_0242ac130003.ecafbd2a_5688_11eb_ae93_0242ac130004.ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', - ), - ).toEqual([ - 'ecafbd2a-5688-11eb-ae93-0242ac130003', - 'ecafbd2a-5688-11eb-ae93-0242ac130004', - 'ecafbd2a-5688-11eb-ae93-0242ac130002', - 'ecafbd2a-5688-11eb-ae93-0242ac130001', - ]); - - expect(getParentsIdsFromPath(null)).toEqual([]); - expect(getParentsIdsFromPath(undefined)).toEqual([]); - }); - it('ignoreSelf = true', () => { - expect(getParentsIdsFromPath('someid', { ignoreSelf: true })).toEqual([]); - expect( - getParentsIdsFromPath('parent.child', { ignoreSelf: true }), - ).toEqual(['parent']); - expect( - getParentsIdsFromPath( - 'ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', - { ignoreSelf: true }, - ), - ).toEqual(['ecafbd2a-5688-11eb-ae93-0242ac130002']); - expect( - getParentsIdsFromPath( - 'ecafbd2a_5688_11eb_ae93_0242ac130003.ecafbd2a_5688_11eb_ae93_0242ac130004.ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', - { ignoreSelf: true }, - ), - ).toEqual([ - 'ecafbd2a-5688-11eb-ae93-0242ac130003', - 'ecafbd2a-5688-11eb-ae93-0242ac130004', - 'ecafbd2a-5688-11eb-ae93-0242ac130002', - ]); - - expect(getParentsIdsFromPath(null, { ignoreSelf: true })).toEqual([]); - expect(getParentsIdsFromPath(undefined, { ignoreSelf: true })).toEqual( - [], - ); - }); - }); -}); diff --git a/src/utils/item.test.ts b/src/utils/item.test.ts new file mode 100644 index 000000000..30d64bf6b --- /dev/null +++ b/src/utils/item.test.ts @@ -0,0 +1,181 @@ +import { DiscriminatedItem, PermissionLevel } from '@graasp/sdk'; + +import { describe, expect, it } from 'vitest'; + +import { + getHighestPermissionForMemberFromMemberships, + getParentsIdsFromPath, + isUrlValid, + transformIdForPath, +} from './item'; + +describe('item utils', () => { + it('isUrlValid', () => { + expect(isUrlValid(null)).toBeFalsy(); + expect(isUrlValid()).toBeFalsy(); + expect(isUrlValid('somelink')).toBeFalsy(); + expect(isUrlValid('graasp.eu')).toBeTruthy(); + expect(isUrlValid('https://graasp')).toBeFalsy(); + + expect(isUrlValid('https://graasp.eu')).toBeTruthy(); + expect(isUrlValid('http://graasp.eu')).toBeTruthy(); + expect(isUrlValid('https://www.youtube.com/')).toBeTruthy(); + }); + + it('transformIdForPath', () => { + expect(transformIdForPath('someid')).toEqual('someid'); + expect(transformIdForPath('some-id')).toEqual('some_id'); + const id = 'ecafbd2a-5688-11eb-ae93-0242ac130002'; + const path = 'ecafbd2a_5688_11eb_ae93_0242ac130002'; + expect(transformIdForPath(id)).toEqual(path); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + expect(() => transformIdForPath(null)).toThrow(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + expect(() => transformIdForPath(undefined)).toThrow(); + }); + + describe('getParentsIdsFromPath', () => { + it('default', () => { + expect(getParentsIdsFromPath('someid')).toEqual(['someid']); + expect(getParentsIdsFromPath('parent.child')).toEqual([ + 'parent', + 'child', + ]); + expect( + getParentsIdsFromPath( + 'ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', + ), + ).toEqual([ + 'ecafbd2a-5688-11eb-ae93-0242ac130002', + 'ecafbd2a-5688-11eb-ae93-0242ac130001', + ]); + expect( + getParentsIdsFromPath( + 'ecafbd2a_5688_11eb_ae93_0242ac130003.ecafbd2a_5688_11eb_ae93_0242ac130004.ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', + ), + ).toEqual([ + 'ecafbd2a-5688-11eb-ae93-0242ac130003', + 'ecafbd2a-5688-11eb-ae93-0242ac130004', + 'ecafbd2a-5688-11eb-ae93-0242ac130002', + 'ecafbd2a-5688-11eb-ae93-0242ac130001', + ]); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + expect(getParentsIdsFromPath(null)).toEqual([]); + expect(getParentsIdsFromPath(undefined)).toEqual([]); + }); + it('ignoreSelf = true', () => { + expect(getParentsIdsFromPath('someid', { ignoreSelf: true })).toEqual([]); + expect( + getParentsIdsFromPath('parent.child', { ignoreSelf: true }), + ).toEqual(['parent']); + expect( + getParentsIdsFromPath( + 'ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', + { ignoreSelf: true }, + ), + ).toEqual(['ecafbd2a-5688-11eb-ae93-0242ac130002']); + expect( + getParentsIdsFromPath( + 'ecafbd2a_5688_11eb_ae93_0242ac130003.ecafbd2a_5688_11eb_ae93_0242ac130004.ecafbd2a_5688_11eb_ae93_0242ac130002.ecafbd2a_5688_11eb_ae93_0242ac130001', + { ignoreSelf: true }, + ), + ).toEqual([ + 'ecafbd2a-5688-11eb-ae93-0242ac130003', + 'ecafbd2a-5688-11eb-ae93-0242ac130004', + 'ecafbd2a-5688-11eb-ae93-0242ac130002', + ]); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + expect(getParentsIdsFromPath(null, { ignoreSelf: true })).toEqual([]); + expect(getParentsIdsFromPath(undefined, { ignoreSelf: true })).toEqual( + [], + ); + }); + }); + + describe('getHighestPermission', () => { + it('returns null when no memberId', () => { + expect( + getHighestPermissionForMemberFromMemberships({ + memberId: undefined, + itemPath: '1234', + }), + ).toEqual(null); + }); + + it('returns null when no memberships', () => { + expect( + getHighestPermissionForMemberFromMemberships({ + memberId: '1234', + itemPath: '1234', + memberships: undefined, + }), + ).toEqual(null); + expect( + getHighestPermissionForMemberFromMemberships({ + memberId: '1234', + itemPath: '1234', + memberships: [], + }), + ).toEqual(null); + }); + + it('returns closest permission when only one permission', () => { + const member = { id: '1234', name: 'bob', email: 'bob@graasp.org' }; + const item = { path: '1234' } as DiscriminatedItem; + const membership = { + id: 'membership-123', + member, + item, + permission: PermissionLevel.Read, + creator: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + expect( + getHighestPermissionForMemberFromMemberships({ + memberId: member.id, + itemPath: item.path, + memberships: [membership], + }), + ).toEqual(membership); + }); + + it('returns closest permission when multiple permissions', () => { + const member = { id: '1234', name: 'bob', email: 'bob@graasp.org' }; + const item1 = { path: '1234' } as DiscriminatedItem; + const item2 = { path: '1234.5678' } as DiscriminatedItem; + const membership1 = { + id: 'membership-123', + member, + item: item1, + permission: PermissionLevel.Read, + creator: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const membership2 = { + id: 'membership-123', + member, + item: item2, + permission: PermissionLevel.Read, + creator: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + expect( + getHighestPermissionForMemberFromMemberships({ + memberId: member.id, + itemPath: item2.path, + memberships: [membership1, membership2], + }), + ).toEqual(membership2); + }); + }); +}); diff --git a/src/utils/item.ts b/src/utils/item.ts index 66a8efda8..d89b9c967 100644 --- a/src/utils/item.ts +++ b/src/utils/item.ts @@ -195,12 +195,12 @@ export const getHighestPermissionForMemberFromMemberships = ({ ({ item: { path: mPath }, member: { id: mId } }) => mId === memberId && itemPath.includes(mPath), ); - if (!itemMemberships) { + if (!itemMemberships || itemMemberships.length === 0) { return null; } const sorted = [...itemMemberships]; - sorted?.sort((a, b) => (a.item.path.length > b.item.path.length ? 1 : -1)); + sorted?.sort((a, b) => (a.item.path.length > b.item.path.length ? -1 : 1)); return sorted[0]; }; diff --git a/yarn.lock b/yarn.lock index 7b937ee8b..c302ad639 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3905,6 +3905,17 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:1.4.0": + version: 1.4.0 + resolution: "@vitest/expect@npm:1.4.0" + dependencies: + "@vitest/spy": "npm:1.4.0" + "@vitest/utils": "npm:1.4.0" + chai: "npm:^4.3.10" + checksum: 10/00d794a807b7e496d8450133430c8528d4b6cfaba9520bf49640c941b14acaa7b28f151c249b44d935740cae887f0648980db63f38e37bdeb6c2906387e15188 + languageName: node + linkType: hard + "@vitest/runner@npm:1.3.1": version: 1.3.1 resolution: "@vitest/runner@npm:1.3.1" @@ -3916,6 +3927,17 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:1.4.0": + version: 1.4.0 + resolution: "@vitest/runner@npm:1.4.0" + dependencies: + "@vitest/utils": "npm:1.4.0" + p-limit: "npm:^5.0.0" + pathe: "npm:^1.1.1" + checksum: 10/7b8a692de5cef72ef698e83eb5bbb89076924e7a557ed087e80c5080e000a575f34c481f3b880aa2588da5a095504dc55216c319f6924eddfcfc3412f10a27b2 + languageName: node + linkType: hard + "@vitest/snapshot@npm:1.3.1": version: 1.3.1 resolution: "@vitest/snapshot@npm:1.3.1" @@ -3927,6 +3949,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:1.4.0": + version: 1.4.0 + resolution: "@vitest/snapshot@npm:1.4.0" + dependencies: + magic-string: "npm:^0.30.5" + pathe: "npm:^1.1.1" + pretty-format: "npm:^29.7.0" + checksum: 10/43e22f8aeef4b87bcce79b37775415d4b558e32d906992d4a0acbe81c8e84cbfe3e488dd32c504c4f4d8f2c3f96842acb524b4b210036fda6796e64d0140d5f6 + languageName: node + linkType: hard + "@vitest/spy@npm:1.3.1": version: 1.3.1 resolution: "@vitest/spy@npm:1.3.1" @@ -3936,6 +3969,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:1.4.0": + version: 1.4.0 + resolution: "@vitest/spy@npm:1.4.0" + dependencies: + tinyspy: "npm:^2.2.0" + checksum: 10/0e48f9a64f62801c2abf10df1013ec5e5b75c47bdca6a5d4c8246b3dd7bdf01ade3df6c99fd0751a870a16bd63c127b3e58e0f5cbc320c48d0727ab5da89d028 + languageName: node + linkType: hard + "@vitest/utils@npm:1.3.1": version: 1.3.1 resolution: "@vitest/utils@npm:1.3.1" @@ -3948,6 +3990,18 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:1.4.0": + version: 1.4.0 + resolution: "@vitest/utils@npm:1.4.0" + dependencies: + diff-sequences: "npm:^29.6.3" + estree-walker: "npm:^3.0.3" + loupe: "npm:^2.3.7" + pretty-format: "npm:^29.7.0" + checksum: 10/2261705e2edc10376f2524a4bf6616688680094d94fff683681a1ef8d3d59271dee2d80893efad8e6437bbdb00390e2edd754d94cf42100db86f2cfd9c44826f + languageName: node + linkType: hard + "JSONStream@npm:^1.3.5": version: 1.3.5 resolution: "JSONStream@npm:1.3.5" @@ -7593,6 +7647,7 @@ __metadata: vite: "npm:5.1.4" vite-plugin-checker: "npm:0.6.4" vite-plugin-istanbul: "npm:5.0.0" + vitest: "npm:1.4.0" languageName: unknown linkType: soft @@ -13545,6 +13600,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:1.4.0": + version: 1.4.0 + resolution: "vite-node@npm:1.4.0" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.4" + pathe: "npm:^1.1.1" + picocolors: "npm:^1.0.0" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10/691e828c2abe6b62d44183c4e04bdfd119fed405439126fbdc5bfb791644baee3961c1ce429a67b360cc3d8b7c472160c7e82c59491f044a232b4ff480d8a2a2 + languageName: node + linkType: hard + "vite-plugin-checker@npm:0.6.4": version: 0.6.4 resolution: "vite-plugin-checker@npm:0.6.4" @@ -13740,6 +13810,56 @@ __metadata: languageName: node linkType: hard +"vitest@npm:1.4.0": + version: 1.4.0 + resolution: "vitest@npm:1.4.0" + dependencies: + "@vitest/expect": "npm:1.4.0" + "@vitest/runner": "npm:1.4.0" + "@vitest/snapshot": "npm:1.4.0" + "@vitest/spy": "npm:1.4.0" + "@vitest/utils": "npm:1.4.0" + acorn-walk: "npm:^8.3.2" + chai: "npm:^4.3.10" + debug: "npm:^4.3.4" + execa: "npm:^8.0.1" + local-pkg: "npm:^0.5.0" + magic-string: "npm:^0.30.5" + pathe: "npm:^1.1.1" + picocolors: "npm:^1.0.0" + std-env: "npm:^3.5.0" + strip-literal: "npm:^2.0.0" + tinybench: "npm:^2.5.1" + tinypool: "npm:^0.8.2" + vite: "npm:^5.0.0" + vite-node: "npm:1.4.0" + why-is-node-running: "npm:^2.2.2" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 1.4.0 + "@vitest/ui": 1.4.0 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10/cf4675657f4a9ea755d0af70d62827fca9daee64e81d0392067c70a0d1f5f8fd4a47523e28ecf42d667e4d4d7c68b09d5e08389d4b58dc36065364f6c76cda7d + languageName: node + linkType: hard + "void-elements@npm:3.1.0": version: 3.1.0 resolution: "void-elements@npm:3.1.0"