From b718f026fdfe1ceaa3c251a2c2f45ef010fe5c7e Mon Sep 17 00:00:00 2001
From: Theo Lesage <tlesage@teklia.com>
Date: Mon, 15 Apr 2024 10:46:42 +0000
Subject: [PATCH] Create Metadata pinia store

---
 src/api/metadata.ts                          |   4 +-
 src/components/Element/DetailsPanel.vue      |   4 -
 src/components/Element/Metadata/Metadata.vue |  40 +-
 src/store/elements.js                        |  69 ----
 src/store/index.js                           |   6 +-
 src/stores/index.ts                          |   1 +
 src/stores/metadata.ts                       |  70 ++++
 src/types/index.ts                           |   1 -
 tests/unit/store/elements.spec.js            | 379 -------------------
 tests/unit/stores/metadata.spec.js           | 167 ++++++++
 10 files changed, 262 insertions(+), 479 deletions(-)
 create mode 100644 src/stores/metadata.ts
 create mode 100644 tests/unit/stores/metadata.spec.js

diff --git a/src/api/metadata.ts b/src/api/metadata.ts
index bc3b26848..b96e7041e 100644
--- a/src/api/metadata.ts
+++ b/src/api/metadata.ts
@@ -42,7 +42,7 @@ export const updateAllowedMetadata = unique(
 
 // Create a metadata.
 export const createMetadata = unique(
-  async ({ elementId, ...data }: MetaDataCreate): Promise<MetaData> =>
+  async (elementId: UUID, data: MetaDataCreate): Promise<MetaData> =>
     (await axios.post(`/element/${elementId}/metadata/`, data)).data
 )
 
@@ -57,5 +57,5 @@ export const deleteMetadata = unique(async (id: UUID) => await axios.delete(`/me
 
 // List element metadata.
 export const listMetadata = unique(
-  async (id: UUID): Promise<PageNumberPagination<MetaData>> => (await axios.get(`/element/${id}/metadata/`)).data
+  async (id: UUID): Promise<MetaData[]> => (await axios.get(`/element/${id}/metadata/`)).data
 )
diff --git a/src/components/Element/DetailsPanel.vue b/src/components/Element/DetailsPanel.vue
index 556f95366..c6e156a42 100644
--- a/src/components/Element/DetailsPanel.vue
+++ b/src/components/Element/DetailsPanel.vue
@@ -156,10 +156,6 @@ export default defineComponent({
     classifications () {
       return (this.element && this.element.classifications) || []
     },
-    metadata () {
-      // @ts-expect-error Some Element attributes like metadata are set on the fly on the former store
-      return (this.element && this.element?.metadata) || []
-    },
     datasets (): ElementDatasetSet[] | null {
       return this.elementDatasetSets?.[this.elementId] ?? null
     }
diff --git a/src/components/Element/Metadata/Metadata.vue b/src/components/Element/Metadata/Metadata.vue
index c3772f7fb..36c2d6afe 100644
--- a/src/components/Element/Metadata/Metadata.vue
+++ b/src/components/Element/Metadata/Metadata.vue
@@ -31,13 +31,13 @@
         <button
           class="button is-danger"
           :class="{ 'is-loading': isLoading }"
-          v-on:click="deleteMetadata"
+          v-on:click="metadataDelete"
         >
           Delete
         </button>
       </template>
     </Modal>
-    <form v-on:submit.prevent="updateMetadata">
+    <form v-on:submit.prevent="metadataUpdate">
       <Modal
         v-if="selectedMetadata"
         v-model="editModal"
@@ -194,7 +194,7 @@ import {
 
 import { METADATA_TYPES } from '@/config'
 import { corporaMixin } from '@/mixins'
-import { useDisplayStore } from '@/stores'
+import { useDisplayStore, useMetaDataStore } from '@/stores'
 
 import DateInput from '@/components/DateInput'
 import Modal from '@/components/Modal'
@@ -238,17 +238,15 @@ export default {
     ...mapVuexState('corpora', ['corpusAllowedMetadata']),
     ...mapVuexState('elements', ['elements']),
     ...mapState(useDisplayStore, ['lastMetadataName', 'lastMetadataType', 'dropdowns']),
+    ...mapState(useMetaDataStore, ['metadata']),
     allowedMetadata () {
       return this.corpusAllowedMetadata[this.corpusId] || []
     },
     element () {
       return this.elements[this.elementId]
     },
-    metadata () {
-      return (this.element && this.element.metadata) || []
-    },
     count () {
-      return (this.metadata && this.metadata.length)
+      return this.elementMetadata?.length
     },
     markdownMetadata () {
       return this.editableMetadata.filter(m => m.type === 'markdown')
@@ -256,10 +254,13 @@ export default {
     standardMetadata () {
       return this.editableMetadata.filter(m => m.type !== 'markdown')
     },
+    elementMetadata () {
+      return this.metadata[this.elementId]
+    },
     editableMetadata () {
       // Annotate each metadata with an editable property
       if (!this.count) return
-      return this.metadata.map(md => ({
+      return this.elementMetadata.map(md => ({
         ...md,
         editable: this.isEditable(md)
       }))
@@ -271,6 +272,7 @@ export default {
   methods: {
     ...mapVuexMutations('notifications', ['notify']),
     ...mapActions(useDisplayStore, ['setLastMetadata']),
+    ...mapActions(useMetaDataStore, ['updateMetadata', 'createMetadata', 'deleteMetadata', 'listMetadata']),
     isEditable (md) {
       return this.isAdmin || (this.canWrite(this.corpus) && this.isAllowed(md))
     },
@@ -325,17 +327,14 @@ export default {
       }
       return isEmpty(this.formErrors)
     },
-    async updateMetadata () {
+    async metadataUpdate () {
       // Assert fields are valid
       if (!this.validateForm()) return
-      let endpoint = 'elements/updateMetadata'
-      if (this.createModalForm) endpoint = 'elements/createMetadata'
+      let method = this.updateMetadata
+      if (this.createModalForm) method = this.createMetadata
       this.isLoading = true
       try {
-        await this.$store.dispatch(endpoint, {
-          elementId: this.elementId,
-          metadata: this.selectedMetadata
-        })
+        await method(this.elementId, this.selectedMetadata)
         this.editModal = false
         this.setLastMetadata(this.selectedMetadata.name, this.selectedMetadata.type)
       } catch (err) {
@@ -344,13 +343,10 @@ export default {
         this.isLoading = false
       }
     },
-    async deleteMetadata () {
+    async metadataDelete () {
       this.isLoading = true
       try {
-        await this.$store.dispatch('elements/deleteMetadata', {
-          elementId: this.elementId,
-          metadata: this.selectedMetadata
-        })
+        await this.deleteMetadata(this.elementId, this.selectedMetadata)
         this.deleteModal = false
       } finally {
         this.isLoading = false
@@ -414,8 +410,8 @@ export default {
        * This check does not use the computed properties because this might be called with a newValue from a watcher,
        * in which case it is not guaranteed that the computed properties are up to date.
        */
-      if (!elementId || this.elements[elementId]?.metadata) return
-      this.$store.dispatch('elements/listMetadata', { elementId })
+      if (!elementId || this.metadata[elementId]) return
+      this.listMetadata(elementId)
     }
   },
   watch: {
diff --git a/src/store/elements.js b/src/store/elements.js
index 18335060e..2698c2dd4 100644
--- a/src/store/elements.js
+++ b/src/store/elements.js
@@ -40,16 +40,6 @@ const mergeElement = (state, element) => {
   }
 }
 
-/**
- * Methods used to edit an element attribute in a generic way.
- * Attribute has to be an Array (e.g. A metadata or a classification).
- */
-const setInArray = (attribute, state, { elementId, value }) => {
-  const element = state.elements[elementId]
-  if (!element) return
-  mergeElement(state, { ...element, [attribute]: value })
-}
-
 const createInArray = (attribute, state, { elementId, value }) => {
   const element = state.elements[elementId]
   if (!element) return
@@ -256,12 +246,6 @@ export const mutations = {
     }
   },
 
-  // Element metadata mutations
-  setMetadata (...args) { setInArray('metadata', ...args) },
-  addMetadata (...args) { createInArray('metadata', ...args) },
-  updateMetadata (...args) { updateInArray('metadata', ...args) },
-  removeMetadata (...args) { removeFromArray('metadata', ...args) },
-
   // Element classifications mutations
   addClassification (...args) { createInArray('classifications', ...args) },
   updateClassification (...args) { updateInArray('classifications', ...args) },
@@ -457,59 +441,6 @@ export const actions = {
       commit('addNeighbors', { element: payload.id, neighbors: null })
       commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true })
     }
-  },
-
-  /**
-   * Perform a request to create a new metadata.
-   */
-  async createMetadata ({ commit }, { metadata, elementId }) {
-    try {
-      const resp = await api.createMetadata({ elementId, ...metadata })
-      commit('addMetadata', { elementId, value: resp })
-      return resp
-    } catch (err) {
-      commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true })
-      throw err
-    }
-  },
-
-  /**
-   * Perform a request to update a metadata.
-   */
-  async updateMetadata ({ commit }, { metadata, elementId }) {
-    try {
-      const resp = await api.updateMetadata(metadata)
-      commit('updateMetadata', { elementId, value: resp })
-      return resp
-    } catch (err) {
-      commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true })
-      throw err
-    }
-  },
-
-  /**
-   * Perform a request to delete a metadata.
-   */
-  async deleteMetadata ({ commit }, { metadata, elementId }) {
-    try {
-      await api.deleteMetadata(metadata.id)
-      commit('removeMetadata', { elementId, value: metadata })
-    } catch (err) {
-      commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true })
-      throw err
-    }
-  },
-
-  /**
-   * Perform a request to list metadata on an element.
-   */
-  async listMetadata ({ commit }, { elementId }) {
-    try {
-      const resp = await api.listMetadata(elementId)
-      commit('setMetadata', { elementId, value: resp })
-    } catch (err) {
-      commit('notifications/notify', { type: 'error', text: errorParser(err) }, { root: true })
-    }
   }
 }
 
diff --git a/src/store/index.js b/src/store/index.js
index 41c78a304..54b01d3d4 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -14,7 +14,8 @@ import {
   useRightsStore,
   useSearchStore,
   useWorkerStore,
-  useRepositoryStore
+  useRepositoryStore,
+  useMetaDataStore
 } from '@/stores'
 
 /**
@@ -53,7 +54,8 @@ export const piniaStores = [
   useRightsStore,
   useSearchStore,
   useWorkerStore,
-  useRepositoryStore
+  useRepositoryStore,
+  useMetaDataStore
 ]
 
 export const actions = {
diff --git a/src/stores/index.ts b/src/stores/index.ts
index c55f48605..f13a26bfb 100644
--- a/src/stores/index.ts
+++ b/src/stores/index.ts
@@ -16,3 +16,4 @@ export { useExportStore } from './exports'
 export { useEntityStore } from './entity'
 export { useTranscriptionStore } from './transcription'
 export { useRepositoryStore } from './repos'
+export { useMetaDataStore } from './metadata'
diff --git a/src/stores/metadata.ts b/src/stores/metadata.ts
new file mode 100644
index 000000000..a50f20c8e
--- /dev/null
+++ b/src/stores/metadata.ts
@@ -0,0 +1,70 @@
+import { defineStore } from 'pinia'
+import {
+  TypedMetaData,
+  MetaDataCreate,
+  MetaData,
+  UUID
+} from '@/types'
+import * as api from '@/api'
+import { useNotificationStore } from '.'
+import { errorParser } from '@/helpers'
+
+interface State {
+  metadata: {
+    [elementId: UUID]: MetaData[]
+  }
+}
+
+export const useMetaDataStore = defineStore('metadata', {
+  state: (): State => ({
+    metadata: {}
+  }),
+  actions: {
+    async createMetadata (elementId: UUID, data: MetaDataCreate) {
+      try {
+        const resp = await api.createMetadata(elementId, data)
+        if (this.metadata[elementId]) {
+          this.metadata[elementId].push(resp)
+        } else {
+          this.metadata[elementId] = [resp]
+        }
+      } catch (err) {
+        useNotificationStore().notify({ type: 'error', text: errorParser(err) })
+        throw err
+      }
+    },
+    async updateMetadata (elementId: UUID, metadata: TypedMetaData) {
+      try {
+        if (!this.metadata[elementId]) return
+        const resp = await api.updateMetadata(metadata)
+        const elementMetadata = this.metadata[elementId]
+        const index = elementMetadata.findIndex(md => md.id === resp.id)
+        if (index < 0) return
+        elementMetadata.splice(index, 1, resp)
+      } catch (err) {
+        useNotificationStore().notify({ type: 'error', text: errorParser(err) })
+        throw new Error(`Metadata ${metadata.id} not found on element ${elementId}`)
+      }
+    },
+    async deleteMetadata (elementId: UUID, metadata: TypedMetaData) {
+      try {
+        await api.deleteMetadata(metadata.id)
+        const elementMetadata = this.metadata[elementId]
+        const index = elementMetadata.findIndex(md => md.id === metadata.id)
+        if (index < 0) throw new Error(`Metadata ${metadata.id} not found on element ${elementId}`)
+        elementMetadata.splice(index, 1)
+        this.metadata[elementId] = elementMetadata
+      } catch (err) {
+        useNotificationStore().notify({ type: 'error', text: errorParser(err) })
+      }
+    },
+    async listMetadata (elementId: UUID) {
+      try {
+        this.metadata[elementId] = await api.listMetadata(elementId)
+      } catch (err) {
+        useNotificationStore().notify({ type: 'error', text: errorParser(err) })
+        throw err
+      }
+    }
+  }
+})
diff --git a/src/types/index.ts b/src/types/index.ts
index a744af9c3..7e8983845 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -227,7 +227,6 @@ export interface StringMetaData extends BaseMetaData {
 export type TypedMetaData = StringMetaData | NumericMetaData
 
 export interface MetaDataCreate extends Omit<TypedMetaData, 'id'> {
-  elementId: UUID
   worker_version?: UUID
   worker_run_id?: UUID
 }
diff --git a/tests/unit/store/elements.spec.js b/tests/unit/store/elements.spec.js
index 97971cc78..d094abb21 100644
--- a/tests/unit/store/elements.spec.js
+++ b/tests/unit/store/elements.spec.js
@@ -410,91 +410,6 @@ describe('elements', () => {
         })
       })
     })
-
-    describe('setMetadata', () => {
-      it('sets metadata on an element', () => {
-        const state = {
-          elements: {
-            element1: {
-              id: 'element1',
-              metadata: [{ id: 'meta1', name: 'AAA' }]
-            }
-          }
-        }
-        mutations.setMetadata(state, { elementId: 'element1', value: [{ id: 'meta2', name: 'BBB' }] })
-        assert.deepStrictEqual(state, {
-          elements: {
-            element1: {
-              id: 'element1',
-              metadata: [{ id: 'meta2', name: 'BBB' }]
-            }
-          }
-        })
-      })
-    })
-
-    describe('addMetadata', () => {
-      it('adds a new metadata on an element', () => {
-        const state = {
-          elements: {
-            element1: {
-              id: 'element1',
-              metadata: [{ id: 'meta1', name: 'AAA' }]
-            }
-          }
-        }
-        mutations.addMetadata(state, { elementId: 'element1', value: { id: 'meta2', name: 'BBB' } })
-        assert.deepStrictEqual(state, {
-          elements: {
-            element1: {
-              id: 'element1',
-              metadata: [{ id: 'meta1', name: 'AAA' }, { id: 'meta2', name: 'BBB' }]
-            }
-          }
-        })
-      })
-    })
-
-    describe('removeMetdata', () => {
-      it('removes a metadata', () => {
-        const state = {
-          elements: {
-            element1: { id: 'element1', metadata: [{ id: 'meta1', name: 'AAA' }] }
-          }
-        }
-        mutations.removeMetadata(state, { elementId: 'element1', value: { id: 'meta1' } })
-        assert.deepStrictEqual(state, {
-          elements: { element1: { id: 'element1', metadata: [] } }
-        })
-      })
-    })
-
-    describe('updateMetdata', () => {
-      it('updates the metadata of an element', () => {
-        const state = {
-          elements: {
-            element1: {
-              id: 'element1',
-              classifications: [{ id: 'classif1', state: 'pending' }],
-              metadata: [{ id: 'meta1', name: 'AAA' }]
-            }
-          }
-        }
-        mutations.updateMetadata(state, {
-          elementId: 'element1',
-          value: { id: 'meta1', name: 'BBB' }
-        })
-        assert.deepStrictEqual(state, {
-          elements: {
-            element1: {
-              id: 'element1',
-              classifications: [{ id: 'classif1', state: 'pending' }],
-              metadata: [{ id: 'meta1', name: 'BBB' }]
-            }
-          }
-        })
-      })
-    })
   })
 
   describe('actions', () => {
@@ -1150,300 +1065,6 @@ describe('elements', () => {
         assert.deepStrictEqual(store.state.elements.neighbors, { elementid: null })
       })
     })
-
-    describe('listMetadata', () => {
-      it('lists metadata on an element', async () => {
-        const metadata = {
-          id: 'metadataid',
-          name: 'blah',
-          type: 'text',
-          value: 'lol'
-        }
-        mock.onGet('/element/elementid/metadata/').reply(200, [metadata])
-        store.state.elements.elements.elementid = {
-          id: 'elementid'
-        }
-
-        await store.dispatch('elements/listMetadata', { elementId: 'elementid' })
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/listMetadata',
-            payload: {
-              elementId: 'elementid'
-            }
-          },
-          {
-            mutation: 'elements/setMetadata',
-            payload: {
-              elementId: 'elementid',
-              value: [metadata]
-            }
-          }
-        ])
-        assert.deepStrictEqual(store.state.elements.elements.elementid, {
-          id: 'elementid',
-          metadata: [metadata]
-        })
-      })
-
-      it('handles errors', async () => {
-        mock.onGet('/element/elementid/metadata/').reply(500)
-
-        await store.dispatch('elements/listMetadata', { elementId: 'elementid' })
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/listMetadata',
-            payload: {
-              elementId: 'elementid'
-            }
-          },
-          {
-            mutation: 'notifications/notify',
-            payload: {
-              type: 'error',
-              text: 'Request failed with status code 500'
-            }
-          }
-        ])
-      })
-    })
-
-    describe('createMetadata', () => {
-      it('creates a metadata on an element', async () => {
-        const payload = {
-          name: 'blah',
-          type: 'text',
-          value: 'lol'
-        }
-        const metadata = {
-          id: 'metadataid',
-          ...payload
-        }
-        mock.onPost('/element/elementid/metadata/').reply(201, metadata)
-        store.state.elements.elements.elementid = {
-          id: 'elementid'
-        }
-
-        await store.dispatch('elements/createMetadata', { elementId: 'elementid', metadata: payload })
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/createMetadata',
-            payload: {
-              elementId: 'elementid',
-              metadata: payload
-            }
-          },
-          {
-            mutation: 'elements/addMetadata',
-            payload: {
-              elementId: 'elementid',
-              value: metadata
-            }
-          }
-        ])
-        assert.deepStrictEqual(store.state.elements.elements.elementid, {
-          id: 'elementid',
-          metadata: [metadata]
-        })
-      })
-
-      it('handles errors', async () => {
-        const payload = {
-          name: 'blah',
-          type: 'text',
-          value: 'lol'
-        }
-        mock.onPost('/element/elementid/metadata/').reply(500)
-
-        await assertRejects(async () => store.dispatch('elements/createMetadata', {
-          elementId: 'elementid',
-          metadata: payload
-        }))
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/createMetadata',
-            payload: {
-              elementId: 'elementid',
-              metadata: payload
-            }
-          },
-          {
-            mutation: 'notifications/notify',
-            payload: {
-              type: 'error',
-              text: 'Request failed with status code 500'
-            }
-          }
-        ])
-      })
-    })
-
-    describe('updateMetadata', () => {
-      it('updates a metadata', async () => {
-        const metadata = {
-          id: 'metadataid',
-          name: 'blah',
-          type: 'text',
-          value: 'lol'
-        }
-        mock.onPatch('/metadata/metadataid/').reply(200, {
-          ...metadata,
-          value: 'new value'
-        })
-        store.state.elements.elements.elementid = {
-          id: 'elementid',
-          metadata: [metadata]
-        }
-
-        await store.dispatch('elements/updateMetadata', {
-          elementId: 'elementid',
-          metadata: {
-            ...metadata,
-            value: 'new value'
-          }
-        })
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/updateMetadata',
-            payload: {
-              elementId: 'elementid',
-              metadata: {
-                ...metadata,
-                value: 'new value'
-              }
-            }
-          },
-          {
-            mutation: 'elements/updateMetadata',
-            payload: {
-              elementId: 'elementid',
-              value: {
-                ...metadata,
-                value: 'new value'
-              }
-            }
-          }
-        ])
-        assert.deepStrictEqual(store.state.elements.elements.elementid, {
-          id: 'elementid',
-          metadata: [
-            {
-              ...metadata,
-              value: 'new value'
-            }
-          ]
-        })
-      })
-
-      it('handles errors', async () => {
-        mock.onPatch('/metadata/metadataid/').reply(500)
-
-        await assertRejects(async () => store.dispatch('elements/updateMetadata', {
-          elementId: 'elementid',
-          metadata: {
-            id: 'metadataid',
-            value: 'new value'
-          }
-        }))
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/updateMetadata',
-            payload: {
-              elementId: 'elementid',
-              metadata: {
-                id: 'metadataid',
-                value: 'new value'
-              }
-            }
-          },
-          {
-            mutation: 'notifications/notify',
-            payload: {
-              type: 'error',
-              text: 'Request failed with status code 500'
-            }
-          }
-        ])
-      })
-    })
-
-    describe('deleteMetadata', () => {
-      it('deletes a metadata', async () => {
-        const metadata = {
-          id: 'metadataid',
-          name: 'blah',
-          type: 'text',
-          value: 'lol'
-        }
-        mock.onDelete('/metadata/metadataid/').reply(204)
-        store.state.elements.elements.elementid = {
-          id: 'elementid',
-          metadata: [metadata]
-        }
-
-        await store.dispatch('elements/deleteMetadata', {
-          elementId: 'elementid',
-          metadata
-        })
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/deleteMetadata',
-            payload: {
-              elementId: 'elementid',
-              metadata
-            }
-          },
-          {
-            mutation: 'elements/removeMetadata',
-            payload: {
-              elementId: 'elementid',
-              value: metadata
-            }
-          }
-        ])
-        assert.deepStrictEqual(store.state.elements.elements.elementid, {
-          id: 'elementid',
-          metadata: []
-        })
-      })
-
-      it('handles errors', async () => {
-        mock.onDelete('/metadata/metadataid/').reply(500)
-
-        await assertRejects(async () => store.dispatch('elements/deleteMetadata', {
-          elementId: 'elementid',
-          metadata: {
-            id: 'metadataid'
-          }
-        }))
-
-        assert.deepStrictEqual(store.history, [
-          {
-            action: 'elements/deleteMetadata',
-            payload: {
-              elementId: 'elementid',
-              metadata: {
-                id: 'metadataid'
-              }
-            }
-          },
-          {
-            mutation: 'notifications/notify',
-            payload: {
-              type: 'error',
-              text: 'Request failed with status code 500'
-            }
-          }
-        ])
-      })
-    })
   })
 
   describe('getters', () => {
diff --git a/tests/unit/stores/metadata.spec.js b/tests/unit/stores/metadata.spec.js
new file mode 100644
index 000000000..fc7001458
--- /dev/null
+++ b/tests/unit/stores/metadata.spec.js
@@ -0,0 +1,167 @@
+import axios from 'axios'
+import { assert } from 'chai'
+import { createPinia, setActivePinia } from 'pinia'
+import { useMetaDataStore } from '@/stores'
+import { FakeAxios } from '../testhelpers'
+
+describe('metadata', () => {
+  describe('actions', () => {
+    let mock, store
+
+    before('Setting up mocks', () => {
+      mock = new FakeAxios(axios)
+      setActivePinia(createPinia())
+      store = useMetaDataStore()
+    })
+
+    afterEach(() => {
+      // Remove any handlers, but leave mocking in place
+      mock.reset()
+      store.$reset()
+    })
+
+    after('Removing Axios mock', () => {
+      mock.restore()
+    })
+
+    describe('createMetadata', () => {
+      it('create a metadata for an element', async () => {
+        const payload = {
+          type: 'text',
+          name: 'Metadata',
+          value: 'metadata value',
+          entity_id: 'entity_1',
+          worker_run_id: 'worker_run_1'
+        }
+        mock.onPost('/element/element_1/metadata/').reply(201, {
+          id: 'metadata_1',
+          type: 'text',
+          name: 'Metadata',
+          value: 'metadata value',
+          entity_id: 'entity_1',
+          worker_run_id: 'worker_run_1'
+        })
+
+        await store.createMetadata('element_1', payload)
+
+        assert.deepStrictEqual(store.metadata, {
+          element_1: [
+            {
+              id: 'metadata_1',
+              type: 'text',
+              name: 'Metadata',
+              value: 'metadata value',
+              entity_id: 'entity_1',
+              worker_run_id: 'worker_run_1'
+            }
+          ]
+        })
+      })
+    })
+
+    describe('updateMetadata', () => {
+      it('Update metadata of an element', async () => {
+        const payload = {
+          id: 'metadata_1',
+          type: 'numeric',
+          name: 'Metadata',
+          value: 12,
+          entity_id: 'entity_2'
+        }
+
+        store.metadata = {
+          element_1: [
+            {
+              id: 'metadata_1',
+              type: 'text',
+              name: 'Metadata name',
+              value: 'Metadata value',
+              entity_id: 'entity_1'
+            }
+          ]
+        }
+        mock.onPatch('/metadata/metadata_1/').reply(200, payload)
+
+        await store.updateMetadata('element_1', payload)
+
+        assert.deepStrictEqual(store.metadata, {
+          element_1: [
+            {
+              id: 'metadata_1',
+              type: 'numeric',
+              name: 'Metadata',
+              value: 12,
+              entity_id: 'entity_2'
+            }
+          ]
+        })
+      })
+    })
+
+    describe('deleteMetadata', () => {
+      it('Delete a metadata', async () => {
+        store.metadata = {
+          element_1: [
+            {
+              id: 'metadata_1',
+              type: 'text',
+              name: 'Metadata',
+              value: 'metadata value',
+              entity_id: 'entity_1'
+            }
+          ]
+        }
+
+        mock.onDelete('/metadata/metadata_1/').reply(204)
+
+        await store.deleteMetadata('element_1', {
+          id: 'metadata_1'
+        })
+
+        assert.deepStrictEqual(store.metadata, { element_1: [] })
+      })
+    })
+
+    describe('listMetadata', () => {
+      it('List MetaData of an element', async () => {
+        mock.onGet('/element/element_1/metadata/').reply(200, [
+          {
+            id: 'metadata_1',
+            name: 'Metadata',
+            type: 'text',
+            value: 'metadata value',
+            entity_id: 'entity_1'
+          },
+          {
+            id: 'metadata_2',
+            name: 'Metadata',
+            type: 'text',
+            value: 'metadata value',
+            entity_id: 'entity_2'
+          }
+        ])
+
+        await store.listMetadata('element_1')
+
+        assert.deepStrictEqual(store.metadata, {
+          element_1: [
+            {
+              id: 'metadata_1',
+              name: 'Metadata',
+              type: 'text',
+              value: 'metadata value',
+              entity_id: 'entity_1'
+            },
+            {
+              id: 'metadata_2',
+              name: 'Metadata',
+              type: 'text',
+              value: 'metadata value',
+              entity_id: 'entity_2'
+            }
+          ]
+        })
+      })
+    })
+  })
+})
-- 
GitLab