diff --git a/bun.lockb b/bun.lockb
index 94886d7ec58aab76974728e32acbce8e5b53710b..60c32177a430fc1b9b24ea51dc61fd09c778caf1 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 548cf5cda331f384e8c4f132516a91c44403dfac..618e5a6384f3c1e38bf1f17daf499bb0011f0e63 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
   "version": "1.0.0",
   "scripts": {
     "build": "tsc",
-    "test": "echo \"Error: no test specified\" && exit 1",
+    "test": "vitest",
     "dev": "bun run --watch --inspect=6498 src/index.ts",
     "start": "bun run --production src/index.ts",
     "lint": "eslint src/**/* --no-error-on-unmatched-pattern"
@@ -20,7 +20,8 @@
     "@typescript-eslint/eslint-plugin": "^8.19.1",
     "@typescript-eslint/parser": "^8.19.1",
     "eslint": "^9.17.0",
-    "typescript-eslint": "^8.19.1"
+    "typescript-eslint": "^8.19.1",
+    "vitest": "^3.0.8"
   },
   "peerDependencies": {
     "typescript": "^5.0.0"
diff --git a/src/readers/diffCheck.ts b/src/readers/diffCheck.ts
index ba3dd09995ef4a2070b372f7e2363a18ca85f957..eba2c6cc1a131b5a8434019cfe8affebbd47792b 100644
--- a/src/readers/diffCheck.ts
+++ b/src/readers/diffCheck.ts
@@ -5,6 +5,10 @@ import type { GraphQueryResultMetaFromBackend } from 'ts-common/src/model/webSoc
 import { ums } from '../variables';
 import type { InsightModel } from 'ts-common';
 
+export const compareHashedQueryResults = (previousHash: string | null, currentHash: string): boolean => {
+  return !previousHash || !hashIsEqual(currentHash, previousHash);
+};
+
 export const diffCheck = async (
   insight: InsightModel,
   ss: SaveState,
@@ -20,7 +24,8 @@ export const diffCheck = async (
   });
 
   log.debug('Comparing hash values from current and previous query');
-  const changed = !previousQueryResult || !hashIsEqual(queryResultHash, previousQueryResult);
+  const changed = compareHashedQueryResults(previousQueryResult, queryResultHash);
+
   insight.status ||= changed;
   log.debug('Updated node and edge ids in SaveState');
 
diff --git a/src/readers/queryService.ts b/src/readers/queryService.ts
index 4609e9363341d77ad898702ae7ee2ab4cb955347..765a995e01b8c4b37126cd917a62ad5f4de574ba 100644
--- a/src/readers/queryService.ts
+++ b/src/readers/queryService.ts
@@ -87,6 +87,8 @@ export const queryServiceReader = async (frontendPublisher: RabbitMqBroker, mlPu
   }
   log.info('Starting query reader for', type);
 
+  const publisher = new QueryPublisher(frontendPublisher, mlPublisher);
+
   const queryServiceConsumer = await new RabbitMqBroker(
     rabbitMq,
     'requests-exchange',
diff --git a/src/readers/statCheck.ts b/src/readers/statCheck.ts
index 96ea6556b1db0472ddba29e5fa8def1036598bf6..578f9cb365ae8bc894aba24c471773aedf630bff 100644
--- a/src/readers/statCheck.ts
+++ b/src/readers/statCheck.ts
@@ -1,7 +1,7 @@
 import type { GraphQueryResultMetaFromBackend, InsightModel } from 'ts-common';
 import { log } from '../logger';
 
-function processAlarmStats(alarmStat: InsightModel, resultQuery: GraphQueryResultMetaFromBackend): boolean {
+export function processAlarmStats(alarmStat: InsightModel, resultQuery: GraphQueryResultMetaFromBackend): boolean {
   for (const condition of alarmStat.conditionsCheck) {
     const ssInsightNode = condition.nodeLabel;
     const ssInsightStatistic = condition.statistic;
diff --git a/src/tests/insights/diffCheck.test.ts b/src/tests/insights/diffCheck.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c0d0c6490d7c5cd1809c877b213ed04f6b8ecb13
--- /dev/null
+++ b/src/tests/insights/diffCheck.test.ts
@@ -0,0 +1,68 @@
+import { expect, test, describe, it } from 'vitest';
+import type { GraphQueryResultMetaFromBackend } from 'ts-common';
+import { hashDictionary, hashIsEqual } from '../../utils/hashing';
+import { compareHashedQueryResults } from './../../readers/diffCheck';
+import { Logger } from 'ts-common';
+
+Logger.excludedOwners.push('ts-common');
+Logger.excludedOwners.push('query-service');
+
+describe('Hash Comparison Tests', () => {
+  it('should detect different hashes for different graph structures', () => {
+    // First query result
+    const query1: GraphQueryResultMetaFromBackend = {
+      nodes: [{ _id: 'node1' }, { _id: 'node2' }],
+      edges: [{ _id: 'edge1' }],
+    } as GraphQueryResultMetaFromBackend;
+
+    // Second query result with different structure
+    const query2: GraphQueryResultMetaFromBackend = {
+      nodes: [{ _id: 'node1' }, { _id: 'node2' }, { _id: 'node3' }],
+      edges: [{ _id: 'edge1' }, { _id: 'edge2' }],
+    } as GraphQueryResultMetaFromBackend;
+
+    const hash1 = hashDictionary({
+      nodes: query1.nodes.map(node => node._id),
+      edges: query1.edges.map(edge => edge._id),
+    });
+
+    const hash2 = hashDictionary({
+      nodes: query2.nodes.map(node => node._id),
+      edges: query2.edges.map(edge => edge._id),
+    });
+
+    // Test direct hash comparison
+    expect(hashIsEqual(hash1, hash2)).toBe(false);
+
+    // Test using compareHashedQueryResults
+    expect(compareHashedQueryResults(hash1, hash2)).toBe(true);
+  });
+
+  it('should detect identical hashes for same graph structures', () => {
+    const query1 = {
+      nodes: [{ _id: 'node1' }, { _id: 'node2' }],
+      edges: [{ _id: 'edge1' }],
+    } as GraphQueryResultMetaFromBackend;
+
+    const query2 = {
+      nodes: [{ _id: 'node1' }, { _id: 'node2' }],
+      edges: [{ _id: 'edge1' }],
+    } as GraphQueryResultMetaFromBackend;
+
+    const hash1 = hashDictionary({
+      nodes: query1.nodes.map(node => node._id),
+      edges: query1.edges.map(edge => edge._id),
+    });
+
+    const hash2 = hashDictionary({
+      nodes: query2.nodes.map(node => node._id),
+      edges: query2.edges.map(edge => edge._id),
+    });
+
+    // Test direct hash comparison
+    expect(hashIsEqual(hash1, hash2)).toBe(true);
+
+    // Test using compareHashedQueryResults
+    expect(compareHashedQueryResults(hash1, hash2)).toBe(false);
+  });
+});
diff --git a/src/tests/insights/populateTemplate.test.ts b/src/tests/insights/populateTemplate.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ce8e70b01e58981bfc110d9f46eb6872d1f37ead
--- /dev/null
+++ b/src/tests/insights/populateTemplate.test.ts
@@ -0,0 +1,54 @@
+import { expect, test, describe, it } from 'vitest';
+import { populateTemplate } from './../../utils/insights';
+import { type GraphQueryResultMetaFromBackend } from 'ts-common';
+import { Logger } from 'ts-common';
+
+Logger.excludedOwners.push('ts-common');
+Logger.excludedOwners.push('query-service');
+
+const mockResult: GraphQueryResultMetaFromBackend = {
+  metaData: {
+    topological: {
+      density: 0,
+      self_loops: 0,
+    },
+    nodes: {
+      count: 1,
+      labels: ['NodeTypeA'],
+      types: {
+        NodeTypeA: {
+          count: 1,
+          attributes: {
+            age: {
+              attributeType: 'number',
+              statistics: {
+                min: 10,
+                max: 50,
+                average: 42,
+                count: 100,
+              },
+            },
+          },
+        },
+      },
+    },
+    edges: {
+      count: 0,
+      labels: [],
+      types: {},
+    },
+  },
+  nodeCounts: { updatedAt: 2313 },
+  nodes: [],
+  edges: [],
+};
+
+describe('populateTemplate', () => {
+  it('should replace statistic variables correctly', async () => {
+    const template = 'The mean value is {{ statistic:NodeTypeA • age • average }}.';
+    const expectedOutput = 'The mean value is 42 .';
+    const result = await populateTemplate(template, mockResult, []);
+
+    expect(result).toBe(expectedOutput);
+  });
+});
diff --git a/src/tests/insights/statCheck.test.ts b/src/tests/insights/statCheck.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a6b1fd30cd5b37acf8b651f31faf86c489de8155
--- /dev/null
+++ b/src/tests/insights/statCheck.test.ts
@@ -0,0 +1,380 @@
+import { expect, test, describe, it } from 'vitest';
+import type { GraphQueryResultMetaFromBackend, InsightModel } from 'ts-common';
+import { processAlarmStats } from './../../readers/statCheck';
+import { Logger } from 'ts-common';
+
+Logger.excludedOwners.push('ts-common');
+Logger.excludedOwners.push('query-service');
+
+const baseInsight: Omit<InsightModel, 'conditionsCheck'> = {
+  id: 1,
+  name: 'Test Insight',
+  description: 'Base insight for testing',
+  recipients: ['test@example.com'],
+  frequency: 'daily',
+  template: 'default',
+  saveStateId: 'save-state-1',
+  type: 'alert',
+  alarmMode: 'conditional',
+  createdAt: new Date().toISOString(),
+  updatedAt: new Date().toISOString(),
+  previousResultHash: 'abc123',
+  lastProcessedAt: new Date().toISOString(),
+  status: true,
+};
+
+describe('QueryprocessAlarmStats', () => {
+  it('should return true when condition is met', () => {
+    const alarmStat: InsightModel = {
+      ...baseInsight,
+      conditionsCheck: [
+        {
+          nodeLabel: 'TestNode',
+          statistic: 'count',
+          operator: '>',
+          value: 1,
+        },
+      ],
+    };
+
+    const resultQuery: GraphQueryResultMetaFromBackend = {
+      nodes: [
+        {
+          _id: 'node1',
+          label: 'TestNode',
+          attributes: {
+            name: 'Test Node 1',
+            value: 123,
+          },
+        },
+        {
+          _id: 'node2',
+          label: 'TestNode',
+          attributes: {
+            name: 'Test Node 2',
+            value: 456,
+          },
+        },
+      ],
+      edges: [
+        {
+          _id: 'edge1',
+          label: 'CONNECTS',
+          from: 'node1',
+          to: 'node2',
+          attributes: {
+            weight: 1,
+            timestamp: '2025-03-06',
+          },
+        },
+      ],
+      metaData: {
+        topological: {
+          density: 0.5,
+          self_loops: 0,
+        },
+        nodes: {
+          count: 2,
+          labels: ['TestNode'],
+          types: {
+            TestNode: {
+              count: 2,
+              avgDegreeIn: 0.5,
+              avgDegreeOut: 0.5,
+              attributes: {
+                name: {
+                  attributeType: 'string',
+                  statistics: {
+                    uniqueItems: 2,
+                    values: ['Test Node 1', 'Test Node 2'],
+                    mode: 'Test Node 1',
+                    count: 2,
+                  },
+                },
+                value: {
+                  attributeType: 'number',
+                  statistics: {
+                    min: 123,
+                    max: 456,
+                    average: 289.5,
+                    count: 2,
+                  },
+                },
+              },
+            },
+          },
+        },
+        edges: {
+          count: 1,
+          labels: ['CONNECTS'],
+          types: {
+            CONNECTS: {
+              count: 1,
+              attributes: {
+                weight: {
+                  attributeType: 'number',
+                  statistics: {
+                    min: 1,
+                    max: 1,
+                    average: 1,
+                    count: 1,
+                  },
+                },
+                timestamp: {
+                  attributeType: 'datetime',
+                  statistics: {
+                    min: 1743782400,
+                    max: 1743782400,
+                    range: 0,
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+      nodeCounts: {
+        TestNode: 2,
+        updatedAt: 122331,
+      },
+    };
+
+    expect(processAlarmStats(alarmStat, resultQuery)).toBe(true);
+  });
+
+  it('should return false when condition is not met', () => {
+    const alarmStat: InsightModel = {
+      ...baseInsight,
+      conditionsCheck: [
+        {
+          nodeLabel: 'TestNode',
+          statistic: 'count',
+          operator: '<',
+          value: 1,
+        },
+      ],
+    };
+
+    const resultQuery: GraphQueryResultMetaFromBackend = {
+      nodes: [
+        {
+          _id: 'node1',
+          label: 'TestNode',
+          attributes: {
+            name: 'Test Node 1',
+            value: 123,
+          },
+        },
+        {
+          _id: 'node2',
+          label: 'TestNode',
+          attributes: {
+            name: 'Test Node 2',
+            value: 456,
+          },
+        },
+      ],
+      edges: [
+        {
+          _id: 'edge1',
+          label: 'CONNECTS',
+          from: 'node1',
+          to: 'node2',
+          attributes: {
+            weight: 1,
+            timestamp: '2025-03-06',
+          },
+        },
+      ],
+      metaData: {
+        topological: {
+          density: 0.5,
+          self_loops: 0,
+        },
+        nodes: {
+          count: 2,
+          labels: ['TestNode'],
+          types: {
+            TestNode: {
+              count: 2,
+              avgDegreeIn: 0.5,
+              avgDegreeOut: 0.5,
+              attributes: {
+                name: {
+                  attributeType: 'string',
+                  statistics: {
+                    uniqueItems: 2,
+                    values: ['Test Node 1', 'Test Node 2'],
+                    mode: 'Test Node 1',
+                    count: 2,
+                  },
+                },
+                value: {
+                  attributeType: 'number',
+                  statistics: {
+                    min: 123,
+                    max: 456,
+                    average: 289.5,
+                    count: 2,
+                  },
+                },
+              },
+            },
+          },
+        },
+        edges: {
+          count: 1,
+          labels: ['CONNECTS'],
+          types: {
+            CONNECTS: {
+              count: 1,
+              attributes: {
+                weight: {
+                  attributeType: 'number',
+                  statistics: {
+                    min: 1,
+                    max: 1,
+                    average: 1,
+                    count: 1,
+                  },
+                },
+                timestamp: {
+                  attributeType: 'datetime',
+                  statistics: {
+                    min: 1743782400,
+                    max: 1743782400,
+                    range: 0,
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+      nodeCounts: {
+        TestNode: 2,
+        updatedAt: 122331,
+      },
+    };
+
+    expect(processAlarmStats(alarmStat, resultQuery)).toBe(false);
+  });
+
+  it('should return false when nodeLabel is not found', () => {
+    const alarmStat: InsightModel = {
+      ...baseInsight,
+      conditionsCheck: [
+        {
+          nodeLabel: 'NonExistentNode',
+          statistic: 'count',
+          operator: '>',
+          value: 5,
+        },
+      ],
+    };
+
+    const resultQuery: GraphQueryResultMetaFromBackend = {
+      nodes: [
+        {
+          _id: 'node1',
+          label: 'TestNode',
+          attributes: {
+            name: 'Test Node 1',
+            value: 123,
+          },
+        },
+        {
+          _id: 'node2',
+          label: 'TestNode',
+          attributes: {
+            name: 'Test Node 2',
+            value: 456,
+          },
+        },
+      ],
+      edges: [
+        {
+          _id: 'edge1',
+          label: 'CONNECTS',
+          from: 'node1',
+          to: 'node2',
+          attributes: {
+            weight: 1,
+            timestamp: '2025-03-06',
+          },
+        },
+      ],
+      metaData: {
+        topological: {
+          density: 0.5,
+          self_loops: 0,
+        },
+        nodes: {
+          count: 2,
+          labels: ['TestNode'],
+          types: {
+            TestNode: {
+              count: 2,
+              avgDegreeIn: 0.5,
+              avgDegreeOut: 0.5,
+              attributes: {
+                name: {
+                  attributeType: 'string',
+                  statistics: {
+                    uniqueItems: 2,
+                    values: ['Test Node 1', 'Test Node 2'],
+                    mode: 'Test Node 1',
+                    count: 2,
+                  },
+                },
+                value: {
+                  attributeType: 'number',
+                  statistics: {
+                    min: 123,
+                    max: 456,
+                    average: 289.5,
+                    count: 2,
+                  },
+                },
+              },
+            },
+          },
+        },
+        edges: {
+          count: 1,
+          labels: ['CONNECTS'],
+          types: {
+            CONNECTS: {
+              count: 1,
+              attributes: {
+                weight: {
+                  attributeType: 'number',
+                  statistics: {
+                    min: 1,
+                    max: 1,
+                    average: 1,
+                    count: 1,
+                  },
+                },
+                timestamp: {
+                  attributeType: 'datetime',
+                  statistics: {
+                    min: 1743782400,
+                    max: 1743782400,
+                    range: 0,
+                  },
+                },
+              },
+            },
+          },
+        },
+      },
+      nodeCounts: {
+        TestNode: 2,
+        updatedAt: 122331,
+      },
+    };
+
+    expect(processAlarmStats(alarmStat, resultQuery)).toBe(false);
+  });
+});
diff --git a/src/utils/cypher/converter/queryConverter.test.ts b/src/tests/query/queryConverter.test.ts
similarity index 98%
rename from src/utils/cypher/converter/queryConverter.test.ts
rename to src/tests/query/queryConverter.test.ts
index 5227c6cd6ed1d867142f8bacd4718795b3702f23..f5e819d779bee110c7945c121fbc0e559481ee06 100644
--- a/src/utils/cypher/converter/queryConverter.test.ts
+++ b/src/tests/query/queryConverter.test.ts
@@ -1,6 +1,10 @@
-import { query2Cypher } from './queryConverter';
+import { query2Cypher } from '../../utils/cypher/converter/queryConverter';
 import { StringFilterTypes, type BackendQueryFormat } from 'ts-common';
-import { expect, test, describe, it } from 'bun:test';
+import { expect, describe, it } from 'vitest';
+import { Logger } from 'ts-common';
+
+Logger.excludedOwners.push('ts-common');
+Logger.excludedOwners.push('query-service');
 
 function fixCypherSpaces(cypher?: string | null): string {
   if (!cypher) {
diff --git a/src/tests/query/queryTranslator/queryTranslator.test.ts b/src/tests/query/queryTranslator/queryTranslator.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..85e7d47fbb1e5edf21f3856333ced1dba5aa9505
--- /dev/null
+++ b/src/tests/query/queryTranslator/queryTranslator.test.ts
@@ -0,0 +1,96 @@
+import { expect, describe, it } from 'vitest';
+import type { QueryMultiGraph } from 'ts-common/src/model/graphology';
+import { MLTypesEnum } from 'ts-common/src/model/query/machineLearningModel';
+import { type QueryBuilderSettings } from 'ts-common/src/model/query/queryBuilderModel';
+import { Query2BackendQuery } from './../../../utils/reactflow/query2backend';
+import type { MachineLearning } from 'ts-common/src/model/query/queryRequestModel';
+import { type BackendQueryFormat } from 'ts-common';
+import { createQueryMultiGraphFromData, settingsBase, ss_id } from './testData';
+import { visualQuery_1 } from './testData';
+import { expectedResult_1 } from './testData';
+import { Logger } from 'ts-common';
+
+Logger.excludedOwners.push('ts-common');
+Logger.excludedOwners.push('query-service');
+
+describe('query2backend', () => {
+  it('should return correctly a node - 0', () => {
+    const nodesData = [
+      {
+        id: 'Movie',
+        schemaKey: 'Movie',
+        type: 'entity',
+        width: 100,
+        height: 100,
+        x: 50,
+        y: 50,
+        name: 'Movie',
+        attributes: [
+          { name: '(# Connection)', type: 'float' },
+          { name: 'tagline', type: 'string' },
+          { name: 'votes', type: 'int' },
+          { name: 'title', type: 'string' },
+          { name: 'released', type: 'int' },
+        ],
+      },
+    ];
+
+    const visualQuery: QueryMultiGraph = createQueryMultiGraphFromData(nodesData, []);
+
+    const ml: MachineLearning[] = [
+      { type: MLTypesEnum.linkPrediction, parameters: [], id: 1 },
+      { type: MLTypesEnum.centrality, parameters: [], id: 2 },
+      { type: MLTypesEnum.communityDetection, parameters: [], id: 3 },
+      { type: MLTypesEnum.shortestPath, parameters: [], id: 4 },
+    ];
+
+    const result = Query2BackendQuery(ss_id, visualQuery, settingsBase, ml);
+    const expectedResult: BackendQueryFormat = {
+      saveStateID: 'test',
+      query: [
+        {
+          id: 'path_0',
+          node: {
+            label: 'Movie',
+            id: 'Movie',
+            relation: undefined,
+          },
+        },
+      ],
+      machineLearning: [
+        { type: MLTypesEnum.linkPrediction, parameters: [], id: 1 },
+        { type: MLTypesEnum.centrality, parameters: [], id: 2 },
+        { type: MLTypesEnum.communityDetection, parameters: [], id: 3 },
+        { type: MLTypesEnum.shortestPath, parameters: [], id: 4 },
+      ],
+      limit: 500,
+      return: ['*'],
+      cached: false,
+      logic: undefined,
+    };
+    expect(result).toEqual(expectedResult);
+  });
+  it('should return correctly on a simple query with multiple paths - 1', () => {
+    const ml: MachineLearning[] = [
+      { type: MLTypesEnum.linkPrediction, parameters: [], id: 1 },
+      { type: MLTypesEnum.centrality, parameters: [], id: 2 },
+      { type: MLTypesEnum.communityDetection, parameters: [], id: 3 },
+      { type: MLTypesEnum.shortestPath, parameters: [], id: 4 },
+    ];
+
+    const result = Query2BackendQuery(ss_id, visualQuery_1, settingsBase, ml);
+
+    expect(result).toEqual(expectedResult_1);
+  });
+  /*
+  it('should return correctly on a complex query with logic', () => {});
+  it('should return correctly on a query with group by logic', () => {});
+  it('should return correctly on a query with no label', () => {});
+  it('should return correctly on a query with no depth', () => {});
+  it('should return correctly on a query with average calculation', () => {});
+  it('should return correctly on a query with average calculation and multiple paths', () => {});
+  it('should return correctly on a single entity query with lower like logic', () => {});
+  it('should return correctly on a query with like logic', () => {});
+  it('should return correctly on a query with both direction relation', () => {});
+*/
+});
diff --git a/src/tests/query/queryTranslator/testData.ts b/src/tests/query/queryTranslator/testData.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6a3154ec2179a1f5a4c584ea54b359355228caf9
--- /dev/null
+++ b/src/tests/query/queryTranslator/testData.ts
@@ -0,0 +1,541 @@
+import type { QueryMultiGraph, QueryGraphNodes, QueryGraphEdges } from 'ts-common/src/model/graphology';
+import type { SerializedNode, SerializedEdge } from 'graphology-types';
+import { Handles, QueryElementTypes } from 'ts-common/src/model/reactflow';
+import { type BackendQueryFormat } from 'ts-common';
+import { MLTypesEnum } from 'ts-common/src/model/query/machineLearningModel';
+import { type QueryBuilderSettings } from 'ts-common/src/model/query/queryBuilderModel';
+
+export function createQueryMultiGraphFromData(nodesData: any[], edgesData: any[]): QueryMultiGraph {
+  const nodes: SerializedNode<QueryGraphNodes>[] = nodesData.map(node => ({
+    key: node.id,
+    attributes: {
+      id: node.id,
+      name: node.name,
+      schemaKey: node.schemaKey,
+      type: node.type,
+      width: node.width,
+      height: node.height,
+      x: node.x,
+      y: node.y,
+      attributes: node.attributes.map((attribute: any) => ({
+        handleData: {
+          nodeId: node.id,
+          nodeName: node.name,
+          nodeType: node.type,
+          handleType: 'entityAttributeHandle' as Handles, // check if different reactflow Handles
+          attributeName: attribute.name,
+          attributeType: attribute.type,
+        },
+      })),
+      leftRelationHandleId: {
+        nodeId: node.id,
+        nodeName: node.name,
+        nodeType: node.type,
+        handleType: 'entityLeftHandle' as Handles,
+      },
+      rightRelationHandleId: {
+        nodeId: node.id,
+        nodeName: node.name,
+        nodeType: node.type,
+        handleType: 'entityRightHandle' as Handles,
+      },
+      selected: node.selected || false,
+    },
+  }));
+
+  const edges: SerializedEdge<QueryGraphEdges>[] = edgesData.map(edge => ({
+    source: edge.sourceNodeId,
+    target: edge.targetNodeId,
+    type: edge.type,
+    sourceHandleData: {
+      nodeId: edge.sourceNodeId,
+      nodeName: edge.sourceNodeName,
+      nodeType: edge.sourceNodeType,
+      handleType: edge.sourceHandleType,
+      attributeName: edge.sourceAttributeName,
+      attributeType: edge.sourceAttributeType,
+    },
+    targetHandleData: {
+      nodeId: edge.targetNodeId,
+      nodeName: edge.targetNodeName,
+      nodeType: edge.targetNodeType,
+      handleType: edge.targetHandleType,
+      attributeName: edge.targetAttributeName,
+      attributeType: edge.targetAttributeType,
+    },
+  }));
+
+  return {
+    nodes: nodes,
+    edges: edges,
+    options: {
+      type: 'mixed',
+      multi: true,
+      allowSelfLoops: false,
+    },
+    attributes: {},
+  };
+}
+
+export const ss_id: string = 'test';
+export const settingsBase: QueryBuilderSettings = {
+  depth: {
+    max: 1,
+    min: 1,
+  },
+  limit: 500,
+  layout: 'manual',
+  unionTypes: {},
+  autocompleteRelation: true,
+};
+
+export const visualQuery_1: QueryMultiGraph = {
+  edges: [
+    {
+      key: 'geid_183_0',
+      source: 'id_1741246511422',
+      target: 'id_1741246512287',
+      attributes: {
+        type: 'connection',
+        sourceHandleData: {
+          nodeId: 'id_1741246511422',
+          nodeName: 'Person',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityRight,
+          attributeName: '',
+        },
+        targetHandleData: {
+          nodeId: 'id_1741246512287',
+          nodeName: 'WROTE',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationLeft,
+          attributeName: '',
+        },
+      },
+    },
+    {
+      key: 'geid_183_1',
+      source: 'id_1741246512287',
+      target: 'id_1741246511585',
+      attributes: {
+        type: 'connection',
+        sourceHandleData: {
+          nodeId: 'id_1741246512287',
+          nodeName: 'WROTE',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationRight,
+          attributeName: '',
+        },
+        targetHandleData: {
+          nodeId: 'id_1741246511585',
+          nodeName: 'Movie',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityLeft,
+          attributeName: '',
+        },
+      },
+    },
+    {
+      key: 'geid_262_0',
+      source: 'id_1741246511422',
+      target: 'id_1741246625352',
+      attributes: {
+        type: 'connection',
+        sourceHandleData: {
+          nodeId: 'id_1741246511422',
+          nodeName: 'Person',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityRight,
+          attributeName: '',
+        },
+        targetHandleData: {
+          nodeId: 'id_1741246625352',
+          nodeName: 'PRODUCED',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationLeft,
+          attributeName: '',
+        },
+      },
+    },
+    {
+      key: 'geid_319_0',
+      source: 'id_1741246625352',
+      target: 'id_1741246630119',
+      attributes: {
+        type: 'connection',
+        sourceHandleData: {
+          nodeId: 'id_1741246625352',
+          nodeName: 'PRODUCED',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationRight,
+          attributeName: '',
+        },
+        targetHandleData: {
+          nodeId: 'id_1741246630119',
+          nodeName: 'Movie',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityLeft,
+          attributeName: '',
+        },
+      },
+    },
+  ],
+  nodes: [
+    {
+      key: 'id_1741246512287',
+      attributes: {
+        x: 180,
+        y: 90,
+        id: 'id_1741246512287',
+        name: 'WROTE',
+        type: QueryElementTypes.Relation,
+        depth: {
+          max: 1,
+          min: 1,
+        },
+        width: 86.15010000000001,
+        height: 20,
+        schemaKey: 'WROTE_PersonMovie',
+        attributes: [
+          {
+            handleData: {
+              nodeId: 'id_1741246512287',
+              nodeName: 'WROTE',
+              nodeType: QueryElementTypes.Relation,
+              handleType: Handles.RelationAttribute,
+              attributeName: '(# Connection)',
+              attributeType: 'float',
+            },
+          },
+        ],
+        collection: 'WROTE',
+        leftEntityHandleId: {
+          nodeId: 'id_1741246512287',
+          nodeName: 'WROTE',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationLeft,
+        },
+        rightEntityHandleId: {
+          nodeId: 'id_1741246512287',
+          nodeName: 'WROTE',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationRight,
+        },
+      },
+    },
+    {
+      key: 'id_1741246511422',
+      attributes: {
+        x: 0,
+        y: 90,
+        id: 'id_1741246511422',
+        name: 'Person',
+        type: QueryElementTypes.Entity,
+        width: 78.2166,
+        height: 20,
+        schemaKey: 'Person',
+        attributes: [
+          {
+            handleData: {
+              nodeId: 'id_1741246511422',
+              nodeName: 'Person',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: '(# Connection)',
+              attributeType: 'float',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246511422',
+              nodeName: 'Person',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: 'name',
+              attributeType: 'string',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246511422',
+              nodeName: 'Person',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: 'born',
+              attributeType: 'int',
+            },
+          },
+        ],
+        leftRelationHandleId: {
+          nodeId: 'id_1741246511422',
+          nodeName: 'Person',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityLeft,
+        },
+        rightRelationHandleId: {
+          nodeId: 'id_1741246511422',
+          nodeName: 'Person',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityRight,
+        },
+      },
+    },
+    {
+      key: 'id_1741246511585',
+      attributes: {
+        x: 430,
+        y: 90,
+        id: 'id_1741246511585',
+        name: 'Movie',
+        type: QueryElementTypes.Entity,
+        width: 72.1999,
+        height: 20,
+        schemaKey: 'Movie',
+        attributes: [
+          {
+            handleData: {
+              nodeId: 'id_1741246511585',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: '(# Connection)',
+              attributeType: 'float',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246511585',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.RelationAttribute,
+              attributeName: 'tagline',
+              attributeType: 'string',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246511585',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: 'votes',
+              attributeType: 'int',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246511585',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: 'title',
+              attributeType: 'string',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246511585',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: 'released',
+              attributeType: 'int',
+            },
+          },
+        ],
+        leftRelationHandleId: {
+          nodeId: 'id_1741246511585',
+          nodeName: 'Movie',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityLeft,
+        },
+        rightRelationHandleId: {
+          nodeId: 'id_1741246511585',
+          nodeName: 'Movie',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityRight,
+        },
+      },
+    },
+    {
+      key: 'id_1741246625352',
+      attributes: {
+        x: 180,
+        y: 170,
+        id: 'id_1741246625352',
+        name: 'PRODUCED',
+        type: QueryElementTypes.Relation,
+        depth: {
+          max: 1,
+          min: 1,
+        },
+        width: 104.2002,
+        height: 20,
+        schemaKey: 'PRODUCED_PersonMovie',
+        attributes: [
+          {
+            handleData: {
+              nodeId: 'id_1741246625352',
+              nodeName: 'PRODUCED',
+              nodeType: QueryElementTypes.Relation,
+              handleType: Handles.RelationAttribute,
+              attributeName: '(# Connection)',
+              attributeType: 'float',
+            },
+          },
+        ],
+        collection: 'PRODUCED',
+        leftEntityHandleId: {
+          nodeId: 'id_1741246625352',
+          nodeName: 'PRODUCED',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationLeft,
+        },
+        rightEntityHandleId: {
+          nodeId: 'id_1741246625352',
+          nodeName: 'PRODUCED',
+          nodeType: QueryElementTypes.Relation,
+          handleType: Handles.RelationRight,
+        },
+      },
+    },
+    {
+      key: 'id_1741246630119',
+      attributes: {
+        x: 390,
+        y: 200,
+        id: 'id_1741246630119',
+        name: 'Movie',
+        type: QueryElementTypes.Entity,
+        width: 72.1999,
+        height: 20,
+        schemaKey: 'Movie',
+        attributes: [
+          {
+            handleData: {
+              nodeId: 'id_1741246630119',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: '(# Connection)',
+              attributeType: 'float',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246630119',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.RelationAttribute,
+              attributeName: 'tagline',
+              attributeType: 'string',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246630119',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.RelationAttribute,
+              attributeName: 'votes',
+              attributeType: 'int',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246630119',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.EntityAttribute,
+              attributeName: 'title',
+              attributeType: 'string',
+            },
+          },
+          {
+            handleData: {
+              nodeId: 'id_1741246630119',
+              nodeName: 'Movie',
+              nodeType: QueryElementTypes.Entity,
+              handleType: Handles.RelationAttribute,
+              attributeName: 'released',
+              attributeType: 'int',
+            },
+          },
+        ],
+        leftRelationHandleId: {
+          nodeId: 'id_1741246630119',
+          nodeName: 'Movie',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityLeft,
+        },
+        rightRelationHandleId: {
+          nodeId: 'id_1741246630119',
+          nodeName: 'Movie',
+          nodeType: QueryElementTypes.Entity,
+          handleType: Handles.EntityRight,
+        },
+      },
+    },
+  ],
+  options: {
+    type: 'mixed',
+    multi: true,
+    allowSelfLoops: true,
+  },
+  attributes: {},
+};
+export const expectedResult_1: BackendQueryFormat = {
+  saveStateID: 'test',
+  return: ['*'],
+  query: [
+    {
+      id: 'path_0',
+      node: {
+        id: 'id_1741246511422',
+        label: 'Person',
+        relation: {
+          id: 'id_1741246512287',
+          label: 'WROTE',
+          depth: {
+            max: 1,
+            min: 1,
+          },
+          direction: 'BOTH',
+          node: {
+            id: 'id_1741246511585',
+            label: 'Movie',
+          },
+        },
+      },
+    },
+    {
+      id: 'path_1',
+      node: {
+        id: 'id_1741246511422',
+        label: 'Person',
+        relation: {
+          id: 'id_1741246625352',
+          label: 'PRODUCED',
+          depth: {
+            max: 1,
+            min: 1,
+          },
+          direction: 'BOTH',
+          node: {
+            id: 'id_1741246630119',
+            label: 'Movie',
+          },
+        },
+      },
+    },
+  ],
+  machineLearning: [
+    { type: MLTypesEnum.linkPrediction, parameters: [], id: 1 },
+    { type: MLTypesEnum.centrality, parameters: [], id: 2 },
+    { type: MLTypesEnum.communityDetection, parameters: [], id: 3 },
+    { type: MLTypesEnum.shortestPath, parameters: [], id: 4 },
+  ],
+  limit: 500,
+  cached: false,
+  logic: undefined,
+};
diff --git a/src/utils/queryPublisher.ts b/src/utils/queryPublisher.ts
index d10bea719a1387114ca8c66ff08a5f42bc2b0930..ca17e2f169c136a5f7f73df401a68fe086749eb0 100644
--- a/src/utils/queryPublisher.ts
+++ b/src/utils/queryPublisher.ts
@@ -6,19 +6,35 @@ import type { RabbitMqBroker } from 'ts-common/rabbitMq';
 export class QueryPublisher {
   private frontendPublisher: RabbitMqBroker;
   private mlPublisher: RabbitMqBroker;
-  private routingKey: string;
-  private headers: BackendMessageHeader;
-  private queryID: number;
+  private routingKey?: string;
+  private headers?: BackendMessageHeader;
+  private queryID?: string;
 
-  constructor(frontendPublisher: RabbitMqBroker, mlPublisher: RabbitMqBroker, headers: BackendMessageHeader, queryID: number) {
+  constructor(frontendPublisher: RabbitMqBroker, mlPublisher: RabbitMqBroker) {
     this.frontendPublisher = frontendPublisher;
     this.mlPublisher = mlPublisher;
+  }
+
+  withHeaders(headers?: BackendMessageHeader) {
     this.headers = headers;
-    this.routingKey = headers.routingKey;
+    return this;
+  }
+
+  withRoutingKey(routingKey?: string) {
+    this.routingKey = routingKey;
+    return this;
+  }
+
+  withQueryID(queryID?: string) {
     this.queryID = queryID;
+    return this;
   }
 
   publishStatusToFrontend(status: string) {
+    if (!this.headers || !this.routingKey || !this.queryID) {
+      throw new Error('Headers or RoutingKey or queryID not set');
+    }
+
     this.frontendPublisher.publishMessageToFrontend(
       {
         type: wsReturnKey.queryStatusUpdate,
@@ -32,6 +48,10 @@ export class QueryPublisher {
   }
 
   publishErrorToFrontend(reason: string) {
+    if (!this.headers || !this.routingKey || !this.queryID) {
+      throw new Error('Headers or RoutingKey or queryID not set');
+    }
+
     this.frontendPublisher.publishMessageToFrontend(
       {
         type: wsReturnKey.queryStatusError,
@@ -45,13 +65,17 @@ export class QueryPublisher {
   }
 
   publishTranslationResultToFrontend(query: string) {
+    if (!this.headers || !this.routingKey || !this.queryID) {
+      throw new Error('Headers or RoutingKey or queryID not set');
+    }
+
     this.frontendPublisher.publishMessageToFrontend(
       {
         type: wsReturnKey.queryStatusTranslationResult,
         callID: this.headers.callID,
         value: {
           result: query,
-          queryID: this.queryID,
+          queryID: this.headers.callID,
         },
         status: 'success',
       },
@@ -61,6 +85,10 @@ export class QueryPublisher {
   }
 
   publishResultToFrontend(result: GraphQueryResultMetaFromBackend) {
+    if (!this.headers || !this.routingKey || !this.queryID) {
+      throw new Error('Headers or RoutingKey or queryID not set');
+    }
+
     this.frontendPublisher.publishMessageToFrontend(
       {
         type: wsReturnKey.queryStatusResult,
@@ -70,7 +98,7 @@ export class QueryPublisher {
             type: 'nodelink',
             payload: result,
           },
-          queryID: this.queryID,
+          queryID: this.headers.callID,
         },
         status: 'success',
       },
@@ -80,6 +108,10 @@ export class QueryPublisher {
   }
 
   publishMachineLearningRequest(result: GraphQueryResultFromBackend, mlAttributes: MachineLearning, headers: BackendMessageHeader) {
+    if (!this.headers || !this.routingKey) {
+      throw new Error('Headers or RoutingKey or queryID not set');
+    }
+
     // FIXME: Change ML to use the same message format that the frontend uses
     const toMlResult = {
       nodes: result.nodes.map(node => ({ ...node, id: node._id })),