From 044faf06ece02d30f963bb98f3e87cab6d36b57b Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 7 Mar 2025 15:05:09 +0100
Subject: [PATCH 1/8] test: adds test with vite

---
 bun.lockb                                     | Bin 150175 -> 182704 bytes
 package.json                                  |   3 +-
 .../query}/queryConverter.test.ts             |   4 +-
 .../queryTranslator/queryTranslator.test.ts   |  92 +++
 src/tests/query/queryTranslator/testData.ts   | 541 ++++++++++++++++++
 5 files changed, 637 insertions(+), 3 deletions(-)
 rename src/{utils/cypher/converter => tests/query}/queryConverter.test.ts (99%)
 create mode 100644 src/tests/query/queryTranslator/queryTranslator.test.ts
 create mode 100644 src/tests/query/queryTranslator/testData.ts

diff --git a/bun.lockb b/bun.lockb
index 94886d7ec58aab76974728e32acbce8e5b53710b..2207e737365016760d1a78a9f35c8d1a457ac494 100755
GIT binary patch
delta 35857
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

delta 15083
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
At^fc4

diff --git a/package.json b/package.json
index 548cf5c..97913ac 100644
--- a/package.json
+++ b/package.json
@@ -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/utils/cypher/converter/queryConverter.test.ts b/src/tests/query/queryConverter.test.ts
similarity index 99%
rename from src/utils/cypher/converter/queryConverter.test.ts
rename to src/tests/query/queryConverter.test.ts
index 5227c6c..e35f7b4 100644
--- a/src/utils/cypher/converter/queryConverter.test.ts
+++ b/src/tests/query/queryConverter.test.ts
@@ -1,6 +1,6 @@
-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';
 
 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 0000000..c18a968
--- /dev/null
+++ b/src/tests/query/queryTranslator/queryTranslator.test.ts
@@ -0,0 +1,92 @@
+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';
+
+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 0000000..6a3154e
--- /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,
+};
-- 
GitLab


From 8891e85df5c79aa4046512dbbc6d0bc619385bb9 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Tue, 11 Mar 2025 11:51:50 +0100
Subject: [PATCH 2/8] feat: adds updates

---
 bun.lockb                   | Bin 182704 -> 182704 bytes
 src/readers/queryService.ts |   2 ++
 src/utils/queryPublisher.ts |  46 ++++++++++++++++++++++++++++++------
 3 files changed, 41 insertions(+), 7 deletions(-)

diff --git a/bun.lockb b/bun.lockb
index 2207e737365016760d1a78a9f35c8d1a457ac494..60c32177a430fc1b9b24ea51dc61fd09c778caf1 100755
GIT binary patch
delta 25
hcmdlmnR~-z?uIRl|6i~(#u@7w=vi!Md&#)!0RV@Y3P%6{

delta 25
ecmdlmnR~-z?uIRl|6i~(F@V8#wwH{n9smG(nF#X$

diff --git a/src/readers/queryService.ts b/src/readers/queryService.ts
index 4609e93..765a995 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/utils/queryPublisher.ts b/src/utils/queryPublisher.ts
index d10bea7..ca17e2f 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 })),
-- 
GitLab


From 46bd50738a8af7315fb15e040b09eacb02c5c629 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 14 Mar 2025 11:40:13 +0100
Subject: [PATCH 3/8] test: adds ts logger

---
 package.json                                            | 2 +-
 src/tests/query/queryConverter.test.ts                  | 3 +++
 src/tests/query/queryTranslator/queryTranslator.test.ts | 3 +++
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 97913ac..618e5a6 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"
diff --git a/src/tests/query/queryConverter.test.ts b/src/tests/query/queryConverter.test.ts
index e35f7b4..81b5c0e 100644
--- a/src/tests/query/queryConverter.test.ts
+++ b/src/tests/query/queryConverter.test.ts
@@ -1,6 +1,9 @@
 import { query2Cypher } from '../../utils/cypher/converter/queryConverter';
 import { StringFilterTypes, type BackendQueryFormat } from 'ts-common';
 import { expect, describe, it } from 'vitest';
+import { Logger } from 'ts-common';
+
+Logger.excludedOwners.push('ts-common');
 
 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
