Bläddra i källkod

Merge branch 'feature/complete_tests_and_coverage' into develop

Olivier Massot 3 år sedan
förälder
incheckning
773618ee96

+ 4 - 0
.gitlab-ci.yml

@@ -24,6 +24,10 @@ unit:
       - ./coverage/
     when: always
     reports:
+      junit: coverage/junit.xml
       coverage_report:
         coverage_format: cobertura
         path: coverage/cobertura-coverage.xml
+
+  # Extract total coverage from job logs (https://docs.gitlab.com/15.6/ee/ci/yaml/index.html#coverage)
+  coverage: '/All files\s*|\s*\d+\.\d+/'

+ 2 - 1
composables/data/useAp2iRequestService.ts

@@ -98,9 +98,10 @@ export const useAp2iRequestService = () => {
     }
 
     //Avoid memory leak
-    if(apiRequestServiceClass === null){
+    if (apiRequestServiceClass === null) {
         // Utilise la fonction `create` d'ohmyfetch pour générer un fetcher dédié à l'interrogation de Ap2i
         const fetcher = $fetch.create(config)
+        // @ts-ignore
         apiRequestServiceClass = new ApiRequestService(fetcher)
     }
 

+ 7 - 3
composables/data/useEntityManager.ts

@@ -1,13 +1,17 @@
 import EntityManager from "~/services/data/entityManager";
 import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
+import ApiResource from "~/models/ApiResource";
+import {useRepo} from "pinia-orm";
 
 let entityManager:EntityManager|null = null
 
 export const useEntityManager = () => {
-    const { apiRequestService, pending } = useAp2iRequestService()
     //Avoid memory leak
     if(entityManager === null){
-        entityManager = new EntityManager(apiRequestService)
+        const { apiRequestService } = useAp2iRequestService()
+        const getRepo = (model: typeof ApiResource) => useRepo(model)
+
+        entityManager = new EntityManager(apiRequestService, getRepo)
     }
-    return { em: entityManager, pending: pending }
+    return { em: entityManager }
 }

+ 5 - 4
composables/data/useEnumManager.ts

@@ -2,13 +2,14 @@ import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
 import EnumManager from "~/services/data/enumManager";
 import {useI18n} from "vue-i18n";
 
-let enumManager:EnumManager|null = null
+let enumManager:EnumManager | null = null
+
 export const useEnumManager = () => {
-    const { apiRequestService, pending } = useAp2iRequestService()
     //Avoid memory leak
-    if(enumManager === null){
+    if (enumManager === null) {
+        const { apiRequestService } = useAp2iRequestService()
         const i18n = useI18n() as any
         enumManager = new EnumManager(apiRequestService, i18n)
     }
-    return { enumManager: enumManager, pending: pending }
+    return { enumManager: enumManager }
 }

+ 4 - 2
composables/data/useImageManager.ts

@@ -1,12 +1,14 @@
 import {useAp2iRequestService} from "~/composables/data/useAp2iRequestService";
 import ImageManager from "~/services/data/imageManager";
 
-let imageManager:ImageManager|null = null
+let imageManager:ImageManager | null = null
+
 export const useImageManager = () => {
     //Avoid memory leak
-    if(imageManager === null){
+    if (imageManager === null) {
         const { apiRequestService } = useAp2iRequestService()
         imageManager = new ImageManager(apiRequestService)
     }
+
     return { imageManager: imageManager }
 }

+ 2 - 2
package.json

@@ -33,7 +33,7 @@
     "@typescript-eslint/eslint-plugin": "^5.43.0",
     "@typescript-eslint/parser": "^5.43.0",
     "@vitejs/plugin-vue": "^4.0.0",
-    "@vitest/coverage-c8": "^0.28.3",
+    "@vitest/coverage-c8": "^0.28.4",
     "@vue/eslint-config-standard": "^8.0.1",
     "@vue/test-utils": "^2.2.7",
     "blob-polyfill": "^7.0.20220408",
@@ -47,7 +47,7 @@
     "prettier": "^2.7.1",
     "ts-jest": "^29.0.3",
     "typescript": "4.9.4",
-    "vitest": "0.28.3",
+    "vitest": "0.28.4",
     "vue-jest": "^3.0.7"
   },
   "dependencies": {

+ 15 - 4
services/data/entityManager.ts

@@ -1,5 +1,5 @@
 import ApiRequestService from "./apiRequestService"
-import {Repository, useRepo} from "pinia-orm"
+import {Repository} from "pinia-orm"
 import UrlUtils from "~/services/utils/urlUtils"
 import HydraDenormalizer from "./normalizer/hydraDenormalizer"
 import ApiModel from "~/models/ApiModel"
@@ -19,12 +19,24 @@ import _ from "lodash"
 class EntityManager {
     protected CLONE_PREFIX = '_clone_'
 
+    /**
+     * In instance of ApiRequestService
+     * @protected
+     */
     protected apiRequestService: ApiRequestService
 
+    /**
+     * A method to retrieve the repository of a given ApiResource
+     * @protected
+     */
+    protected _getRepo: (model: typeof ApiResource) => Repository<ApiResource>
+
     public constructor(
-        apiRequestService: ApiRequestService
+        apiRequestService: ApiRequestService,
+        getRepo: (model: typeof ApiResource) => Repository<ApiResource>
     ) {
         this.apiRequestService = apiRequestService
+        this._getRepo = getRepo
     }
 
     /**
@@ -33,8 +45,7 @@ class EntityManager {
      * @param model
      */
     public getRepository(model: typeof ApiResource): Repository<ApiResource> {
-        // TODO: voir si possible de passer par une injection de dépendance plutôt que par un use
-        return useRepo(model)
+        return this._getRepo(model)
     }
 
     /**

+ 21 - 8
services/rights/roleUtils.ts

@@ -42,16 +42,18 @@ const rolesToChange: Array<string> = [
   'ROLE_ONLINEREGISTRATION_ADMINISTRATION_VIEW'
 ]
 
-const actions = ['VIEW', 'MANAGE', 'REFERENCE', 'CORE']
+const actions = ['VIEW', 'REFERENCE', 'CORE']
 
 const actionMap: AnyJson = {
   '': 'manage',
-  'VIEW': 'read'
+  'VIEW': 'read',
+  'REFERENCE': null,
+  'CORE': null,
 }
 
 interface Role {
   subject: string
-  action: 'VIEW' | 'MANAGE' | 'REFERENCE' | ''
+  action: 'VIEW' | 'CORE' | 'REFERENCE' | ''
 }
 
 /**
@@ -100,7 +102,7 @@ class RoleUtils {
     }
     parts.shift()
 
-    let action: 'VIEW' | 'MANAGE' | 'REFERENCE' | '' = ''
+    let action: 'VIEW' | 'CORE' | 'REFERENCE' | '' = ''
     if (actions.includes(parts.at(-1) ?? '')) {
       // @ts-ignore
       action = parts.pop() ?? ''
@@ -112,12 +114,23 @@ class RoleUtils {
   }
 
   static roleToString(role: Role) {
-    return ['ROLE', role.subject, role.action].filter((s: string) => s.length > 0).join('_')
+    //  TODO: est-ce qu'il faut retransformer les - en _ ?  (si oui, attention à maj les tests)
+    return ['ROLE', role.subject, role.action].filter((s: string) => s !== null && s.length > 0).join('_')
   }
 
-  static roleToAbility(role: Role): AbilitiesType {
+  /**
+   * Construit une habilité à partir du rôle en paramètre.
+   * Retourne null si le role ne donne droit à aucune habilité
+   * @param role
+   */
+  static roleToAbility(role: Role): AbilitiesType | null {
+    const mappedAction = actionMap[role.action]
+    if (mappedAction === null) {
+      return null
+    }
+
     return {
-      action: actionMap[role.action],
+      action: mappedAction,
       subject: role.subject.toLowerCase()
     }
   }
@@ -141,7 +154,7 @@ class RoleUtils {
       const ability = RoleUtils.roleToAbility(parsed)
 
       // @ts-ignore
-      if (ability.subject && typeof ability.action !== 'undefined') {
+      if (ability !== null && ability.subject && typeof ability.action !== 'undefined') {
         abilities.push(ability)
       }
     })

+ 2 - 2
services/utils/urlUtils.ts

@@ -66,9 +66,9 @@ class UrlUtils {
    */
   public static extractIdFromUri (uri: string, isLiteral: boolean = false): number|string|null {
     const partUri: Array<string> = uri.split('/')
-    const id: any = partUri.pop()
+    const id: string = partUri.pop() ?? ''
 
-    if (!id || (!isLiteral && isNaN(id))) {
+    if (!id || (!isLiteral && isNaN(id as any))) {
       throw new Error('no id found')
     }
     return isLiteral ? id : parseInt(id)

+ 29 - 8
tests/units/services/data/entityManager.test.ts

@@ -1,9 +1,10 @@
-import { describe, test, expect } from 'vitest'
+import { describe, test, it, expect } from 'vitest'
 import EntityManager from "~/services/data/entityManager";
 import ApiResource from "~/models/ApiResource";
 import ApiModel from "~/models/ApiModel";
 import ApiRequestService from "~/services/data/apiRequestService";
 import {Element, Repository} from "pinia-orm";
+import models from "~/models/models";
 
 
 
@@ -22,18 +23,32 @@ let _console: any = {
     'error': console.error,
 }
 
+vi.mock("~/models/models", async () => {
+    class MyModel {
+        static entity = 'myModel'
+    }
+
+    const models: Record<string, any> = {'myModel': MyModel}
+
+    return {
+        default: models
+    }
+})
+
 let apiRequestService: ApiRequestService
 let entityManager: EntityManager
 let repo: Repository<ApiResource>
+let _getRepo: (model: typeof ApiResource) => Repository<ApiResource>
 
 beforeEach(() => {
     // @ts-ignore
-    apiRequestService = vi.fn() as ApiRequestService
-
-    entityManager = new EntityManager(apiRequestService)
+    repo = vi.fn() as Repository<ApiResource>
 
     // @ts-ignore
-    repo = vi.fn() as Repository<ApiResource>
+    apiRequestService = vi.fn() as ApiRequestService
+    _getRepo = vi.fn((model: typeof ApiResource) => repo)
+
+    entityManager = new EntityManager(apiRequestService, _getRepo)
 })
 
 afterEach(() => {
@@ -44,7 +59,11 @@ afterEach(() => {
 })
 
 describe('getRepository', () => {
-    // TODO: à revoir
+  test('simple call', () => {
+      entityManager.getRepository(DummyApiResource)
+
+      expect(_getRepo).toHaveBeenCalledWith(DummyApiResource)
+  })
 })
 
 describe('cast', () => {
@@ -57,7 +76,9 @@ describe('cast', () => {
 })
 
 describe('getModelFor', () => {
-    // TODO: à revoir
+    test('simple call', () => {
+        expect(entityManager.getModelFor('myModel').entity).toEqual('myModel')
+    })
 })
 
 describe('getModelFromIri', () => {
@@ -70,7 +91,7 @@ describe('getModelFromIri', () => {
 
         expect(result).toEqual(DummyApiResource)
     })
-    test('invalid Iri', () => {
+    test('invalide Iri', () => {
         expect(() => entityManager.getModelFromIri('/invalid')).toThrowError('cannot parse the IRI')
     })
 })

+ 38 - 1
tests/units/services/rights/roleUtils.test.ts

@@ -26,8 +26,45 @@ describe('filterFunctionRoles', () => {
     })
 });
 
-describe('transformUnderscoreToHyphen', () => {
+describe('parseRole', () => {
+    test('simple call', () => {
+        expect(RoleUtils.parseRole('ROLE_MYSUBJECT_VIEW')).toEqual({ subject: 'MYSUBJECT', action: 'VIEW' })
+        expect(RoleUtils.parseRole('ROLE_MYSUBJECT_CORE')).toEqual({ subject: 'MYSUBJECT', action: 'CORE' })
+        expect(RoleUtils.parseRole('ROLE_MYSUBJECT_REFERENCE')).toEqual({ subject: 'MYSUBJECT', action: 'REFERENCE' })
+        expect(RoleUtils.parseRole('ROLE_MYSUBJECT')).toEqual({ subject: 'MYSUBJECT', action: '' })
+    })
+
+    test('multi-word subject', () => {
+        expect(RoleUtils.parseRole('ROLE_MY_SUBJECT_VIEW')).toEqual({ subject: 'MY-SUBJECT', action: 'VIEW' })
+        expect(RoleUtils.parseRole('ROLE_MY_OTHER_SUBJECT')).toEqual({ subject: 'MY-OTHER-SUBJECT', action: '' })
+    })
+
+    test('invalid input', () => {
+        expect(() => RoleUtils.parseRole('INVALID')).toThrowError('can not parse role')
+    })
+})
+
+describe('roleToString', () => {
+    test('simple calls', () => {
+        expect(RoleUtils.roleToString({ subject: 'MYSUBJECT', action: '' })).toEqual('ROLE_MYSUBJECT')
+        expect(RoleUtils.roleToString({ subject: 'MYSUBJECT', action: 'VIEW' })).toEqual('ROLE_MYSUBJECT_VIEW')
+        expect(RoleUtils.roleToString({ subject: 'MY_SUBJECT', action: '' })).toEqual('ROLE_MY_SUBJECT')
+        expect(RoleUtils.roleToString({ subject: 'MY_SUBJECT', action: 'VIEW' })).toEqual('ROLE_MY_SUBJECT_VIEW')
+        expect(RoleUtils.roleToString({ subject: 'MY-SUBJECT', action: 'VIEW' })).toEqual('ROLE_MY-SUBJECT_VIEW')
+        expect(RoleUtils.roleToString({ subject: 'MY-SUBJECT', action: 'VIEW' })).toEqual('ROLE_MY-SUBJECT_VIEW')
+        expect(RoleUtils.roleToString({ subject: 'MY_OTHER-SUBJECT', action: '' })).toEqual('ROLE_MY_OTHER-SUBJECT')
+        expect(RoleUtils.roleToString({ subject: 'MY_OTHER-SUBJECT', action: 'VIEW' })).toEqual('ROLE_MY_OTHER-SUBJECT_VIEW')
+    })
+})
 
+describe('roleToAbility', () => {
+  test('simple calls', () => {
+      expect(RoleUtils.roleToAbility({ subject: 'MYSUBJECT', action: '' })).toEqual({ action: 'manage', subject: 'mysubject'})
+      expect(RoleUtils.roleToAbility({ subject: 'MYSUBJECT', action: 'VIEW' })).toEqual({ action: 'read', subject: 'mysubject'})
+      expect(RoleUtils.roleToAbility({ subject: 'MY-SUBJECT', action: '' })).toEqual({ action: 'manage', subject: 'my-subject'})
+      expect(RoleUtils.roleToAbility({ subject: 'MY-SUBJECT', action: 'REFERENCE' })).toEqual(null)
+      expect(RoleUtils.roleToAbility({ subject: 'MY-SUBJECT', action: 'CORE' })).toEqual(null)
+  })
 })
 
 describe('rolesToAbilities', () => {

+ 4 - 0
vitest.config.ts

@@ -19,6 +19,10 @@ export default defineConfig({
             all: true,
             extension: ['.ts', '.vue'],
             reporter: ['html', 'json', 'text', 'cobertura']
+        },
+        reporters: ['default', 'junit'],
+        outputFile: {
+            junit: './coverage/junit.xml'
         }
     },
     resolve: {

+ 37 - 37
yarn.lock

@@ -1489,45 +1489,45 @@
   resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz#93815beffd23db46288c787352a8ea31a0c03e5e"
   integrity sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==
 
-"@vitest/coverage-c8@^0.28.3":
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/@vitest/coverage-c8/-/coverage-c8-0.28.3.tgz#15966a0c0ca2f30a1811d1b695221495307bcc52"
-  integrity sha512-3Toi4flNyxwSSYohhV3/euFSyrHjaD9vJVwFVcy84lcRHMEkv0W7pxlqZZeCvPdktN+WETbNazx3WWBs0jqhVQ==
+"@vitest/coverage-c8@^0.28.4":
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/@vitest/coverage-c8/-/coverage-c8-0.28.4.tgz#5c7dc2b88135051a4a503068f7677535e68965b4"
+  integrity sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==
   dependencies:
     c8 "^7.12.0"
     picocolors "^1.0.0"
     std-env "^3.3.1"
-    vitest "0.28.3"
+    vitest "0.28.4"
 
-"@vitest/expect@0.28.3":
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.28.3.tgz#8cd570b662e709f56ba29835879890c87429a194"
-  integrity sha512-dnxllhfln88DOvpAK1fuI7/xHwRgTgR4wdxHldPaoTaBu6Rh9zK5b//v/cjTkhOfNP/AJ8evbNO8H7c3biwd1g==
+"@vitest/expect@0.28.4":
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.28.4.tgz#09f2513d2ea951057660540ae235609a0d6378f9"
+  integrity sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==
   dependencies:
-    "@vitest/spy" "0.28.3"
-    "@vitest/utils" "0.28.3"
+    "@vitest/spy" "0.28.4"
+    "@vitest/utils" "0.28.4"
     chai "^4.3.7"
 
-"@vitest/runner@0.28.3":
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.28.3.tgz#a59bc7a1457957291b6bf1964831284768168314"
-  integrity sha512-P0qYbATaemy1midOLkw7qf8jraJszCoEvjQOSlseiXZyEDaZTZ50J+lolz2hWiWv6RwDu1iNseL9XLsG0Jm2KQ==
+"@vitest/runner@0.28.4":
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-0.28.4.tgz#4c4e5aed91d4b19a3071e601c75745d672868388"
+  integrity sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==
   dependencies:
-    "@vitest/utils" "0.28.3"
+    "@vitest/utils" "0.28.4"
     p-limit "^4.0.0"
     pathe "^1.1.0"
 
-"@vitest/spy@0.28.3":
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.28.3.tgz#6f6f7ecdeefecb023a96e69b6083e0314ea6f04c"
-  integrity sha512-jULA6suS6CCr9VZfr7/9x97pZ0hC55prnUNHNrg5/q16ARBY38RsjsfhuUXt6QOwvIN3BhSS0QqPzyh5Di8g6w==
+"@vitest/spy@0.28.4":
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-0.28.4.tgz#beb994b7d46edee4966160eb1363e0493f9d9ef1"
+  integrity sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==
   dependencies:
     tinyspy "^1.0.2"
 
-"@vitest/utils@0.28.3":
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.28.3.tgz#75c076d4fdde5c48ee5de2808c83d615fc74d4ef"
-  integrity sha512-YHiQEHQqXyIbhDqETOJUKx9/psybF7SFFVCNfOvap0FvyUqbzTSDCa3S5lL4C0CLXkwVZttz9xknDoyHMguFRQ==
+"@vitest/utils@0.28.4":
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-0.28.4.tgz#be8378f860f40c2d48a62f46c808cf98b9736100"
+  integrity sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==
   dependencies:
     cli-truncate "^3.1.0"
     diff "^5.1.0"
@@ -7830,10 +7830,10 @@ validate-npm-package-license@^3.0.1:
     spdx-correct "^3.0.0"
     spdx-expression-parse "^3.0.0"
 
-vite-node@0.28.3:
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.28.3.tgz#5d693c237d5467f167f81d158a56d3408fea899c"
-  integrity sha512-uJJAOkgVwdfCX8PUQhqLyDOpkBS5+j+FdbsXoPVPDlvVjRkb/W/mLYQPSL6J+t8R0UV8tJSe8c9VyxVQNsDSyg==
+vite-node@0.28.4:
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-0.28.4.tgz#ce709cde2200d86a2a45457fed65f453234b0261"
+  integrity sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==
   dependencies:
     cac "^6.7.14"
     debug "^4.3.4"
@@ -7910,18 +7910,18 @@ vite@~3.2.4:
   optionalDependencies:
     fsevents "~2.3.2"
 
-vitest@0.28.3:
-  version "0.28.3"
-  resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.28.3.tgz#58322a5ae64854d4cdb75451817b9fb795f9102e"
-  integrity sha512-N41VPNf3VGJlWQizGvl1P5MGyv3ZZA2Zvh+2V8L6tYBAAuqqDK4zExunT1Cdb6dGfZ4gr+IMrnG8d4Z6j9ctPw==
+vitest@0.28.4:
+  version "0.28.4"
+  resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.28.4.tgz#d41113d6e250620cefd83ca967387a5844a7bde2"
+  integrity sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==
   dependencies:
     "@types/chai" "^4.3.4"
     "@types/chai-subset" "^1.3.3"
     "@types/node" "*"
-    "@vitest/expect" "0.28.3"
-    "@vitest/runner" "0.28.3"
-    "@vitest/spy" "0.28.3"
-    "@vitest/utils" "0.28.3"
+    "@vitest/expect" "0.28.4"
+    "@vitest/runner" "0.28.4"
+    "@vitest/spy" "0.28.4"
+    "@vitest/utils" "0.28.4"
     acorn "^8.8.1"
     acorn-walk "^8.2.0"
     cac "^6.7.14"
@@ -7937,7 +7937,7 @@ vitest@0.28.3:
     tinypool "^0.3.1"
     tinyspy "^1.0.2"
     vite "^3.0.0 || ^4.0.0"
-    vite-node "0.28.3"
+    vite-node "0.28.4"
     why-is-node-running "^2.2.2"
 
 vscode-jsonrpc@6.0.0: