Ver código fonte

resolve merge conflicts

Olivier Massot 1 ano atrás
pai
commit
3773db4229

+ 1 - 1
components/Layout/Alert/Container.vue

@@ -16,9 +16,9 @@ Container principal pour l'affichage d'une ou plusieurs alertes
 </template>
 
 <script setup lang="ts">
+import type { ComputedRef } from 'vue'
 import type { Alert } from '~/types/interfaces'
 import { usePageStore } from '~/stores/page'
-import type { ComputedRef } from '@vue/reactivity'
 
 const pageStore = usePageStore()
 

+ 2 - 2
components/Layout/Alert/Content.vue

@@ -13,7 +13,7 @@
     @mouseout="onMouseOut"
   >
     <ul v-if="props.alert.messages.length > 1">
-      <li v-for="message in props.alert.messages">
+      <li v-for="message in props.alert.messages" :key="message">
         {{ $t(message) }}
       </li>
     </ul>
@@ -24,8 +24,8 @@
 </template>
 
 <script setup lang="ts">
+import type { Ref } from 'vue'
 import type { Alert } from '~/types/interfaces'
-import type { Ref } from '@vue/reactivity'
 import { usePageStore } from '~/stores/page'
 
 const props = defineProps({

+ 16 - 9
composables/data/useAp2iRequestService.ts

@@ -5,7 +5,6 @@ import ApiRequestService from '~/services/data/apiRequestService'
 import { usePageStore } from '~/stores/page'
 import UnauthorizedError from '~/services/error/UnauthorizedError'
 import { useAccessProfileStore } from '~/stores/accessProfile'
-import type { AssociativeArray } from '~/types/data'
 
 /**
  * Retourne une instance de ApiRequestService configurée pour interroger l'api Ap2i
@@ -34,16 +33,14 @@ export const useAp2iRequestService = () => {
 
     const accessProfileStore = useAccessProfileStore()
 
-    const headers: AssociativeArray = {
-      'x-accessid': String(accessProfileStore.id),
-      Authorization: 'BEARER ' + accessProfileStore.bearer,
-    }
+    const headers = new Headers(options.headers)
 
+    headers.set('x-accessid', String(accessProfileStore.id))
+    headers.set('Authorization', 'BEARER ' + accessProfileStore.bearer)
     if (accessProfileStore.switchId) {
-      headers['x-switch-user'] = String(accessProfileStore.switchId)
+      headers.set('x-switch-user', String(accessProfileStore.switchId))
     }
-
-    options.headers = { ...options.headers, ...headers }
+    options.headers = headers
 
     pending.value = true
     console.log('Request : ' + request + ' (SSR: ' + process.server + ')')
@@ -80,7 +77,17 @@ export const useAp2iRequestService = () => {
       (response.status === 400 || response.status >= 404)
     ) {
       // @see https://developer.mozilla.org/fr/docs/Web/HTTP/Status
-      const errorMsg = error ? error.message : response.statusText
+      let errorMsg
+      if (error) {
+        errorMsg = error.message
+      } else if (response._data && response._data.detail) {
+        errorMsg = response._data.detail
+      } else if (response.statusText) {
+        errorMsg = response.statusText
+      } else {
+        errorMsg = 'An error occured'
+      }
+
       console.error('! Request error: ' + errorMsg)
       usePageStore().addAlert(TYPE_ALERT.ALERT, [errorMsg])
     }

+ 14 - 1
composables/data/useEntityManager.ts

@@ -1,6 +1,7 @@
 import { useRepo } from 'pinia-orm'
 import EntityManager from '~/services/data/entityManager'
 import { useAp2iRequestService } from '~/composables/data/useAp2iRequestService'
+import { useAccessProfileStore } from '~/stores/accessProfile'
 
 let entityManager: EntityManager | null = null
 
@@ -9,7 +10,19 @@ export const useEntityManager = () => {
     const { apiRequestService } = useAp2iRequestService()
     const getRepo = useRepo
 
-    entityManager = new EntityManager(apiRequestService, getRepo)
+    const profileStore = useAccessProfileStore()
+    const getProfileMask = () => {
+      return {
+        activityYear: profileStore.activityYear,
+        historical: profileStore.historical,
+      }
+    }
+
+    entityManager = new EntityManager(
+      apiRequestService,
+      getRepo,
+      getProfileMask,
+    )
   }
   return { em: entityManager }
 }

+ 3 - 1
lang/fr.json

@@ -694,5 +694,7 @@
   "cmf_licence_structure_breadcrumbs": "Licence CMF - Structure",
   "no_recorded_subdomain": "Aucun sous-domaine enregistré",
   "no_admin_access_recorded": "Aucun compte super-admin enregistré",
-  "redirecting": "Redirection en cours"
+  "redirecting": "Redirection en cours",
+  "Invalid profile hash": "Le profil de l'utilisateur a été modifié ailleurs, veuillez rafraichir la page et réessayer.",
+  "An error occured": "Une erreur s'est produite."
 }

+ 8 - 8
package.json

@@ -23,12 +23,12 @@
     "prettier-fix": "yarn prettier . --write"
   },
   "dependencies": {
-    "@casl/ability": "^6.5.0",
-    "@casl/vue": "2.2.1",
+    "@casl/ability": "^6.7.1",
+    "@casl/vue": "2.2.2",
     "@fortawesome/fontawesome-free": "^6.5.1",
     "@mdi/font": "^7.3.67",
-    "@nuxt/image": "1.1.0",
-    "@nuxtjs/i18n": "^8.2.0",
+    "@nuxt/image": "1.7.0",
+    "@nuxtjs/i18n": "^8.3.1",
     "@pinia-orm/nuxt": "^1.7.0",
     "@pinia/nuxt": "0.5.1",
     "@vuepic/vue-datepicker": "^7.4.0",
@@ -41,7 +41,7 @@
     "libphonenumber-js": "1.10.51",
     "lodash": "^4.17.21",
     "lodash-es": "^4.17.21",
-    "nuxt": "^3.8.2",
+    "nuxt": "^3.11.2",
     "pinia": "^2.1.7",
     "pinia-orm": "^1.7.2",
     "sass": "^1.69.5",
@@ -54,8 +54,8 @@
     "yaml-import": "^2.0.0"
   },
   "devDependencies": {
-    "@nuxt/devtools": "^1.0.5",
-    "@nuxt/test-utils": "^3.8.1",
+    "@nuxt/devtools": "^1.2.0",
+    "@nuxt/test-utils": "^3.12.1",
     "@nuxt/test-utils-edge": "3.8.0-28284309.b3d3d7f4",
     "@nuxtjs/eslint-config": "^12.0.0",
     "@nuxtjs/eslint-config-typescript": "^12.1.0",
@@ -74,7 +74,7 @@
     "@vitejs/plugin-vue": "^4.5.2",
     "@vitest/coverage-v8": "1.0.2",
     "@vue/eslint-config-standard": "^8.0.1",
-    "@vue/test-utils": "^2.4.3",
+    "@vue/test-utils": "^2.4.5",
     "blob-polyfill": "^7.0.20220408",
     "eslint": "^8.55.0",
     "eslint-config-prettier": "^9.1.0",

+ 23 - 10
services/data/apiRequestService.ts

@@ -20,12 +20,14 @@ class ApiRequestService {
    *
    * @param url
    * @param query
+   * @param headers
    */
   public async get(
     url: string,
-    query: AssociativeArray | string | null = null,
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ) {
-    return await this.request(HTTP_METHOD.GET, url, null, query)
+    return await this.request(HTTP_METHOD.GET, url, null, query, headers)
   }
 
   /**
@@ -34,13 +36,15 @@ class ApiRequestService {
    * @param url
    * @param body
    * @param query
+   * @param headers
    */
   public async post(
     url: string,
     body: string | AnyJson | null = null,
-    query: AssociativeArray | string | null = null,
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ) {
-    return await this.request(HTTP_METHOD.POST, url, body, query)
+    return await this.request(HTTP_METHOD.POST, url, body, query, headers)
   }
 
   /**
@@ -49,13 +53,15 @@ class ApiRequestService {
    * @param url
    * @param body
    * @param query
+   * @param headers
    */
   public async put(
     url: string,
     body: string | AnyJson | null = null,
-    query: AssociativeArray | string | null = null,
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ) {
-    return await this.request(HTTP_METHOD.PUT, url, body, query)
+    return await this.request(HTTP_METHOD.PUT, url, body, query, headers)
   }
 
   /**
@@ -63,12 +69,14 @@ class ApiRequestService {
    *
    * @param url
    * @param query
+   * @param headers
    */
   public async delete(
     url: string,
-    query: AssociativeArray | string | null = null,
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ) {
-    return await this.request(HTTP_METHOD.DELETE, url, null, query)
+    return await this.request(HTTP_METHOD.DELETE, url, null, query, headers)
   }
 
   /**
@@ -78,21 +86,26 @@ class ApiRequestService {
    * @param url
    * @param body
    * @param query
+   * @param headers
    * @protected
    */
   protected async request(
     method: HTTP_METHOD,
     url: string,
     body: string | AnyJson | null = null,
-    query: AssociativeArray | string | null = null,
+    query: AssociativeArray | null = null,
+    headers: AssociativeArray | null = null,
   ): Promise<Response> {
     const config: FetchOptions = { method }
-    if (query && typeof query !== 'string') {
+    if (query) {
       config.query = query
     }
     if (method === HTTP_METHOD.POST || method === HTTP_METHOD.PUT) {
       config.body = body
     }
+    if (headers) {
+      config.headers = headers
+    }
 
     // @ts-expect-error TODO: solve the type mismatch
     return await this.fetch(url, config)

+ 14 - 2
services/data/entityManager.ts

@@ -10,6 +10,7 @@ import ApiResource from '~/models/ApiResource'
 import type { AnyJson, AssociativeArray, Collection } from '~/types/data.d'
 import models from '~/models/models'
 import HydraNormalizer from '~/services/data/normalizer/hydraNormalizer'
+import ObjectUtils from '~/services/utils/objectUtils'
 import Query from '~/services/data/Query'
 
 /**
@@ -33,12 +34,16 @@ class EntityManager {
    */
   protected _getRepo: (model: typeof ApiResource) => Repository<ApiResource>
 
+  protected _getProfileMask: () => object
+
   public constructor(
     apiRequestService: ApiRequestService,
     getRepo: (model: typeof ApiResource) => Repository<ApiResource>,
+    getProfileMask: () => object,
   ) {
     this.apiRequestService = apiRequestService
     this._getRepo = getRepo
+    this._getProfileMask = getProfileMask
   }
 
   /**
@@ -267,12 +272,14 @@ class EntityManager {
 
     const data: AnyJson = HydraNormalizer.normalizeEntity(instance)
 
+    const headers = { profileHash: await this.makeProfileHash() }
+
     if (!instance.isNew()) {
       url = UrlUtils.join(url, String(instance.id))
-      response = await this.apiRequestService.put(url, data)
+      response = await this.apiRequestService.put(url, data, null, headers)
     } else {
       delete data.id
-      response = await this.apiRequestService.post(url, data)
+      response = await this.apiRequestService.post(url, data, null, headers)
     }
 
     const hydraResponse = HydraNormalizer.denormalize(response, model)
@@ -452,6 +459,11 @@ class EntityManager {
     repository.destroy(tempInstanceId)
     repository.destroy(this.CLONE_PREFIX + tempInstanceId)
   }
+
+  protected async makeProfileHash(): Promise<string> {
+    const mask = this._getProfileMask()
+    return await ObjectUtils.hash(mask)
+  }
 }
 
 export default EntityManager

+ 13 - 1
services/utils/objectUtils.ts

@@ -3,8 +3,9 @@
  * @class ObjectUtils
  * Classe aidant à manipuler des Objets
  */
+import _ from 'lodash'
 import type { AnyJson } from '~/types/data'
-
+import StringUtils from '~/services/utils/stringUtils'
 export default class ObjectUtils {
   /**
    * Flatten un objet nested en un objet avec un seul niveau avec des noms de propriétés transformées comme cela 'foo.bar'
@@ -138,4 +139,15 @@ export default class ObjectUtils {
         return obj
       }, {})
   }
+
+  /**
+   * Créé un hash à partir d'un objet
+   * (après l'avoir trié selon ses clés, et converti en json sans espace)
+   *
+   * @param obj
+   */
+  static async hash(obj: object): Promise<string> {
+    const sortedObject = this.sortObjectsByKey(_.cloneDeep(obj))
+    return await StringUtils.hash(JSON.stringify(sortedObject), 'SHA-1')
+  }
 }

+ 24 - 0
services/utils/stringUtils.ts

@@ -1,3 +1,5 @@
+import crypto from 'crypto'
+
 export default class StringUtils {
   /**
    * Normalise une chaine de caractères en retirant la casse et les caractères spéciaux, à des fins de recherche
@@ -30,4 +32,26 @@ export default class StringUtils {
   public static parseInt(s: string | number) {
     return typeof s === 'number' ? s : parseInt(s)
   }
+
+  /**
+   * Hash une chaine de caractères avec l'algorithme demandé
+   *
+   * @param input
+   * @param algorithm
+   */
+  public static async hash(input: string, algorithm: string = 'SHA-256') {
+    const textAsBuffer = new TextEncoder().encode(input)
+
+    const isNode =
+      typeof process !== 'undefined' &&
+      process.versions != null &&
+      process.versions.node != null
+
+    const cryptoLib = isNode ? crypto : window.crypto
+
+    const hashBuffer = await cryptoLib.subtle.digest(algorithm, textAsBuffer)
+
+    const hashArray = Array.from(new Uint8Array(hashBuffer))
+    return hashArray.map((item) => item.toString(16).padStart(2, '0')).join('')
+  }
 }

+ 86 - 3
tests/units/services/data/apiRequestService.test.ts

@@ -1,4 +1,6 @@
 import { describe, expect, test, beforeEach } from 'vitest'
+import type { $Fetch } from 'nitropack'
+import type { FetchOptions } from 'ofetch'
 import ApiRequestService from '~/services/data/apiRequestService'
 import { HTTP_METHOD } from '~/types/enum/data'
 import type { AssociativeArray } from '~/types/data'
@@ -27,9 +29,29 @@ describe('get', () => {
     // @ts-ignore
     apiRequestService.request = vi.fn(mockedRequestMethod)
 
-    const result = await apiRequestService.get('https://myapi.com/api/item', {
-      a: 1,
-    })
+    const result = await apiRequestService.get('https://myapi.com/api/item')
+
+    expect(result).toEqual('a_response')
+    // @ts-ignore
+    expect(apiRequestService.request).toHaveBeenCalledWith(
+      HTTP_METHOD.GET,
+      'https://myapi.com/api/item',
+      null,
+      null,
+      null,
+    )
+  })
+  test('with query and headers', async () => {
+    // @ts-ignore
+    apiRequestService.request = vi.fn(mockedRequestMethod)
+
+    const result = await apiRequestService.get(
+      'https://myapi.com/api/item',
+      {
+        a: 1,
+      },
+      { b: 2 },
+    )
 
     expect(result).toEqual('a_response')
     // @ts-ignore
@@ -38,6 +60,7 @@ describe('get', () => {
       'https://myapi.com/api/item',
       null,
       { a: 1 },
+      { b: 2 },
     )
   })
 })
@@ -47,10 +70,30 @@ describe('post', () => {
     // @ts-ignore
     apiRequestService.request = vi.fn(mockedRequestMethod)
 
+    const result = await apiRequestService.post(
+      'https://myapi.com/api/item',
+      'request_body',
+    )
+
+    expect(result).toEqual('a_response')
+    // @ts-ignore
+    expect(apiRequestService.request).toHaveBeenCalledWith(
+      HTTP_METHOD.POST,
+      'https://myapi.com/api/item',
+      'request_body',
+      null,
+      null,
+    )
+  })
+  test('with query and headers', async () => {
+    // @ts-ignore
+    apiRequestService.request = vi.fn(mockedRequestMethod)
+
     const result = await apiRequestService.post(
       'https://myapi.com/api/item',
       'request_body',
       { a: 1 },
+      { b: 2 },
     )
 
     expect(result).toEqual('a_response')
@@ -60,6 +103,7 @@ describe('post', () => {
       'https://myapi.com/api/item',
       'request_body',
       { a: 1 },
+      { b: 2 },
     )
   })
 })
@@ -69,10 +113,30 @@ describe('put', () => {
     // @ts-ignore
     apiRequestService.request = vi.fn(mockedRequestMethod)
 
+    const result = await apiRequestService.put(
+      'https://myapi.com/api/item',
+      'request_body',
+    )
+
+    expect(result).toEqual('a_response')
+    // @ts-ignore
+    expect(apiRequestService.request).toHaveBeenCalledWith(
+      HTTP_METHOD.PUT,
+      'https://myapi.com/api/item',
+      'request_body',
+      null,
+      null,
+    )
+  })
+  test('with query and headers', async () => {
+    // @ts-ignore
+    apiRequestService.request = vi.fn(mockedRequestMethod)
+
     const result = await apiRequestService.put(
       'https://myapi.com/api/item',
       'request_body',
       { a: 1 },
+      { b: 2 },
     )
 
     expect(result).toEqual('a_response')
@@ -82,6 +146,7 @@ describe('put', () => {
       'https://myapi.com/api/item',
       'request_body',
       { a: 1 },
+      { b: 2 },
     )
   })
 })
@@ -91,9 +156,26 @@ describe('delete', () => {
     // @ts-ignore
     apiRequestService.request = vi.fn(mockedRequestMethod)
 
+    const result = await apiRequestService.delete('https://myapi.com/api/item')
+
+    expect(result).toEqual('a_response')
+    // @ts-ignore
+    expect(apiRequestService.request).toHaveBeenCalledWith(
+      HTTP_METHOD.DELETE,
+      'https://myapi.com/api/item',
+      null,
+      null,
+      null,
+    )
+  })
+  test('with query and headers', async () => {
+    // @ts-ignore
+    apiRequestService.request = vi.fn(mockedRequestMethod)
+
     const result = await apiRequestService.delete(
       'https://myapi.com/api/item',
       { a: 1 },
+      { b: 2 },
     )
 
     expect(result).toEqual('a_response')
@@ -103,6 +185,7 @@ describe('delete', () => {
       'https://myapi.com/api/item',
       null,
       { a: 1 },
+      { b: 2 },
     )
   })
 })