index c18a968..3a7ebdb 100644
--- a/src/tests/query/queryTranslator/queryTranslator.test.ts
+++ b/src/tests/query/queryTranslator/queryTranslator.test.ts
@@ -8,6 +8,9 @@ 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');
 
 describe('query2backend', () => {
   it('should return correctly a node - 0', () => {
-- 
GitLab


From 8412bf264035ac45a688828592255d17d94dccb9 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 14 Mar 2025 11:52:59 +0100
Subject: [PATCH 4/8] test: query logger

---
 src/tests/query/queryConverter.test.ts                  | 1 +
 src/tests/query/queryTranslator/queryTranslator.test.ts | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/tests/query/queryConverter.test.ts b/src/tests/query/queryConverter.test.ts
index 81b5c0e..f5e819d 100644
--- a/src/tests/query/queryConverter.test.ts
+++ b/src/tests/query/queryConverter.test.ts
@@ -4,6 +4,7 @@ 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
index 3a7ebdb..85e7d47 100644
--- a/src/tests/query/queryTranslator/queryTranslator.test.ts
+++ b/src/tests/query/queryTranslator/queryTranslator.test.ts
@@ -11,6 +11,7 @@ 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', () => {
-- 
GitLab


From 577604e8b7ca6292a6f2bdde0749741790722325 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 7 Mar 2025 15:53:37 +0100
Subject: [PATCH 5/8] test: adds tests for insights

---
 src/readers/diffCheck.ts             |   7 +-
 src/readers/statCheck.ts             |   2 +-
 src/tests/insights/diffCheck.test.ts |  64 +++++
 src/tests/insights/statCheck.test.ts | 376 +++++++++++++++++++++++++++
 4 files changed, 447 insertions(+), 2 deletions(-)
 create mode 100644 src/tests/insights/diffCheck.test.ts
 create mode 100644 src/tests/insights/statCheck.test.ts

diff --git a/src/readers/diffCheck.ts b/src/readers/diffCheck.ts
index ba3dd09..eba2c6c 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/statCheck.ts b/src/readers/statCheck.ts
index 96ea655..578f9cb 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 0000000..5832586
--- /dev/null
+++ b/src/tests/insights/diffCheck.test.ts
@@ -0,0 +1,64 @@
+import { expect, test, describe, it } from 'bun:test';
+import type { GraphQueryResultMetaFromBackend } from 'ts-common';
+import { hashDictionary, hashIsEqual } from '../../utils/hashing';
+import { compareHashedQueryResults } from './../../readers/diffCheck';
+
+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/statCheck.test.ts b/src/tests/insights/statCheck.test.ts
new file mode 100644
index 0000000..b79e5ae
--- /dev/null
+++ b/src/tests/insights/statCheck.test.ts
@@ -0,0 +1,376 @@
+import { expect, test, describe, it } from 'bun:test';
+import type { GraphQueryResultMetaFromBackend, InsightModel } from 'ts-common';
+import { processAlarmStats } from './../../readers/statCheck';
+
+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);
+  });
+});
-- 
GitLab


From bb93fe1543650e535403acbc491d0828ad0ee81a Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 14 Mar 2025 11:52:00 +0100
Subject: [PATCH 6/8] test: update to vitest

---
 src/tests/insights/diffCheck.test.ts | 6 +++++-
 src/tests/insights/statCheck.test.ts | 6 +++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/tests/insights/diffCheck.test.ts b/src/tests/insights/diffCheck.test.ts
index 5832586..c0d0c64 100644
--- a/src/tests/insights/diffCheck.test.ts
+++ b/src/tests/insights/diffCheck.test.ts
@@ -1,7 +1,11 @@
-import { expect, test, describe, it } from 'bun:test';
+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', () => {
diff --git a/src/tests/insights/statCheck.test.ts b/src/tests/insights/statCheck.test.ts
index b79e5ae..a6b1fd3 100644
--- a/src/tests/insights/statCheck.test.ts
+++ b/src/tests/insights/statCheck.test.ts
@@ -1,6 +1,10 @@
-import { expect, test, describe, it } from 'bun:test';
+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,
-- 
GitLab


From cf27478a37800d6b792de31fbd0096b3fdb169da Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 7 Mar 2025 16:16:56 +0100
Subject: [PATCH 7/8] test: adds populate template test

---
 src/tests/insights/populateTemplate.test.ts | 50 +++++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 src/tests/insights/populateTemplate.test.ts

diff --git a/src/tests/insights/populateTemplate.test.ts b/src/tests/insights/populateTemplate.test.ts
new file mode 100644
index 0000000..fafa759
--- /dev/null
+++ b/src/tests/insights/populateTemplate.test.ts
@@ -0,0 +1,50 @@
+import { expect, test, describe, it } from 'bun:test';
+import { populateTemplate } from './../../utils/insights';
+import { type GraphQueryResultMetaFromBackend } from 'ts-common';
+
+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);
+  });
+});
-- 
GitLab


From c669d9d086e0784d04a1bca9e7390872b574d713 Mon Sep 17 00:00:00 2001
From: MarcosPierasNL <pieras.marcos@gmail.com>
Date: Fri, 14 Mar 2025 11:56:33 +0100
Subject: [PATCH 8/8] test: adds vitest and logger

---
 src/tests/insights/populateTemplate.test.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/tests/insights/populateTemplate.test.ts b/src/tests/insights/populateTemplate.test.ts
index fafa759..ce8e70b 100644
--- a/src/tests/insights/populateTemplate.test.ts
+++ b/src/tests/insights/populateTemplate.test.ts
@@ -1,6 +1,10 @@
-import { expect, test, describe, it } from 'bun:test';
+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: {
-- 
GitLab