Forráskód Böngészése

Merge branch 'feature/V8-4063-post-mr-fixes' into develop

Olivier Massot 3 éve
szülő
commit
f6c66617d4

+ 1 - 1
pages/poc/[id].vue

@@ -78,7 +78,7 @@ const cancel = async () => {
   if (file.value === null) {
     throw new Error('File not found')
   }
-  if (em.isNewEntity(File, id.value)) {
+  if (em.isNewInstance(File, id.value)) {
     await em.delete(File, file.value)
   } else {
     em.reset(File, file.value)

+ 77 - 90
services/data/entityManager.ts

@@ -42,12 +42,12 @@ class EntityManager {
      * This in used internally to ensure the object is recognized as an ApiResource
      *
      * @param model
-     * @param entity
+     * @param instance
      * @protected
      */
     // noinspection JSMethodCanBeStatic
-    protected cast(model: typeof ApiResource, entity: ApiResource): ApiResource {
-        return new model(entity)
+    protected cast(model: typeof ApiResource, instance: ApiResource): ApiResource {
+        return new model(instance)
     }
 
     /**
@@ -81,35 +81,38 @@ class EntityManager {
     public newInstance(model: typeof ApiResource, properties: object = {}): ApiResource {
         const repository = this.getRepository(model)
 
-        let entity = repository.make(properties)
+        let instance = repository.make(properties)
 
-        // Keep track of the entity's model
-        entity.setModel(model)
+        // Keep track of the model
+        // TODO : attendre de voir si utile ou non
+        // instance.setModel(model)
 
         // @ts-ignore
         if (!properties.hasOwnProperty('id') || !properties.id) {
             // Object has no id yet, we give him a temporary one
-            entity.id = 'tmp' + uuid4()
+            instance.id = 'tmp' + uuid4()
         }
 
-        entity = repository.save(entity)
-
-        this.saveInitialState(model, entity)
-        return entity
+        return this.save(model, instance, true)
     }
 
     /**
-     * Save the entity into the store
+     * Save the model instance into the store
      *
      * @param model
-     * @param entity
+     * @param instance
+     * @param permanent Is the change already persisted in the datasource? If this is the case, the initial state of this
+     *                  record is also updated.
      */
-    public save(model: typeof ApiResource, entity: ApiResource): ApiResource {
-        return this.getRepository(model).save(entity)
+    public save(model: typeof ApiResource, instance: ApiResource, permanent: boolean = false): ApiResource {
+        if (permanent) {
+            this.saveInitialState(model, instance)
+        }
+        return this.getRepository(model).save(instance)
     }
 
     /**
-     * Find the entity into the store
+     * Find the model instance in the store
      * TODO: comment réagit la fonction si l'id n'existe pas?
      *
      * @param model
@@ -122,7 +125,7 @@ class EntityManager {
     }
 
     /**
-     * Fetch an Entity / ApiResource by its id, save it to the store and returns it
+     * Fetch an ApiModel / ApiResource by its id, save it to the store and returns it
      *
      * @param model  Model of the object to fetch
      * @param id   Id of the object to fetch
@@ -131,7 +134,7 @@ class EntityManager {
      */
     public async fetch(model: typeof ApiResource, id: number, forceRefresh: boolean = false): Promise<ApiResource> {
 
-        // If the entity is already in the store and forceRefresh is false, return the object in store
+        // If the model instance is already in the store and forceRefresh is false, return the object in store
         if (!forceRefresh) {
             const item = this.find(model, id)
             if (item && typeof item !== 'undefined') {
@@ -149,7 +152,7 @@ class EntityManager {
     }
 
     /**
-     * Fetch a collection of entity
+     * Fetch a collection of model instances
      * The content of `query` is converted into a query-string in the request URL
      *
      * @param model
@@ -187,61 +190,44 @@ class EntityManager {
     }
 
     /**
-     * Créé une entité à partir d'une réponse de l'api au format Hydra, l'enregistre
-     * dans le store et la retourne
-     *
-     * @param model
-     * @param response
-     * @protected
-     */
-    protected async saveResponseAsEntity(model: typeof ApiModel, response: Response) {
-        const repository = this.getRepository(model)
-
-        const hydraResponse = await HydraDenormalizer.denormalize(response)
-        const returnedEntity = this.newInstance(model, hydraResponse.data)
-
-        this.saveInitialState(model, returnedEntity)
-
-        // Save data into the store
-        repository.save(returnedEntity)
-
-        return returnedEntity
-    }
-
-    /**
-     * Persist the entity as it is in the store into the data source via the API
+     * Persist the model instance as it is in the store into the data source via the API
      *
      * @param model
-     * @param entity
+     * @param instance
      */
-    public async persist(model: typeof ApiModel, entity: ApiModel) {
+    public async persist(model: typeof ApiModel, instance: ApiModel) {
         // Recast in case class definition has been "lost"
-        entity = this.cast(model, entity)
+        // TODO: attendre de voir si cette ligne est nécessaire
+        // instance = this.cast(model, instance)
 
         let url = UrlUtils.join('api', model.entity)
         let response
 
-        const data: any = entity.$toJson()
+        const data: any = instance.$toJson()
 
-        if (!entity.isNew()) {
-            url = UrlUtils.join(url, String(entity.id))
+        if (!instance.isNew()) {
+            url = UrlUtils.join(url, String(instance.id))
             response = await this.apiRequestService.put(url, data)
         } else {
             delete data.id
             response = await this.apiRequestService.post(url, data)
         }
 
-        const createdEntity = this.saveResponseAsEntity(model, response)
+        const hydraResponse = await HydraDenormalizer.denormalize(response)
+
+        const newInstance = this.newInstance(model, hydraResponse.data)
 
-        if (entity.isNew()) {
-            this.removeTempAfterPersist(model, entity.id)
+        // Si l'instance était nouvelle avant d'être persistée, elle vient désormais de recevoir un id définitif. On
+        // peut donc supprimer l'instance temporaire.
+        if (instance.isNew()) {
+            this.removeTempAfterPersist(model, instance.id)
         }
 
-        return createdEntity
+        return newInstance
     }
 
     /**
-     * Send an update request (PUT) to the API with the given data on an existing entity
+     * Send an update request (PUT) to the API with the given data on an existing model instance
      *
      * @param model
      * @param id
@@ -253,44 +239,45 @@ class EntityManager {
         const body = JSON.stringify(data)
         const response = await this.apiRequestService.put(url, body)
 
-        return this.saveResponseAsEntity(model, response)
+        const hydraResponse = await HydraDenormalizer.denormalize(response)
+        return this.newInstance(model, hydraResponse.data)
     }
 
     /**
-     * Delete the entity from the datasource via the API
+     * Delete the model instance from the datasource via the API
      *
      * @param model
-     * @param entity
+     * @param instance
      */
-    public async delete(model: typeof ApiModel, entity: ApiResource) {
+    public async delete(model: typeof ApiModel, instance: ApiResource) {
         const repository = this.getRepository(model)
 
         // If object has been persisted to the datasource, send a delete request
-        if (!entity.isNew()) {
-            const url = UrlUtils.join('api', model.entity, String(entity.id))
+        if (!instance.isNew()) {
+            const url = UrlUtils.join('api', model.entity, String(instance.id))
             await this.apiRequestService.delete(url)
         }
 
         // reactiveUpdate the store
-        repository.destroy(entity.id)
+        repository.destroy(instance.id)
     }
 
     /**
-     * Reset the entity to its initial state (i.e. the state it had when it was fetched from the API)
+     * Reset the model instance to its initial state (i.e. the state it had when it was fetched from the API)
      *
      * @param model
-     * @param entity
+     * @param instance
      */
-    public reset(model: typeof ApiResource, entity: ApiResource) {
-        const initialEntity = this.getInitialStateOf(model, entity.id)
-        if (initialEntity === null) {
-            throw new Error('no initial state recorded for this object - abort [' + model.entity + '/' + entity.id + ']')
+    public reset(model: typeof ApiResource, instance: ApiResource) {
+        const initialInstance = this.getInitialStateOf(model, instance.id)
+        if (initialInstance === null) {
+            throw new Error('no initial state recorded for this object - abort [' + model.entity + '/' + instance.id + ']')
         }
 
         const repository = this.getRepository(model)
-        repository.save(initialEntity)
+        repository.save(initialInstance)
 
-        return initialEntity
+        return initialInstance
     }
 
     /**
@@ -319,15 +306,15 @@ class EntityManager {
     }
 
     /**
-     * Is the entity a new one, or does it already exist in the data source (=API)
+     * Is the model instance a new one, or does it already exist in the data source (=API)
      *
-     * This is a convenient way of testing an entity you did not already fetch, else prefer the use of the
+     * This is a convenient way of testing a model instance you did not already fetch, else prefer the use of the
      * isNew() method of ApiResource
      *
      * @param model
      * @param id
      */
-    public isNewEntity(model: typeof ApiModel, id: number | string): boolean {
+    public isNewInstance(model: typeof ApiModel, id: number | string): boolean {
         const repository = this.getRepository(model)
 
         const item = repository.find(id)
@@ -341,24 +328,24 @@ class EntityManager {
     }
 
     /**
-     * Save the state of the entity in the store, so this state could be be restored later
+     * Save the state of the model instance in the store, so this state could be restored later
      *
      * @param model
-     * @param entity
+     * @param instance
      * @private
      */
-    protected saveInitialState(model: typeof ApiResource, entity: ApiResource) {
+    protected saveInitialState(model: typeof ApiResource, instance: ApiResource) {
         const repository = this.getRepository(model)
 
         // Clone and prefix id
-        const clone = _.cloneDeep(entity)
+        const clone = _.cloneDeep(instance)
         clone.id = this.CLONE_PREFIX + clone.id
 
         repository.save(clone)
     }
 
     /**
-     * Return the saved state of the entity from the store
+     * Return the saved state of the model instance from the store
      *
      * @param model
      * @param id
@@ -368,39 +355,39 @@ class EntityManager {
         const repository = this.getRepository(model)
 
         // Find the clone by id
-        const entity = repository.find(this.CLONE_PREFIX + id)
-        if (entity === null) {
+        const instance = repository.find(this.CLONE_PREFIX + id)
+        if (instance === null) {
             return null
         }
 
         // Restore the initial id
-        entity.id = id
-        return entity
+        instance.id = id
+        return instance
     }
 
     /**
-     * Delete the temporary entity from the repo after it was persisted via the api, replaced by the entity
+     * Delete the temporary model instance from the repo after it was persisted via the api, replaced by the instance
      * that has been returned by the api with is definitive id.
      *
      * @param model
-     * @param tempEntityId
+     * @param tempInstanceId
      * @private
      */
-    protected removeTempAfterPersist(model: typeof ApiResource, tempEntityId: number | string) {
+    protected removeTempAfterPersist(model: typeof ApiResource, tempInstanceId: number | string) {
         const repository = this.getRepository(model)
 
-        const entity = repository.find(tempEntityId)
-        if (!entity || typeof entity === 'undefined') {
+        const instance = repository.find(tempInstanceId)
+        if (!instance || typeof instance === 'undefined') {
             // TODO: il vaudrait peut-être mieux lever une erreur ici?
-            console.error(model.entity + '/' + tempEntityId + ' does not exist!')
+            console.error(model.entity + '/' + tempInstanceId + ' does not exist!')
             return
         }
-        if (!entity.isNew()) {
-            throw new Error('Error: Can not remove a non-temporary entity')
+        if (!instance.isNew()) {
+            throw new Error('Error: Can not remove a non-temporary model instance')
         }
 
-        repository.destroy(tempEntityId)
-        repository.destroy(this.CLONE_PREFIX + tempEntityId)
+        repository.destroy(tempInstanceId)
+        repository.destroy(this.CLONE_PREFIX + tempInstanceId)
     }
 }
 

+ 0 - 36
services/encoder/yamlEncoder.ts

@@ -1,36 +0,0 @@
-import {dump, load} from 'js-yaml';
-import {AnyJson} from "~/types/data";
-import {Encoder} from "~/types/interfaces";
-
-/**
- * Encodage / décodage de données au format Yaml
- */
-class YamlEncoder implements Encoder {
-  //   TODO: voir si toujours utile de le garder? ou peut-être en faire un YamlUtils et ajouter la fonction 'readFile'
-  //       utilisée dans le abilityBuilder
-  /**
-   * Convertit l'objet suivant au format Yaml
-   *
-   * @param {AnyJson} data
-   * @return {AnyJson}
-   */
-  public encode (data: AnyJson): string {
-      return dump(data)
-  }
-
-  /**
-   * Parse une chaine de caractères au format Yaml et
-   * retourne son équivalent sous forme d'objet
-   *
-   * @param {AnyJson} data
-   * @return {AnyJson}
-   */
-  public decode (data: string): AnyJson | null {
-      if (!data) {
-          return null
-      }
-      return load(data) as AnyJson
-  }
-}
-
-export default new YamlEncoder()

+ 46 - 43
services/rights/roleUtils.ts

@@ -42,9 +42,16 @@ const rolesToChange: Array<string> = [
   'ROLE_ONLINEREGISTRATION_ADMINISTRATION_VIEW'
 ]
 
+const actions = ['VIEW', 'MANAGE', 'REFERENCE', 'CORE']
+
 const actionMap: AnyJson = {
   '': 'manage',
-  _VIEW: 'read'
+  'VIEW': 'read'
+}
+
+interface Role {
+  subject: string
+  action: 'VIEW' | 'MANAGE' | 'REFERENCE' | ''
 }
 
 /**
@@ -63,7 +70,7 @@ class RoleUtils {
     if (!profileName.match(/^[A-Z_]+$/)) {
       throw new Error('invalid role name')
     }
-    // TODO: actuellement, passer un profil ne corresondant à aucun rôle ne lèvera aucune erreur, et se contentera de
+    // TODO: actuellement, passer un profil ne correspondant à aucun rôle ne lèvera aucune erreur, et se contentera de
     //       retourner false; ce serait pas mal de lever une erreur, ce ne serait pas normal de demander un rôle inexistant
     return roles.includes('ROLE_' + profileName + '_CORE')
   }
@@ -81,30 +88,38 @@ class RoleUtils {
   }
 
   /**
-   * Fix en attendant la migration complète, quelques rôles disposent d'underscore en trop, on corrige cela...
+   * Parse une chaine de caractère décrivant un rôle applicatif
    *
-   * TODO: remove after complete migration
-   *
-   * @param {Array<string>} roles
-   * @return {Array<string>}
+   * @param role
    */
-  static transformUnderscoreToHyphen (roles: Array<string>): Array<string> {
-    // TODO: clarifier le fonctionnement de cette méthode et de la regex, qu'est-ce qui sépare le groupe 2 et 3 au final?
-    const regex = /(ROLE_)([A-Z]*_[A-Z]*)([A-Z_]*)*/i
-
-    let match
-    roles = roles.map((role) => {
-      if (rolesToChange.includes(role)) {
-        if ((match = regex.exec(role)) !== null) {
-          const role = match[1]
-          const subject = match[2].replace('_', '-')
-          const action = match[3]
-          return role + subject + (action || '')
-        }
-      }
-      return role
-    })
-    return roles
+  static parseRole(role: string): Role {
+    const parts = role.split('_')
+
+    if (parts[0] !== 'ROLE') {
+      throw new Error('can not parse role')
+    }
+    parts.shift()
+
+    let action: 'VIEW' | 'MANAGE' | 'REFERENCE' | '' = ''
+    if (actions.includes(parts.at(-1) ?? '')) {
+      // @ts-ignore
+      action = parts.pop() ?? ''
+    }
+
+    const subject = parts.join('-')
+
+    return { subject, action }
+  }
+
+  static roleToString(role: Role) {
+    return ['ROLE', role.subject, role.action].filter((s: string) => s.length > 0).join('_')
+  }
+
+  static roleToAbility(role: Role): AbilitiesType {
+    return {
+      action: actionMap[role.action],
+      subject: role.subject.toLowerCase()
+    }
   }
 
   /**
@@ -121,25 +136,13 @@ class RoleUtils {
   static rolesToAbilities (roles: Array<string>): [] | Array<AbilitiesType> {
     const abilities:Array<AbilitiesType> = []
 
-    roles = RoleUtils.transformUnderscoreToHyphen(roles)
-
-    // TODO: on pourrait peut-être faciliter la lecture en réécrivant la regex en `ROLE_([A-Z-]*)(_[A-Z]+)?`, ou
-    //       même en faisant un simple split sur le '_'
-    const regex = /(ROLE_)([A-Z-]*)([_A-Z]*)/i
-    let match
-
-    _.each(roles, (role) => {
-      if ((match = regex.exec(role)) !== null) {
-        const subject = match[2]
-        const actionName = match[3] ?? ''
-        const action = actionMap[actionName]
-
-        if (subject && typeof action !== 'undefined') {
-          abilities.push({
-            action: action,
-            subject: subject.toLowerCase()
-          })
-        }
+    _.each(roles, (role: string) => {
+      const parsed: Role | null= RoleUtils.parseRole(role)
+      const ability = RoleUtils.roleToAbility(parsed)
+
+      // @ts-ignore
+      if (ability.subject && typeof ability.action !== 'undefined') {
+        abilities.push(ability)
       }
     })
 

+ 8 - 4
services/utils/urlUtils.ts

@@ -56,18 +56,22 @@ class UrlUtils {
 
   /**
    * Extrait l'ID de l'URI passée en paramètre
-   * L'URI est supposée être de la forme `.../foo/bar/{id}`, où l'id est un identifiant numérique
+   * L'URI est supposée être de la forme `.../foo/bar/{id}`,
+   * où l'id est un identifiant numérique, à moins que isLiteral soit défini comme vrai.
+   * Dans ce cas, si isLiteral est vrai, l'id sera retourné sous forme de texte.
+   *
    *
    * @param uri
+   * @param isLiteral
    */
-  public static extractIdFromUri (uri: string): number|null {
+  public static extractIdFromUri (uri: string, isLiteral: boolean = false): number|string|null {
     const partUri: Array<string> = uri.split('/')
     const id: any = partUri.pop()
 
-    if (!id || isNaN(id)) {
+    if (!id || (!isLiteral && isNaN(id))) {
       throw new Error('no id found')
     }
-    return parseInt(id)
+    return isLiteral ? id : parseInt(id)
   }
 
   /**

+ 1 - 1
stores/sse.ts

@@ -15,7 +15,7 @@ export const useSseStore = defineStore('sse', () => {
     switch (event.operation) {
       case "update":
       case "create":
-        await em.persist(model, JSON.parse(event.data))
+        await em.save(model, JSON.parse(event.data), true)
         break
 
       case "delete":

+ 0 - 57
tests/readme.md

@@ -1,57 +0,0 @@
-# Tests unitaires
-
-Ce répertoire contient les tests unitaires du front Vue.js
-
-> Plus d'infos:
-> 
-> * https://vuejs.org/guide/scaling-up/testing.html#unit-testing
-> * https://test-utils.vuejs.org/
-> * https://plugins.jetbrains.com/plugin/19220-vitest-runner
-
-## Exécuter les tests
-
-Pour exécuter les tests, lancer :
-
-    yarn test
-
-## Mocking
-
-#### Différentes façons pour Mocker
-
-    import axios from 'axios'
-    import { NuxtAxiosInstance } from '@nuxtjs/axios'
-    jest.mock('axios')
-    const mockNuxtAxios = axios as jest.Mocked<NuxtAxiosInstance>
-    mockNuxtAxios.$get = jest.fn().mockReturnValue({})
-
-Ou
-
-    import DataProvider from "~/services/dataProvider/dataProvider";
-    jest.mock('~/services/dataProvider/dataProvider');
-    const DataProviderMock = DataProvider as jest.MockedClass<typeof DataProvider>;
-    DataProviderMock.prototype.invoke.mockImplementation(async () => true)
-
-Ou
-
-    import VueI18n from "vue-i18n";
-    const VueI18nMock = VueI18n as jest.MockedClass<typeof VueI18n>;
-    VueI18nMock.prototype.t = jest.fn().mockReturnValue('siret_error')
-
-Ou, si on définit le fichier ~/services/dataProvider/__mocks__/dataProvider.ts avec l'implémenation de invoke :
-
-    const mock = jest.fn().mockImplementation(() => {
-        return {invoke: () => {
-            return true
-        }};
-    });
-    export default mock;
-
-Alors on appelle useHandleSiret comme cela : 
-
-    const siretResponse = UseChecker.useHandleSiret(new DataProviderMock())
-
-
-ATTENTION : dans ce cas cette déclaration sera prioritaire sur les autres façons de mocker l'implémentation et on ne peut pas avoir 2 types de mock différents pour
-cette classe dans l'application.
-
-Pour pouvoir gérer l'implémentation à la volée, il ne faut pas qu'il y ait le fichier dans le dossier __mocks__

+ 11 - 14
tests/units/services/data/apiRequestService.test.ts

@@ -4,25 +4,13 @@ import {$Fetch, FetchOptions} from "ohmyfetch";
 import {HTTP_METHOD} from "~/types/enum/data";
 import {AssociativeArray} from "~/types/data";
 
-class TestableApiRequestService extends ApiRequestService {
-    public async request(
-        method: HTTP_METHOD,
-        url: string,
-        body: string | null = null,
-        params: AssociativeArray | null = null,
-        query: AssociativeArray | null = null
-    ): Promise<Response> {
-        return super.request(method, url, body, params, query)
-    }
-}
-
 let fetcher: $Fetch
-let apiRequestService: TestableApiRequestService
+let apiRequestService: ApiRequestService
 
 beforeEach(() => {
     // @ts-ignore
     fetcher = vi.fn((url: string, config: FetchOptions) => 'fetch_response') as $Fetch
-    apiRequestService = new TestableApiRequestService(fetcher)
+    apiRequestService = new ApiRequestService(fetcher)
 })
 
 const mockedRequestMethod = (
@@ -41,6 +29,7 @@ describe('get', () => {
         const result = await apiRequestService.get('https://myapi.com/api/item', { a: 1 })
 
         expect(result).toEqual('a_response')
+        // @ts-ignore
         expect(apiRequestService.request).toHaveBeenCalledWith(
             HTTP_METHOD.GET, 'https://myapi.com/api/item', null, null, { a: 1 }
         )
@@ -60,6 +49,7 @@ describe('post', () => {
         )
 
         expect(result).toEqual('a_response')
+        // @ts-ignore
         expect(apiRequestService.request).toHaveBeenCalledWith(
             HTTP_METHOD.POST,
             'https://myapi.com/api/item',
@@ -83,6 +73,7 @@ describe('put', () => {
         )
 
         expect(result).toEqual('a_response')
+        // @ts-ignore
         expect(apiRequestService.request).toHaveBeenCalledWith(
             HTTP_METHOD.PUT,
             'https://myapi.com/api/item',
@@ -104,6 +95,7 @@ describe('delete', () => {
         )
 
         expect(result).toEqual('a_response')
+        // @ts-ignore
         expect(apiRequestService.request).toHaveBeenCalledWith(
             HTTP_METHOD.DELETE,
             'https://myapi.com/api/item',
@@ -117,6 +109,7 @@ describe('delete', () => {
 describe('request', () => {
     test('simple call', async () => {
 
+        // @ts-ignore
         const result = await apiRequestService.request(HTTP_METHOD.GET, 'https://myapi.com/api/item')
 
         expect(result).toEqual('fetch_response')
@@ -126,6 +119,7 @@ describe('request', () => {
 
     test('post with body', async () => {
 
+        // @ts-ignore
         const result = await apiRequestService.request(HTTP_METHOD.POST, 'https://myapi.com/api/item', 'a_body')
 
         expect(result).toEqual('fetch_response')
@@ -135,6 +129,7 @@ describe('request', () => {
 
     test('put with body', async () => {
 
+        // @ts-ignore
         const result = await apiRequestService.request(HTTP_METHOD.PUT, 'https://myapi.com/api/item', 'a_body')
 
         expect(result).toEqual('fetch_response')
@@ -144,6 +139,7 @@ describe('request', () => {
 
     test('get : body must be ignored even if provided', async () => {
 
+        // @ts-ignore
         const result = await apiRequestService.request(HTTP_METHOD.GET, 'https://myapi.com/api/item', 'a_body')
 
         expect(result).toEqual('fetch_response')
@@ -152,6 +148,7 @@ describe('request', () => {
     })
 
     test('with query and params', async () => {
+        // @ts-ignore
         const result = await apiRequestService.request(
             HTTP_METHOD.PUT,
             'https://myapi.com/api/item',

+ 58 - 79
tests/units/services/data/entityManager.test.ts

@@ -1,19 +1,11 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, 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 {ApiResponse} from "~/types/data";
-import HydraDenormalizer from "~/services/data/normalizer/hydraDenormalizer";
-
-class TestableEntityManager extends EntityManager {
-    public cast(model: typeof ApiResource, entity: ApiResource): ApiResource { return super.cast(model, entity) }
-    public async saveResponseAsEntity(model: typeof ApiModel, response: Response) { return super.saveResponseAsEntity(model, response) }
-    public saveInitialState(model: typeof ApiResource, entity: ApiResource) { return super.saveInitialState(model, entity) }
-    public getInitialStateOf(model: typeof ApiResource, id: string | number): ApiResource | null { return super.getInitialStateOf(model, id) }
-    public removeTempAfterPersist(model: typeof ApiResource, tempEntityId: number | string) { return super.removeTempAfterPersist(model, tempEntityId) }
-}
+
+
 
 class DummyApiResource extends ApiResource {
     static entity = 'dummyResource'
@@ -24,16 +16,31 @@ class DummyApiModel extends ApiModel {
     static entity = 'dummyModel'
 }
 
+let _console: any = {
+    'log': console.log,
+    'warn': console.warn,
+    'error': console.error,
+}
+
 let apiRequestService: ApiRequestService
-let entityManager: TestableEntityManager
+let entityManager: EntityManager
+let repo: Repository<ApiResource>
 
 beforeEach(() => {
     // @ts-ignore
     apiRequestService = vi.fn() as ApiRequestService
 
-    entityManager = new TestableEntityManager(apiRequestService)
+    entityManager = new EntityManager(apiRequestService)
+
+    // @ts-ignore
+    repo = vi.fn() as Repository<ApiResource>
+})
 
-    // TODO: s'assurer que les mocks globaux sont bien réinitialisés après les tests, en particulier les fonctions de console
+afterEach(() => {
+    // Reset console methods after mock
+    console.log = _console['log']
+    console.warn = _console['warn']
+    console.error = _console['error']
 })
 
 describe('getRepository', () => {
@@ -63,7 +70,7 @@ describe('getModelFromIri', () => {
 
         expect(result).toEqual(DummyApiResource)
     })
-    test('invalide Iri', () => {
+    test('invalid Iri', () => {
         expect(() => entityManager.getModelFromIri('/invalid')).toThrowError('cannot parse the IRI')
     })
 })
@@ -73,19 +80,13 @@ describe('newInstance', () => {
     test('simple call', () => {
         const properties = { 'id': 1 }
 
-        // @ts-ignore
-        const repo = vi.fn() as Repository<ApiResource>
-
         // @ts-ignore
         entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
             return model === DummyApiResource ? repo : null
         })
 
-        entityManager.saveInitialState = vi.fn((model: typeof ApiResource, entity: ApiResource) => null)
-
         // @ts-ignore
         const entity = new DummyApiResource(properties)
-        entity.setModel = vi.fn((model: typeof ApiResource) => null)
 
         // @ts-ignore
         repo.make = vi.fn((properties: object) => {
@@ -93,15 +94,14 @@ describe('newInstance', () => {
             entity.id = properties.id
             return entity
         })
+
         // @ts-ignore
-        repo.save = vi.fn((record: Element) => entity)
+        entityManager.save = vi.fn((model: typeof ApiResource, entity: ApiResource, permanent: boolean) => entity)
 
         const result = entityManager.newInstance(DummyApiResource, properties)
 
         expect(repo.make).toHaveBeenCalledWith(properties)
-        expect(entity.setModel).toHaveBeenCalledWith(DummyApiResource)
-        expect(repo.save).toHaveBeenCalledWith(entity)
-        expect(entityManager.saveInitialState).toHaveBeenCalledWith(DummyApiResource, entity)
+        expect(entityManager.save).toHaveBeenCalledWith(DummyApiResource, entity, true)
 
         expect(result.id).toEqual(properties.id)
     })
@@ -117,6 +117,7 @@ describe('newInstance', () => {
             return model === DummyApiResource ? repo : null
         })
 
+        // @ts-ignore
         entityManager.saveInitialState = vi.fn((model: typeof ApiResource, entity: ApiResource) => null)
 
         // @ts-ignore
@@ -374,61 +375,25 @@ describe('fetchCollection', () => {
     })
 })
 
-describe('saveResponseAsEntity', () => {
-    test('simple call', async () => {
-
-        const entity = new DummyApiModel({id: 1})
-
-        // @ts-ignore
-        const repo = vi.fn() as Repository<ApiResource>
-
-        // @ts-ignore
-        entityManager.getRepository = vi.fn((model: typeof ApiResource) => {
-            return model === DummyApiModel ? repo : null
-        })
-
-        // @ts-ignore
-        const response = {id: 1} as Response
-
-        entityManager.newInstance = vi.fn((model: typeof ApiResource, properties: object) => {
-            return entity
-        })
-
-        entityManager.saveInitialState = vi.fn((model: typeof ApiResource, entity: ApiResource) => null)
-
-        // @ts-ignore
-        repo.save = vi.fn((data: any) => null)
-
-        const result = await entityManager.saveResponseAsEntity(DummyApiModel, response)
-
-        expect(entityManager.getRepository).toHaveBeenCalledWith(DummyApiModel)
-        expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, {id: 1})
-        expect(entityManager.saveInitialState).toHaveBeenCalledWith(DummyApiModel, entity)
-        expect(repo.save).toHaveBeenCalledWith(entity)
-
-        expect(result).toEqual(entity)
-    })
-})
-
-
 describe('persist', () => {
     test('new entity (POST)', async () => {
-        const entity = new DummyApiModel({id: 'tmp1', name: 'bob'})
-        entity.isNew = vi.fn(() => true)
+        const instance = new DummyApiModel({id: 'tmp1', name: 'bob'})
+        instance.isNew = vi.fn(() => true)
 
         // @ts-ignore
-        entity.$toJson = vi.fn(() => {
+        instance.$toJson = vi.fn(() => {
             return {id: 'tmp1', name: 'bob'}
         })
 
-        entityManager.cast = vi.fn((model: typeof ApiResource, entity: ApiResource): ApiResource => entity)
+        // TODO: attendre de voir si cet appel est nécessaire dans l'entity manager
+        // entityManager.cast = vi.fn((model: typeof ApiResource, entity: ApiResource): ApiResource => entity)
 
         const response = { id: 1, name: 'bob' }
         // @ts-ignore
         apiRequestService.post = vi.fn((url, data) => response)
 
         // @ts-ignore
-        entityManager.saveResponseAsEntity = vi.fn((model, response) => {
+        entityManager.newInstance = vi.fn((model, response) => {
             const newEntity = new DummyApiModel(response)
             // @ts-ignore
             newEntity.id = response.id
@@ -438,14 +403,16 @@ describe('persist', () => {
             return newEntity
         })
 
+        // @ts-ignore
         entityManager.removeTempAfterPersist = vi.fn()
 
-        const result = await entityManager.persist(DummyApiModel, entity)
+        const result = await entityManager.persist(DummyApiModel, instance)
 
         // temp id should have been purged from the posted data
         expect(apiRequestService.post).toHaveBeenCalledWith('api/dummyModel', {name: 'bob'})
-        expect(entityManager.saveResponseAsEntity).toHaveBeenCalledWith(DummyApiModel, response)
-        expect(entityManager.removeTempAfterPersist).toHaveBeenCalledWith(DummyApiModel, entity.id)
+        expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, response)
+        // @ts-ignore
+        expect(entityManager.removeTempAfterPersist).toHaveBeenCalledWith(DummyApiModel, instance.id)
 
         expect(result.id).toEqual(1)
         expect(result.name).toEqual('bob')
@@ -460,13 +427,14 @@ describe('persist', () => {
         // @ts-ignore
         entity.$toJson = vi.fn(() => props)
 
-        entityManager.cast = vi.fn((model: typeof ApiResource, entity: ApiResource): ApiResource => entity)
+        // TODO: attendre de voir si cet appel est nécessaire dans l'entity manager
+        // entityManager.cast = vi.fn((model: typeof ApiResource, entity: ApiResource): ApiResource => entity)
 
         // @ts-ignore
         apiRequestService.put = vi.fn((url, data) => props)
 
         // @ts-ignore
-        entityManager.saveResponseAsEntity = vi.fn((model, response) => {
+        entityManager.newInstance = vi.fn((model, response) => {
             const newEntity = new DummyApiModel(response)
             // @ts-ignore
             newEntity.id = response.id
@@ -476,12 +444,14 @@ describe('persist', () => {
             return newEntity
         })
 
+        // @ts-ignore
         entityManager.removeTempAfterPersist = vi.fn()
 
         const result = await entityManager.persist(DummyApiModel, entity)
 
         expect(apiRequestService.put).toHaveBeenCalledWith('api/dummyModel/1', {id: 1, name: 'bob'})
-        expect(entityManager.saveResponseAsEntity).toHaveBeenCalledWith(DummyApiModel, props)
+        expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, props)
+        // @ts-ignore
         expect(entityManager.removeTempAfterPersist).toHaveBeenCalledTimes(0)
 
         expect(result.id).toEqual(1)
@@ -498,7 +468,7 @@ describe('patch', () => {
         apiRequestService.put = vi.fn((url, data) => props)
 
         // @ts-ignore
-        entityManager.saveResponseAsEntity = vi.fn((model, response) => {
+        entityManager.newInstance = vi.fn((model, response) => {
             const newEntity = new DummyApiModel(response)
             // @ts-ignore
             newEntity.id = response.id
@@ -511,7 +481,7 @@ describe('patch', () => {
         const result = await entityManager.patch(DummyApiModel, 1, {name: 'bobby'})
 
         expect(apiRequestService.put).toHaveBeenCalledWith('api/dummyModel/1', '{"name":"bobby"}')
-        expect(entityManager.saveResponseAsEntity).toHaveBeenCalledWith(DummyApiModel, {id: 1, name: 'bobby'})
+        expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, {id: 1, name: 'bobby'})
 
         expect(result.id).toEqual(1)
         expect(result.name).toEqual('bobby')
@@ -582,6 +552,7 @@ describe('reset', () => {
         initialEntity.id = 1
         initialEntity.name = 'serges'
 
+        // @ts-ignore
         entityManager.getInitialStateOf = vi.fn((model: typeof ApiResource, id: string | number) => initialEntity)
 
         // @ts-ignore
@@ -597,6 +568,7 @@ describe('reset', () => {
 
         const result = entityManager.reset(DummyApiModel, entity)
 
+        // @ts-ignore
         expect(entityManager.getInitialStateOf).toHaveBeenCalledWith(DummyApiModel, 1)
         expect(repo.save).toHaveBeenCalledWith(initialEntity)
         expect(result).toEqual(initialEntity)
@@ -606,6 +578,7 @@ describe('reset', () => {
         const entity = new DummyApiModel()
         entity.id = 1
 
+        // @ts-ignore
         entityManager.getInitialStateOf = vi.fn((model: typeof ApiResource, id: string | number) => null)
 
         expect(() => entityManager.reset(DummyApiModel, entity)).toThrowError(
@@ -648,7 +621,7 @@ describe('isNewEntity', () => {
         // @ts-ignore
         repo.find = vi.fn((id: number) => entity)
 
-        const result = entityManager.isNewEntity(DummyApiModel, 1)
+        const result = entityManager.isNewInstance(DummyApiModel, 1)
 
         expect(result).toBeTruthy()
     })
@@ -668,7 +641,7 @@ describe('isNewEntity', () => {
         // @ts-ignore
         repo.find = vi.fn((id: number) => entity)
 
-        const result = entityManager.isNewEntity(DummyApiModel, 1)
+        const result = entityManager.isNewInstance(DummyApiModel, 1)
 
         expect(result).toBeFalsy()
     })
@@ -687,7 +660,7 @@ describe('isNewEntity', () => {
 
         console.error = vi.fn()
 
-        const result = entityManager.isNewEntity(DummyApiModel, 1)
+        const result = entityManager.isNewInstance(DummyApiModel, 1)
 
         expect(result).toBeFalsy()
 
@@ -711,6 +684,7 @@ describe('saveInitialState', () => {
         // @ts-ignore
         repo.save = vi.fn((record: Element) => null)
 
+        // @ts-ignore
         entityManager.saveInitialState(DummyApiResource, entity)
 
         expect(repo.save).toHaveBeenCalledWith({ id: '_clone_1', name: 'bob' })
@@ -737,6 +711,7 @@ describe('getInitialStateOf', () => {
             return { id: 1, name: 'robert' } as DummyApiResource
         })
 
+        // @ts-ignore
         const result = entityManager.getInitialStateOf(DummyApiResource, 1) as DummyApiResource
 
         expect(repo.find).toHaveBeenCalledWith('_clone_1')
@@ -756,6 +731,7 @@ describe('getInitialStateOf', () => {
         // @ts-ignore
         repo.find = vi.fn((id: number | string) => null)
 
+        // @ts-ignore
         const result = entityManager.getInitialStateOf(DummyApiResource, 1) as DummyApiResource
 
         expect(repo.find).toHaveBeenCalledWith('_clone_1')
@@ -784,6 +760,7 @@ describe('removeTempAfterPersist', () => {
         // @ts-ignore
         repo.destroy = vi.fn()
 
+        // @ts-ignore
         entityManager.removeTempAfterPersist(DummyApiResource, 'tmp123')
 
         expect(repo.destroy).toHaveBeenCalledWith('tmp123')
@@ -810,8 +787,9 @@ describe('removeTempAfterPersist', () => {
         // @ts-ignore
         repo.destroy = vi.fn()
 
+        // @ts-ignore
         expect(() => entityManager.removeTempAfterPersist(DummyApiResource, 'tmp123')).toThrowError(
-            'Error: Can not remove a non-temporary entity'
+            'Error: Can not remove a non-temporary model instance'
         )
 
         expect(repo.destroy).toHaveBeenCalledTimes(0)
@@ -834,6 +812,7 @@ describe('removeTempAfterPersist', () => {
 
         console.error = vi.fn()
 
+        // @ts-ignore
         entityManager.removeTempAfterPersist(DummyApiResource, 'tmp123')
 
         expect(repo.destroy).toHaveBeenCalledTimes(0)

+ 9 - 7
tests/units/services/data/imageManager.test.ts

@@ -4,12 +4,7 @@ import ImageManager from "~/services/data/imageManager";
 import 'blob-polyfill';
 
 let apiRequestService: ApiRequestService
-let imageManager: TestableImageManager
-
-class TestableImageManager extends ImageManager {
-    public async toBase64(data: string) { return super.toBase64(data) }
-    public getCacheKey() { return super.getCacheKey() }
-}
+let imageManager: ImageManager
 
 let init_console_error: any
 
@@ -17,7 +12,7 @@ beforeEach(() => {
     // @ts-ignore
     apiRequestService = vi.fn() as ApiRequestService
 
-    imageManager = new TestableImageManager(apiRequestService)
+    imageManager = new ImageManager(apiRequestService)
 
     // Important : Restore console error after mocking
     init_console_error = console.error
@@ -35,6 +30,7 @@ describe('get', () => {
         // @ts-ignore
         apiRequestService.get = vi.fn((url: string) => data)
 
+        // @ts-ignore
         imageManager.getCacheKey = vi.fn(() => '123456')
 
         // @ts-ignore
@@ -61,6 +57,7 @@ describe('get', () => {
         // @ts-ignore
         apiRequestService.get = vi.fn((url: string) => '')
 
+        // @ts-ignore
         imageManager.getCacheKey = vi.fn(() => '123456')
         // @ts-ignore
         imageManager.toBase64 = vi.fn()
@@ -71,6 +68,7 @@ describe('get', () => {
 
         expect(apiRequestService.get).toHaveBeenCalledWith('api/download/1', ['123456'])
         expect(console.error).toHaveBeenCalledWith('Error: image 1 not found or invalid')
+        // @ts-ignore
         expect(imageManager.toBase64).toHaveBeenCalledTimes(0)
 
         expect(result).toEqual(ImageManager.defaultImage)
@@ -80,6 +78,7 @@ describe('get', () => {
         // @ts-ignore
         apiRequestService.get = vi.fn((url: string) => '')
 
+        // @ts-ignore
         imageManager.getCacheKey = vi.fn(() => '123456')
 
         // @ts-ignore
@@ -91,6 +90,7 @@ describe('get', () => {
 
         expect(apiRequestService.get).toHaveBeenCalledWith('api/download/1', ['123456'])
         expect(console.error).toHaveBeenCalledWith('Error: image 1 not found or invalid')
+        // @ts-ignore
         expect(imageManager.toBase64).toHaveBeenCalledTimes(0)
 
         expect(result).toEqual('some_default.jpg')
@@ -99,12 +99,14 @@ describe('get', () => {
 
 describe('toBase64', () => {
     test('simple call', async () => {
+        // @ts-ignore
         expect(await imageManager.toBase64('some_data')).toEqual('data:image/jpeg;base64,c29tZV9kYXRh')
     })
 })
 
 describe('getCacheKey', () => {
     test('simple call', () => {
+        // @ts-ignore
         expect(imageManager.getCacheKey()).toMatch(/\d{10,}/)
     })
 })

+ 18 - 15
tests/units/services/data/normalizer/hydraDenormalizer.test.ts

@@ -3,12 +3,6 @@ import {AnyJson, ApiResponse, HydraMetadata} from "~/types/data";
 import HydraDenormalizer from "~/services/data/normalizer/hydraDenormalizer";
 import {METADATA_TYPE} from "~/types/enum/data";
 
-class TestableHydraDenormalizer extends HydraDenormalizer {
-    public static denormalize(hydraData: AnyJson): ApiResponse { return super.denormalize(hydraData) }
-    public static getData(hydraData: AnyJson): AnyJson { return super.getData(hydraData) }
-    public static getMetadata(data: AnyJson): HydraMetadata { return super.getMetadata(data) }
-}
-
 describe('denormalize', () => {
 
     test('should parse a API Item response and return a JSON Object', () => {
@@ -28,8 +22,10 @@ describe('denormalize', () => {
 
         const result = HydraDenormalizer.denormalize(data)
 
-        expect(result.data).toEqual(TestableHydraDenormalizer.getData(data))
-        expect(result.metadata).toEqual(TestableHydraDenormalizer.getMetadata(data))
+        // @ts-ignore
+        expect(result.data).toEqual(HydraDenormalizer.getData(data))
+        // @ts-ignore
+        expect(result.metadata).toEqual(HydraDenormalizer.getMetadata(data))
 
         const expected = {
             "data": {
@@ -92,8 +88,10 @@ describe('denormalize', () => {
 
         const result = HydraDenormalizer.denormalize(data)
 
-        expect(result.data).toEqual(TestableHydraDenormalizer.getData(data))
-        expect(result.metadata).toEqual(TestableHydraDenormalizer.getMetadata(data))
+        // @ts-ignore
+        expect(result.data).toEqual(HydraDenormalizer.getData(data))
+        // @ts-ignore
+        expect(result.metadata).toEqual(HydraDenormalizer.getMetadata(data))
 
         const expected = JSON.stringify(
             {"data":[
@@ -144,7 +142,8 @@ describe('getData', () => {
             "hydra:member": [ 'foo' ],
         }
 
-        expect(TestableHydraDenormalizer.getData(data)).toEqual([ 'foo' ])
+        // @ts-ignore
+        expect(HydraDenormalizer.getData(data)).toEqual([ 'foo' ])
     })
 
     test('With item', () => {
@@ -155,7 +154,8 @@ describe('getData', () => {
             "param1": 'a',
         }
 
-        expect(TestableHydraDenormalizer.getData(data)).toEqual(data)
+        // @ts-ignore
+        expect(HydraDenormalizer.getData(data)).toEqual(data)
     })
 })
 
@@ -177,7 +177,8 @@ describe('getMetadata', () => {
             }
         }
 
-        const metadata = TestableHydraDenormalizer.getMetadata(data)
+        // @ts-ignore
+        const metadata = HydraDenormalizer.getMetadata(data)
 
         expect(metadata.totalItems).toEqual(10)
         expect(metadata.firstPage).toEqual(1)
@@ -200,7 +201,8 @@ describe('getMetadata', () => {
             }
         }
 
-        const metadata = TestableHydraDenormalizer.getMetadata(data)
+        // @ts-ignore
+        const metadata = HydraDenormalizer.getMetadata(data)
 
         expect(metadata.totalItems).toEqual(10)
         expect(metadata.firstPage).toEqual(1)
@@ -210,7 +212,8 @@ describe('getMetadata', () => {
     })
 
     test('With item metadata', () => {
-        const metadata = TestableHydraDenormalizer.getMetadata({})
+        // @ts-ignore
+        const metadata = HydraDenormalizer.getMetadata({})
         expect(metadata.type).toEqual(METADATA_TYPE.ITEM)
     })
 })

+ 0 - 23
tests/units/services/encoder/yamlDenormalizer.test.ts

@@ -1,23 +0,0 @@
-import {describe, test, expect} from "vitest";
-import YamlEncoder from "~/services/encoder/yamlEncoder";
-import {load} from "js-yaml";
-
-
-describe('encode', () => {
-    test('simple conversion', () => {
-        const result = YamlEncoder.encode({title: {a: 1, b: 2, c: ['foo', 'bar']}})
-        expect(result).toEqual(`title:\n  a: 1\n  b: 2\n  c:\n    - foo\n    - bar\n`)
-    })
-})
-
-describe('decode', () => {
-    test('with empty data', () => {
-        const result = YamlEncoder.decode("")
-
-        expect(result).toBeNull()
-    })
-    test('with data', () => {
-        const result = YamlEncoder.decode(`title:\n a: 1\n b: 2\n c: [foo, bar]`)
-        expect(result).toEqual({title: {a: 1, b: 2, c: ['foo', 'bar']}})
-    })
-})

+ 19 - 11
tests/units/services/layout/menuBuilder/mainMenuBuilder.test.ts

@@ -1,9 +1,9 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect } from 'vitest'
 import {RuntimeConfig} from "@nuxt/schema";
 import {AnyAbility} from "@casl/ability/dist/types";
 import {AccessProfile, organizationState} from "~/types/interfaces";
 import MainMenuBuilder from "~/services/layout/menuBuilder/mainMenuBuilder";
-import {MenuGroup, MenuItem} from "~/types/layout";
+import {MenuGroup} from "~/types/layout";
 import {MENU_LINK_TYPE} from "~/types/enum/layout";
 import AbstractMenuBuilder from "~/services/layout/menuBuilder/abstractMenuBuilder";
 import AccessMenuBuilder from "~/services/layout/menuBuilder/accessMenuBuilder";
@@ -23,14 +23,7 @@ let runtimeConfig: RuntimeConfig
 let ability: AnyAbility
 let organizationProfile: organizationState
 let accessProfile: AccessProfile
-let menuBuilder: TestableMainMenuBuilder
-
-class TestableMainMenuBuilder extends MainMenuBuilder {
-    public buildSubmenu(menuBuilder: typeof AbstractMenuBuilder) {
-        return super.buildSubmenu(menuBuilder)
-    }
-}
-
+let menuBuilder: MainMenuBuilder
 
 beforeEach(()=> {
     runtimeConfig = vi.fn() as any as RuntimeConfig
@@ -40,7 +33,7 @@ beforeEach(()=> {
 
     runtimeConfig.baseUrlAdminLegacy = 'https://mydomain.com/'
 
-    menuBuilder = new TestableMainMenuBuilder(runtimeConfig, ability, organizationProfile, accessProfile)
+    menuBuilder = new MainMenuBuilder(runtimeConfig, ability, organizationProfile, accessProfile)
 })
 
 describe('getMenuName', () => {
@@ -51,23 +44,36 @@ describe('getMenuName', () => {
 
 describe('build', () => {
     test('return all menus', () => {
+        // @ts-ignore
         menuBuilder.buildSubmenu = vi.fn((menuBuilder: typeof AbstractMenuBuilder): MenuGroup => {
             return { label: 'foo' }
         })
 
         const result = menuBuilder.build() as MenuGroup
 
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(AccessMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(AgendaMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(EquipmentMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(EducationalMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(BillingMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(CommunicationMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(DonorsMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(MedalsMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(WebsiteAdminMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(CotisationsMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(StatsMenuBuilder)
+        // @ts-ignore
         expect(menuBuilder.buildSubmenu).toBeCalledWith(Admin2iosMenuBuilder)
 
         expect(result.label).toEqual('main')
@@ -76,6 +82,7 @@ describe('build', () => {
     })
 
     test('return a single group', () => {
+        // @ts-ignore
         menuBuilder.buildSubmenu = vi.fn((menuBuilder: typeof AbstractMenuBuilder): MenuGroup | null => {
             if (menuBuilder.menuName === 'Access') {
                 return { label: 'submenu', children: [
@@ -94,6 +101,7 @@ describe('build', () => {
     })
 
     test('return null', () => {
+        // @ts-ignore
         menuBuilder.buildSubmenu = vi.fn((menuBuilder: typeof AbstractMenuBuilder): MenuGroup | null => {
             return  null
         })

+ 37 - 8
tests/units/services/rights/abilityBuilder.test.ts

@@ -1,18 +1,13 @@
-import { describe, test, it, expect } from 'vitest'
+import { describe, test, expect } from 'vitest'
 import {MongoAbility} from "@casl/ability/dist/types/Ability";
 import {AbilitiesType, AccessProfile, organizationState} from "~/types/interfaces";
 import AbilityBuilder from "~/services/rights/abilityBuilder";
 import {ABILITIES} from "~/types/enum/enums";
-import yaml from "yaml-import";
 
 let ability: MongoAbility
 let accessProfile: AccessProfile
 let organizationProfile: organizationState
-let abilityBuilder: TestableAbilityBuilder
-
-class TestableAbilityBuilder extends AbilityBuilder {
-    public execAndValidateCondition(condition: any, subject: string = '') { return super.execAndValidateCondition(condition, subject) }
-}
+let abilityBuilder: AbilityBuilder
 
 // Mock the content of the config yaml files
 // > This must be done in the global scope: https://vitest.dev/api/vi.html#vi-mock
@@ -49,7 +44,7 @@ beforeEach(() => {
     accessProfile = vi.fn() as any as AccessProfile
     organizationProfile = vi.fn() as any as organizationState
 
-    abilityBuilder = new TestableAbilityBuilder(ability, accessProfile, organizationProfile)
+    abilityBuilder = new AbilityBuilder(ability, accessProfile, organizationProfile)
 })
 
 describe('buildAbilities', () => {
@@ -149,6 +144,7 @@ describe('execAndValidateCondition', () => {
         })
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {
                     'function': 'accessHasAllRoleAbilities',
@@ -160,6 +156,7 @@ describe('execAndValidateCondition', () => {
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {
                     'function': 'accessHasAllRoleAbilities',
@@ -177,6 +174,7 @@ describe('execAndValidateCondition', () => {
         })
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {
                     'function': 'accessHasAnyRoleAbility',
@@ -188,6 +186,7 @@ describe('execAndValidateCondition', () => {
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'accessHasAnyRoleAbility', parameters: [{action: ABILITIES.READ, subject: 'subject2'}]})
         ).toBeFalsy()
@@ -199,18 +198,21 @@ describe('execAndValidateCondition', () => {
         accessProfile.isPayer = true
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'accessHasAnyProfile', parameters: ['guardian', 'payer']}
             )
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'accessHasAnyProfile', parameters: ['guardian', 'caMember']}
             )
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'accessHasAnyProfile', parameters: ['caMember']}
             )
@@ -224,18 +226,21 @@ describe('execAndValidateCondition', () => {
         )
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'organizationHasAllModules', parameters: ['module1', 'module2']}
             )
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'organizationHasAllModules', parameters: ['module1', 'module3']}
             )
         ).toBeFalsy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'organizationHasAllModules', parameters: ['module3']}
             )
@@ -249,18 +254,21 @@ describe('execAndValidateCondition', () => {
         )
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'organizationHasAnyModule', parameters: ['module1', 'module2']}
             )
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'organizationHasAnyModule', parameters: ['module1', 'module3']}
             )
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition(
                 {'function': 'organizationHasAnyModule', parameters: ['module3']}
             )
@@ -270,90 +278,108 @@ describe('execAndValidateCondition', () => {
     test('organizationHasAnyModule', () => {
         // @ts-ignore
         accessProfile.isAdminAccount = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'accessIsAdminAccount'})).toBeTruthy()
 
         // @ts-ignore
         accessProfile.isAdminAccount = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'accessIsAdminAccount'})).toBeFalsy()
     })
 
     test('organizationIsSchool', () => {
         // @ts-ignore
         organizationProfile.isSchool = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsSchool'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.isSchool = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsSchool'})).toBeFalsy()
     })
 
     test('organizationIsArtist', () => {
         // @ts-ignore
         organizationProfile.isArtist = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsArtist'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.isArtist = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsArtist'})).toBeFalsy()
     })
 
     test('organizationIsManagerProduct', () => {
         // @ts-ignore
         organizationProfile.isManagerProduct = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsManagerProduct'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.isManagerProduct = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsManagerProduct'})).toBeFalsy()
     })
 
     test('organizationHasChildren', () => {
         // @ts-ignore
         organizationProfile.hasChildren = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasChildren'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.hasChildren = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasChildren'})).toBeFalsy()
     })
 
     test('organizationIsAssociation', () => {
         // @ts-ignore
         organizationProfile.isAssociation = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsAssociation'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.isAssociation = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsAssociation'})).toBeFalsy()
     })
 
     test('organizationIsShowAdherentList', () => {
         // @ts-ignore
         organizationProfile.isShowAdherentList = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsShowAdherentList'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.isShowAdherentList = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsShowAdherentList'})).toBeFalsy()
     })
 
     test('organizationIsCmf', () => {
         // @ts-ignore
         organizationProfile.isCmf = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsCmf'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.isCmf = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsCmf'})).toBeFalsy()
     })
 
     test('organizationHasWebsite', () => {
         // @ts-ignore
         organizationProfile.getWebsite = true
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite'})).toBeTruthy()
 
         // @ts-ignore
         organizationProfile.getWebsite = false
+        // @ts-ignore
         expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite'})).toBeFalsy()
     })
 
@@ -362,16 +388,19 @@ describe('execAndValidateCondition', () => {
         organizationProfile.getWebsite = true
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite', expectedResult: true})
         ).toBeTruthy()
 
         expect(
+            // @ts-ignore
             abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite', expectedResult: 'abc'})
         ).toBeFalsy()
     })
 
     test('invalid function', () => {
         expect(
+            // @ts-ignore
             () => abilityBuilder.execAndValidateCondition({'function': 'invalid'})
         ).toThrowError('unknown condition function : invalid')
     })

+ 3 - 0
tests/units/services/sse/sseSource.test.ts

@@ -25,6 +25,8 @@ let onMessage: (data: Array<any>) => any
 let onClose: () => any
 let sseSource: TestableSseSource
 
+const init_console_log = console.log
+
 beforeEach(() => {
     mercureUrl = 'https://my.mercure.com'
     topic = 'mytopic'
@@ -37,6 +39,7 @@ beforeEach(() => {
 })
 
 afterEach(() => {
+    console.log = init_console_log
     vi.restoreAllMocks()
 })
 

+ 3 - 0
tests/units/services/utils/urlUtils.test.ts

@@ -66,6 +66,9 @@ describe('extractIdFromUri', () => {
     test('extract id from uri', () => {
         expect(UrlUtils.extractIdFromUri('/api/organizations/1000')).toEqual(1000)
     })
+    test('try to literal id', () => {
+        expect(UrlUtils.extractIdFromUri('/api/organizations/abc', true)).toEqual('abc')
+    })
     test('try to extract missing id', () => {
         expect(() => UrlUtils.extractIdFromUri('/api/organizations/first')).toThrowError('no id found')
     })