+ 60 - 18
tests/units/services/data/entityManager.test.ts

@@ -2,19 +2,10 @@ import { describe, test, vi, expect, beforeEach, afterEach } from 'vitest'
 import { Repository } from 'pinia-orm'
 import type { Element } from 'pinia-orm'
 import { Str, Uid } from 'pinia-orm/dist/decorators'
+import EntityManager from '~/services/data/entityManager'
 import ApiResource from '~/models/ApiResource'
 import ApiModel from '~/models/ApiModel'
 import ApiRequestService from '~/services/data/apiRequestService'
-import EntityManager from '~/services/data/entityManager'
-
-class TestableEntityManager extends EntityManager {
-  public removeTempAfterPersist(
-    model: typeof ApiResource,
-    tempInstanceId: number | string,
-  ): void {
-    return super.removeTempAfterPersist(model, tempInstanceId)
-  }
-}
 
 class DummyApiResource extends ApiResource {
   static entity = 'dummyResource'
@@ -36,6 +27,21 @@ class DummyApiModel extends ApiModel {
   declare name: string
 }
 
+class TestableEntityManager extends EntityManager {
+  public _getProfileMask: () => object
+
+  public removeTempAfterPersist(
+    model: typeof ApiResource,
+    tempInstanceId: number | string,
+  ) {
+    return super.removeTempAfterPersist(model, tempInstanceId)
+  }
+
+  public makeProfileHash() {
+    return super.makeProfileHash()
+  }
+}
+
 const _console: any = {
   log: console.log,
   warn: console.warn,
@@ -58,6 +64,7 @@ let apiRequestService: ApiRequestService
 let entityManager: TestableEntityManager
 let repo: Repository<ApiResource>
 let _getRepo: (model: typeof ApiResource) => Repository<ApiResource>
+let _getProfileMask: () => object
 
 beforeEach(() => {
   // @ts-ignore
@@ -66,8 +73,15 @@ beforeEach(() => {
   // @ts-ignore
   apiRequestService = vi.fn() as ApiRequestService
   _getRepo = vi.fn((model: typeof ApiResource) => repo)
+  _getProfileMask = vi.fn(() => {
+    return {}
+  })
 
-  entityManager = new TestableEntityManager(apiRequestService, _getRepo)
+  entityManager = new TestableEntityManager(
+    apiRequestService,
+    _getRepo,
+    _getProfileMask,
+  )
 })
 
 afterEach(() => {
@@ -512,13 +526,19 @@ describe('persist', () => {
 
     // @ts-ignore
     entityManager.removeTempAfterPersist = vi.fn()
+    entityManager.makeProfileHash = vi.fn(async () => await 'azerty')
 
     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(apiRequestService.post).toHaveBeenCalledWith(
+      'api/dummyModel',
+      {
+        name: 'bob',
+      },
+      null,
+      { profileHash: 'azerty' },
+    )
     expect(entityManager.newInstance).toHaveBeenCalledWith(
       DummyApiModel,
       response,
@@ -528,6 +548,8 @@ describe('persist', () => {
       DummyApiModel,
       instance.id,
     )
+    // @ts-ignore
+    expect(entityManager.makeProfileHash).toHaveBeenCalledTimes(1)
 
     expect(result.id).toEqual(1)
     expect(result.name).toEqual('bob')
@@ -561,16 +583,24 @@ describe('persist', () => {
 
     // @ts-ignore
     entityManager.removeTempAfterPersist = vi.fn()
+    entityManager.makeProfileHash = vi.fn(async () => await 'azerty')
 
     const result = await entityManager.persist(DummyApiModel, entity)
 
-    expect(apiRequestService.put).toHaveBeenCalledWith('api/dummyModel/1', {
-      id: 1,
-      name: 'bob',
-    })
+    expect(apiRequestService.put).toHaveBeenCalledWith(
+      'api/dummyModel/1',
+      {
+        id: 1,
+        name: 'bob',
+      },
+      null,
+      { profileHash: 'azerty' },
+    )
     expect(entityManager.newInstance).toHaveBeenCalledWith(DummyApiModel, props)
     // @ts-ignore
     expect(entityManager.removeTempAfterPersist).toHaveBeenCalledTimes(0)
+    // @ts-ignore
+    expect(entityManager.makeProfileHash).toHaveBeenCalledTimes(1)
 
     expect(result.id).toEqual(1)
     expect(result.name).toEqual('bob')
@@ -958,3 +988,15 @@ describe('removeTempAfterPersist', () => {
     )
   })
 })
+
+describe('makeProfileHash', () => {
+  test('simple call', async () => {
+    entityManager._getProfileMask = vi.fn(() => {
+      return { a: 1 }
+    })
+
+    expect(await entityManager.makeProfileHash()).toEqual(
+      '9f89c740ceb46d7418c924a78ac57941d5e96520',
+    )
+  })
+})

+ 19 - 0
tests/units/services/utils/objectUtils.test.ts

@@ -1,5 +1,6 @@
 import { describe, test, it, expect } from 'vitest'
 import ObjectUtils from '~/services/utils/objectUtils'
+import StringUtils from '~/services/utils/stringUtils'
 
 describe('cloneAndFlatten', () => {
   test('If the object is already flat, it should return an identical object', () => {
@@ -88,3 +89,21 @@ describe('sortObjectsByKey', () => {
   //     expect(ObjectUtils.sortObjectsByKey({})).toThrowError('Expecting an object parameter')
   // })
 })
+
+describe('hash', () => {
+  test('empty object', async () => {
+    expect(await ObjectUtils.hash({})).toBe(
+      'bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f',
+    )
+  })
+  test('simple object', async () => {
+    expect(await ObjectUtils.hash({ a: 1, b: 2, c: 3 })).toBe(
+      'e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597',
+    )
+  })
+  test('simple unsorted object', async () => {
+    expect(await ObjectUtils.hash({ b: 2, a: 1, c: 3 })).toBe(
+      'e7ec4a8f2309bdd4c4c57cb2adfb79c91a293597',
+    )
+  })
+})

+ 11 - 0
tests/units/services/utils/stringUtils.test.ts

@@ -21,3 +21,14 @@ describe('parseInt', () => {
     expect(StringUtils.parseInt('6')).toBe(6)
   })
 })
+
+describe('hash', () => {
+  test('simple cases', async () => {
+    expect(await StringUtils.hash('azerty')).toBe(
+      'f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9',
+    )
+    expect(await StringUtils.hash('azerty', 'SHA-1')).toBe(
+      '9cf95dacd226dcf43da376cdb6cbba7035218921',
+    )
+  })
+})

Diferenças do arquivo suprimidas por serem muito extensas
+ 385 - 190
yarn.lock


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff