Browse Source

Merge pull request #253 from Tumao727/init-codecov

Add service tests and init codecov
nameczz 3 years ago
parent
commit
c3a096b537

+ 14 - 0
.github/workflows/dev.yml

@@ -16,6 +16,20 @@ jobs:
         with:
           node-version: 12
 
+      - name: Run Express tests
+        run: |
+          cd express
+          yarn install
+          yarn test:cov
+
+      - name: Upload coverage to Codecov
+        uses: codecov/codecov-action@v2
+        with:
+          # public repo needn't pass token
+          # token: ${{ secrets.CODECOV_TOKEN }}
+          # only upload express test coverage
+          flags: express
+
       - name: Login to DockerHub
         uses: docker/login-action@v1
         with:

+ 1 - 0
README.md

@@ -1,4 +1,5 @@
 # Milvus insight
+
 [![typescript](https://badges.aleen42.com/src/typescript.svg)](https://badges.aleen42.com/src/typescript.svg)
 [![downloads](https://img.shields.io/docker/pulls/milvusdb/milvus-insight)](https://img.shields.io/docker/pulls/milvusdb/milvus-insight)
 

+ 30 - 0
codecov.yml

@@ -0,0 +1,30 @@
+coverage:
+  status:
+    project:
+      default:
+        flags:
+          - client
+          - express
+      client:
+        target: auto
+        flags:
+          - client
+      express:
+        target: auto
+        flags:
+          - express
+
+comment:
+  layout: "reach, diff, flags, files"
+  behavior: default
+  require_changes: true
+
+flags:
+  client:
+    paths:
+      - client/
+    carryforward: true
+  express:
+    paths:
+      - express/
+    carryforward: true

+ 7 - 0
express/.prettierrc

@@ -0,0 +1,7 @@
+{
+  "tabWidth": 2,
+  "semi": true,
+  "singleQuote": true,
+  "trailingComma": "es5",
+  "bracketSpacing": true
+}

+ 30 - 4
express/package.json

@@ -21,18 +21,40 @@
     "swagger-jsdoc": "^6.1.0",
     "swagger-ui-express": "^4.1.6"
   },
+  "jest": {
+    "testEnvironment": "node",
+    "testTimeout": 10000,
+    "coveragePathIgnorePatterns": [
+      "/node_modules/"
+    ],
+    "rootDir": "src",
+    "testRegex": ".*\\.test\\.ts$",
+    "collectCoverageFrom": [
+      "**/*.service.{js,ts}"
+    ],
+    "transform": {
+      "^.+\\.(t|j)s$": "ts-jest"
+    },
+    "coverageDirectory": "../coverage/"
+  },
   "devDependencies": {
-    "@types/swagger-jsdoc": "^6.0.1",
     "@types/chalk": "^2.2.0",
     "@types/cors": "^2.8.12",
     "@types/express": "^4.17.13",
     "@types/glob": "^7.2.0",
+    "@types/jest": "^27.0.2",
     "@types/morgan": "^1.9.3",
     "@types/node": "^16.11.6",
     "@types/node-cron": "^3.0.0",
+    "@types/supertest": "^2.0.11",
+    "@types/swagger-jsdoc": "^6.0.1",
     "@types/swagger-ui-express": "^4.1.3",
     "@types/ws": "^8.2.0",
+    "jest": "^27.3.1",
     "nodemon": "^2.0.14",
+    "prettier": "^2.4.1",
+    "supertest": "^6.1.6",
+    "ts-jest": "^27.0.7",
     "ts-node": "^10.4.0",
     "tslint": "^6.1.3",
     "typescript": "^4.4.4"
@@ -44,8 +66,12 @@
     "start": "nodemon ./src/app",
     "start:plugin": "yarn build && cross-env PLUGIN_DEV=1 node dist/milvus-insight/express/src/app.js",
     "start:prod": "node dist/app.js",
-    "test": "echo \"Error: no test specified\" && exit 1",
-    "clean": "rimraf dist"
+    "test": "cross-env NODE_ENV=test jest --passWithNoTests",
+    "test:watch": "jest --watch",
+    "test:cov": "cross-env NODE_ENV=test jest --passWithNoTests --coverage",
+    "test:report": "cross-env NODE_ENV=test jest --watchAll=false --coverage --coverageReporters='text-summary'",
+    "clean": "rimraf dist",
+    "format": "prettier --write '**/*.{ts,js}'"
   },
   "nodemonConfig": {
     "ignore": [
@@ -60,4 +86,4 @@
     ],
     "ext": "ts yml"
   }
-}
+}

+ 165 - 0
express/src/__tests__/__mocks__/consts.ts

@@ -0,0 +1,165 @@
+// mock data
+export const mockAddress = '127.0.0.1';
+export const mockCollectionNames = [{ name: 'c1' }, { name: 'c2' }];
+export const mockCollections = [
+  {
+    name: 'c1',
+    collectionID: 1,
+    schema: {
+      fields: [
+        {
+          name: 'vector_field',
+          data_type: 'data_type',
+          type_params: [
+            {
+              key: 'dim',
+              value: '4',
+            },
+          ],
+        },
+        {
+          is_primary_key: true,
+          autoID: true,
+          name: 'age',
+          data_type: 'data_type',
+          type_params: [] as any[],
+        },
+      ],
+      description: 'mock schema description 1',
+    },
+    created_utc_timestamp: '123456',
+  },
+  {
+    name: 'c2',
+    collectionID: 2,
+    schema: {
+      fields: [
+        {
+          name: 'vector_field',
+          data_type: 'data_type',
+          type_params: [
+            {
+              key: 'dim',
+              value: '4',
+            },
+          ],
+        },
+        {
+          name: 'age',
+          data_type: 'data_type',
+          type_params: [] as any[],
+        },
+      ],
+      description: 'mock schema description 2',
+    },
+    created_utc_timestamp: '1234567',
+  },
+];
+export const mockLoadedCollections = [
+  {
+    id: 1,
+    name: 'c1',
+    loadedPercentage: '100',
+  },
+];
+// index state is finished
+export const mockIndexState = [
+  { collection_name: 'c1', state: 3 },
+  { collection_name: 'c2', state: 2 },
+];
+
+export const mockPartition = {
+  partition_names: ['p1', 'p2'],
+  partitionIDs: [1, 2],
+  created_timestamps: ['12345', '12354'],
+  created_utc_timestamps: ['12345', '12354'],
+};
+
+// mock results
+export const mockGetAllCollectionsData = [
+  {
+    collection_name: 'c2',
+    schema: {
+      fields: [
+        {
+          name: 'vector_field',
+          data_type: 'data_type',
+          type_params: [
+            {
+              key: 'dim',
+              value: '4',
+            },
+          ],
+        },
+        {
+          name: 'age',
+          data_type: 'data_type',
+          type_params: [] as any[],
+        },
+      ],
+      description: 'mock schema description 2',
+    },
+    description: 'mock schema description 2',
+    autoID: undefined as boolean,
+    rowCount: 7,
+    id: 2,
+    loadedPercentage: '-1',
+    createdTime: 1234567,
+    index_status: 2,
+  },
+  {
+    collection_name: 'c1',
+    schema: {
+      fields: [
+        {
+          name: 'vector_field',
+          data_type: 'data_type',
+          type_params: [
+            {
+              key: 'dim',
+              value: '4',
+            },
+          ],
+        },
+        {
+          is_primary_key: true,
+          autoID: true,
+          name: 'age',
+          data_type: 'data_type',
+          type_params: [] as any[],
+        },
+      ],
+      description: 'mock schema description 1',
+    },
+    description: 'mock schema description 1',
+    autoID: true,
+    rowCount: 7,
+    id: 1,
+    loadedPercentage: '100',
+    createdTime: 123456,
+    index_status: 3,
+  },
+];
+
+export const mockLoadedCollectionsData = [
+  {
+    collection_name: 'c1',
+    id: 1,
+    rowCount: 7,
+  },
+];
+
+export const mockGetPartitionsInfoData = [
+  {
+    name: 'p1',
+    id: 1,
+    createdTime: '12345',
+    rowCount: 7,
+  },
+  {
+    name: 'p2',
+    id: 2,
+    createdTime: '12354',
+    rowCount: 7,
+  },
+];

+ 353 - 0
express/src/__tests__/__mocks__/milvus/milvusClient.ts

@@ -0,0 +1,353 @@
+import {
+  AlterAliasReq,
+  CreateAliasReq,
+  CreateCollectionReq,
+  CreateIndexReq,
+  CreatePartitionReq,
+  DescribeCollectionReq,
+  DescribeIndexReq,
+  DropAliasReq,
+  DropCollectionReq,
+  DropIndexReq,
+  DropPartitionReq,
+  FlushReq,
+  GetCollectionStatisticsReq,
+  GetIndexBuildProgressReq,
+  GetIndexStateReq,
+  GetPartitionStatisticsReq,
+  InsertReq,
+  LoadCollectionReq,
+  LoadPartitionsReq,
+  ReleaseLoadCollectionReq,
+  ReleasePartitionsReq,
+  SearchReq,
+  ShowCollectionsReq,
+  ShowPartitionsReq,
+} from '@zilliz/milvus2-sdk-node/dist/milvus/types';
+import { DeleteEntitiesReq } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Data';
+import { QueryDto } from '../../../collections/dto';
+import {
+  CodeEnum,
+  ERR_NO_ADDRESS,
+  ERR_NO_ALIAS,
+  ERR_NO_COLLECTION,
+  ERR_NO_INDEX,
+  ERR_NO_PARAM,
+} from '../../utils/constants';
+import { mockStatusInfo } from '../../utils/mock.util';
+import {
+  mockCollectionNames,
+  mockCollections,
+  mockIndexState,
+  mockLoadedCollections,
+  mockPartition,
+} from '../consts';
+
+const mockMilvusClient = jest.fn().mockImplementation((address: string) => {
+  return {
+    collectionManager: {
+      hasCollection: (param: { collection_name: string }) => {
+        const { collection_name } = param;
+        if (address === '') {
+          throw new Error(ERR_NO_ADDRESS);
+        }
+        return collection_name;
+      },
+      showCollections: (param?: ShowCollectionsReq) => {
+        if (!param) {
+          return {
+            status: mockStatusInfo(CodeEnum.success),
+            data: mockCollectionNames,
+          };
+        }
+        const { collection_names, type } = param;
+        // loaded type
+        if (type === 1) {
+          return {
+            status: mockStatusInfo(CodeEnum.success),
+            data: mockLoadedCollections,
+          };
+        }
+        return collection_names && collection_names.length > 0
+          ? {
+              status: mockStatusInfo(CodeEnum.success),
+              data: collection_names,
+            }
+          : { status: mockStatusInfo(CodeEnum.error, ERR_NO_PARAM) };
+      },
+      createCollection: (param: CreateCollectionReq) => {
+        const { collection_name, fields } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: fields };
+      },
+      describeCollection: (param: DescribeCollectionReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return {
+            status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION),
+          };
+        }
+        const res =
+          mockCollections.find((c) => c.name === collection_name) || {};
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          ...res,
+        };
+      },
+      dropCollection: (param: DropCollectionReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: collection_name };
+      },
+      loadCollection: (param: LoadCollectionReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: collection_name };
+      },
+      releaseCollection: (param: ReleaseLoadCollectionReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+
+        return { ...mockStatusInfo(CodeEnum.success), data: collection_name };
+      },
+      getCollectionStatistics: (param: GetCollectionStatisticsReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+        const data = {
+          name: collection_name,
+          stats: [{ key: 'row_count', value: 7 }],
+        };
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          ...data,
+        };
+      },
+      createAlias: (param: CreateAliasReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+
+        return {
+          ...mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      alterAlias: (param: AlterAliasReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+
+        return {
+          ...mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      dropAlias: (param: DropAliasReq) => {
+        const { alias } = param;
+        if (!alias) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_ALIAS);
+        }
+
+        return {
+          ...mockStatusInfo(CodeEnum.success),
+          data: alias,
+        };
+      },
+    },
+    partitionManager: {
+      createPartition: (param: CreatePartitionReq) => {
+        const { collection_name, partition_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: partition_name };
+      },
+      dropPartition: (param: DropPartitionReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: param };
+      },
+      loadPartitions: (param: LoadPartitionsReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: param };
+      },
+      releasePartitions: (param: ReleasePartitionsReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+        return { ...mockStatusInfo(CodeEnum.success), data: param };
+      },
+      showPartitions: (param: ShowPartitionsReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          ...mockPartition,
+        };
+      },
+      getPartitionStatistics: (param: GetPartitionStatisticsReq) => {
+        const { collection_name, partition_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+
+        const data = {
+          name: partition_name,
+          stats: [{ key: 'row_count', value: 7 }],
+        };
+
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          ...data,
+        };
+      },
+    },
+    indexManager: {
+      getIndexState: (param: GetIndexStateReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+        const data =
+          mockIndexState.find((i) => i.collection_name === collection_name) ||
+          {};
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          ...data,
+        };
+      },
+      createIndex: (param: CreateIndexReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+
+        return {
+          ...mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      describeIndex: (param: DescribeIndexReq) => {
+        const { collection_name, field_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+        if (!field_name) {
+          return {
+            status: mockStatusInfo(CodeEnum.indexNoExist, ERR_NO_INDEX),
+          };
+        }
+
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      dropIndex: (param: DropIndexReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION);
+        }
+
+        return {
+          ...mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      getIndexBuildProgress: (param: GetIndexBuildProgressReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+    },
+    dataManager: {
+      flush: (data: FlushReq) => ({
+        data,
+      }),
+      getMetric: (data: { request: { metric_type: string } }) => {
+        const {
+          request: { metric_type: type },
+        } = data;
+
+        return {
+          type,
+        };
+      },
+      insert: (param: InsertReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      search: (param: SearchReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      query: (
+        param: {
+          collection_name: string;
+        } & QueryDto
+      ) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+      deleteEntities: (param: DeleteEntitiesReq) => {
+        const { collection_name } = param;
+        if (!collection_name) {
+          return { status: mockStatusInfo(CodeEnum.error, ERR_NO_COLLECTION) };
+        }
+        return {
+          status: mockStatusInfo(CodeEnum.success),
+          data: param,
+        };
+      },
+    },
+  };
+});
+
+export default mockMilvusClient;

+ 9 - 0
express/src/__tests__/__mocks__/milvus/milvusService.ts

@@ -0,0 +1,9 @@
+const mockMivusService = jest.fn().mockImplementation(() => {
+  return {
+    connectMilvus: (address: string) => new Promise(() => !!address),
+    checkConnect: (address: string) => new Promise(() => address),
+    flush: (collectionName: string) => new Promise(() => collectionName),
+  };
+});
+
+export default mockMivusService;

+ 302 - 0
express/src/__tests__/collections/collections.service.test.ts

@@ -0,0 +1,302 @@
+import mockMilvusClient from '../__mocks__/milvus/milvusClient';
+import { CollectionsService } from '../../collections/collections.service';
+import { MilvusService } from '../../milvus/milvus.service';
+import {
+  ERR_NO_ALIAS,
+  ERR_NO_COLLECTION,
+  ERR_NO_PARAM,
+} from '../utils/constants';
+import {
+  mockAddress,
+  mockCollectionNames,
+  mockCollections,
+  mockGetAllCollectionsData,
+  mockLoadedCollections,
+  mockLoadedCollectionsData,
+} from '../__mocks__/consts';
+
+// mock Milvus client
+jest.mock('@zilliz/milvus2-sdk-node', () => {
+  return {
+    MilvusClient: mockMilvusClient,
+  };
+});
+
+describe('Test collections service', () => {
+  let milvusService: any;
+  let service: any;
+
+  beforeAll(async () => {
+    // setup Milvus service and connect to mock Milvus client
+    milvusService = new MilvusService();
+    await milvusService.connectMilvus(mockAddress);
+    service = new CollectionsService(milvusService);
+  });
+
+  afterAll(() => {
+    milvusService = null;
+    service = null;
+  });
+
+  test('test managers after connected to Milvus', () => {
+    expect(service.collectionManager).toBeDefined();
+    expect(service.dataManager).toBeDefined();
+    expect(service.indexManager).toBeDefined();
+  });
+
+  test('test getCollections method', async () => {
+    const res = await service.getCollections({
+      collection_names: ['c1', 'c2'],
+    });
+    expect(res.data.length).toBe(2);
+
+    const defaultRes = await service.getCollections();
+    expect(defaultRes.data).toEqual(mockCollectionNames);
+
+    const loadedRes = await service.getCollections({ type: 1 });
+    expect(loadedRes.data).toEqual(mockLoadedCollections);
+
+    try {
+      await service.getCollections({ collection_names: [] });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_PARAM);
+    }
+  });
+
+  test('test createCollection method', async () => {
+    const res = await service.createCollection({
+      collection_name: 'c1',
+      fields: [],
+    });
+    expect(res.data.length).toBe(0);
+
+    try {
+      await service.createCollection({ collection_name: '', fields: [] });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test describeCollection method', async () => {
+    const res = await service.describeCollection({
+      collection_name: 'c1',
+    });
+    const { status, ...result } = res;
+    const [mockRes] = mockCollections;
+    expect(result).toEqual(mockRes);
+
+    try {
+      await service.describeCollection({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test dropCollection method', async () => {
+    const res = await service.dropCollection({ collection_name: 'c1' });
+    expect(res.data).toBe('c1');
+
+    try {
+      await service.dropCollection({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test loadCollection method', async () => {
+    const res = await service.loadCollection({ collection_name: 'c1' });
+    expect(res.data).toBe('c1');
+
+    try {
+      await service.loadCollection({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test releaseCollection method', async () => {
+    const res = await service.releaseCollection({ collection_name: 'c1' });
+    expect(res.data).toBe('c1');
+
+    try {
+      await service.releaseCollection({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getCollectionStatistics method', async () => {
+    const res = await service.getCollectionStatistics({
+      collection_name: 'c1',
+    });
+    const { status, ...data } = res;
+    expect(data.name).toBe('c1');
+    expect(data.stats.length).toBe(1);
+
+    try {
+      await service.getCollectionStatistics({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test insert method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      fields_data: {
+        vector_field: [1, 2, 3, 4],
+        age: 7,
+      },
+    };
+    const res = await service.insert(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.insert({
+        collection_name: '',
+        fields_data: {
+          vector_field: [1, 2, 3, 4],
+        },
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test vectorSearch method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      search_params: {
+        anns_field: 'float_vector',
+        topk: '10',
+        metric_type: 'L2',
+        params: JSON.stringify({ nprobe: 1024 }),
+      },
+      vectors: [[1, 2, 3, 4]],
+      vector_type: 101,
+    };
+
+    const res = await service.vectorSearch(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.vectorSearch({ ...mockParam, collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test createAlias method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      alias: 'alias',
+    };
+
+    const res = await service.createAlias(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.createAlias({ collection_name: '', alias: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test alterAlias method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      alias: 'alias',
+    };
+
+    const res = await service.alterAlias(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.alterAlias({ collection_name: '', alias: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test dropAlias method', async () => {
+    const res = await service.dropAlias({ alias: 'alias' });
+    expect(res.data).toBe('alias');
+
+    try {
+      await service.dropAlias({ alias: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_ALIAS);
+    }
+  });
+
+  test('test query method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      expr: 'age > 7',
+    };
+    const res = await service.query(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.query({ collection_name: '', expr: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getIndexStatus method', async () => {
+    const res = await service.getIndexStatus({ collection_name: 'c1' });
+    const { status, ...data } = res;
+    expect(data).toEqual({ collection_name: 'c1', state: 3 });
+  });
+
+  test('test getAllCollections method', async () => {
+    const res = await service.getAllCollections();
+    expect(res).toEqual(mockGetAllCollectionsData);
+  });
+
+  test('test getLoadedCollections method', async () => {
+    const res = await service.getLoadedColletions();
+    expect(res).toEqual(mockLoadedCollectionsData);
+  });
+
+  test('test getStatistics method', async () => {
+    const res = await service.getStatistics();
+    expect(res).toEqual({
+      // 2 collections
+      collectionCount: 2,
+      // each collection 7 row counts
+      totalData: 14,
+    });
+  });
+
+  test('test getCollectionIndexStatus method', async () => {
+    const res = await service.getCollectionsIndexStatus();
+    expect(res).toEqual([
+      {
+        collection_name: 'c1',
+        index_status: 3,
+      },
+      {
+        collection_name: 'c2',
+        index_status: 2,
+      },
+    ]);
+  });
+
+  test('test deleteEntities method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      expr: 'age > 7',
+    };
+
+    const res = await service.deleteEntities(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.deleteEntities({ collection_name: '', expr: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+});

+ 162 - 0
express/src/__tests__/crons/crons.service.test.ts

@@ -0,0 +1,162 @@
+import mockMilvusClient from '../__mocks__/milvus/milvusClient';
+import { schedule } from 'node-cron';
+import { CollectionsService } from '../../collections/collections.service';
+import { CronsService, SchedulerRegistry } from '../../crons/crons.service';
+import { MilvusService } from '../../milvus/milvus.service';
+import { WS_EVENTS, WS_EVENTS_TYPE } from '../../utils/Const';
+import { mockAddress } from '../__mocks__/consts';
+
+// mock Milvus client
+jest.mock('@zilliz/milvus2-sdk-node', () => {
+  return {
+    MilvusClient: mockMilvusClient,
+  };
+});
+
+// mock node-cron
+jest.mock('node-cron', () => {
+  return {
+    schedule: jest.fn(),
+  };
+});
+
+// mock variable
+const mockCronFrequency = '30 00 * * *';
+const mockCronEverySec = '* * * * * *';
+const mockCb = jest.fn();
+const mockErrCb = jest.fn(() => {
+  throw new Error('error');
+});
+const mockName = 'j1';
+const mockSecName = 'everySec';
+
+describe('test crons service', () => {
+  let milvusService: any;
+  let collectionService: any;
+  let cronsService: any;
+  let schedulerRegistry: any;
+
+  const handleStartTask = jest.fn();
+  const handleEndTask = jest.fn();
+
+  const setup = async () => {
+    milvusService = new MilvusService();
+    await milvusService.connectMilvus(mockAddress);
+    collectionService = new CollectionsService(milvusService);
+
+    schedulerRegistry = new SchedulerRegistry([]);
+    cronsService = new CronsService(collectionService, schedulerRegistry);
+  };
+
+  beforeAll(async () => {
+    // setup Milvus service and connect to mock Milvus client
+    await setup();
+  });
+
+  beforeEach(() => {
+    // mock schedule
+    (schedule as jest.Mock).mockImplementationOnce((frequency, callback) => {
+      callback();
+      return {
+        start: handleStartTask,
+        stop: handleEndTask,
+      };
+    });
+  });
+
+  afterAll(() => {
+    milvusService = null;
+    collectionService = null;
+    schedulerRegistry = null;
+    cronsService = null;
+  });
+
+  test('test SchedulerRegistry related methods', async () => {
+    const logSpy = jest.spyOn(console, 'log');
+    schedulerRegistry.setCronJob(mockName, mockCronFrequency, () => mockCb());
+    expect(logSpy).toBeCalledWith(`${mockName}: running a task every seconds`);
+    expect(mockCb).toBeCalledTimes(1);
+    expect(schedule).toBeCalledWith(mockCronFrequency, expect.any(Function));
+
+    const job = schedulerRegistry.getCronJob(mockName);
+    expect(job).toEqual({
+      start: handleStartTask,
+      stop: handleEndTask,
+    });
+
+    schedulerRegistry.setCronJob(mockName, mockCronFrequency, () => mockCb());
+    expect(handleEndTask).toBeCalled();
+
+    schedulerRegistry.setCronJobEverySecond(mockSecName, () => mockCb());
+    expect(schedule).toBeCalledWith(mockCronEverySec, expect.any(Function));
+
+    schedulerRegistry.setCronJob(mockName, mockCronFrequency, () => mockCb());
+    expect(handleEndTask).toBeCalled();
+
+    schedulerRegistry.setCronJob(mockName, mockCronFrequency, () =>
+      mockErrCb()
+    );
+    expect(() => {
+      mockErrCb();
+    }).toThrow();
+  });
+
+  test('test CronService related methods', async () => {
+    try {
+      await cronsService.toggleCronJobByName({
+        name: WS_EVENTS.COLLECTION,
+        type: WS_EVENTS_TYPE.STOP,
+      });
+    } catch (err) {
+      expect(err.message).toBe('No existed job entity');
+    }
+
+    await cronsService.toggleCronJobByName({
+      name: WS_EVENTS.COLLECTION,
+      type: WS_EVENTS_TYPE.START,
+    });
+    expect(schedule).toBeCalledWith(mockCronEverySec, expect.any(Function));
+
+    schedulerRegistry.setCronJob(WS_EVENTS.COLLECTION, mockCronFrequency, () =>
+      mockCb()
+    );
+    await cronsService.toggleCronJobByName({
+      name: WS_EVENTS.COLLECTION,
+      type: WS_EVENTS_TYPE.START,
+    });
+
+    expect(handleStartTask).toBeCalled();
+    await cronsService.toggleCronJobByName({
+      name: WS_EVENTS.COLLECTION,
+      type: WS_EVENTS_TYPE.STOP,
+    });
+    expect(handleStartTask).toBeCalled();
+
+    try {
+      await cronsService.toggleCronJobByName({
+        name: mockName,
+        type: WS_EVENTS_TYPE.STOP,
+      });
+    } catch (err) {
+      expect(err.message).toBe('Unsupported event type');
+    }
+  });
+
+  test('test getCollections error', async () => {
+    // reset setup to trigger error
+    const newCollectionService = new CollectionsService(milvusService);
+    const newSchedulerRegistry = new SchedulerRegistry([]);
+    newCollectionService.getAllCollections = () => {
+      throw new Error('error');
+    };
+
+    const newCronsService = new CronsService(
+      newCollectionService,
+      newSchedulerRegistry
+    );
+
+    await newCronsService.getCollections(WS_EVENTS.COLLECTION);
+    expect(schedule).toBeCalledWith(mockCronEverySec, expect.any(Function));
+    expect(handleEndTask).toBeCalled();
+  });
+});

+ 54 - 0
express/src/__tests__/milvus/milvus.controller.test.ts

@@ -0,0 +1,54 @@
+import express from 'express';
+import http from 'http';
+import supertest from 'supertest';
+import { TransformResMiddlerware } from '../../middlewares';
+import { router as connectRouter } from '../../milvus/index';
+import { mockAddress } from '../__mocks__/consts';
+
+import MilvusService from '../__mocks__/milvus/milvusService';
+
+// mock Milvus client service
+jest.mock('../__mocks__/milvus/milvusService');
+
+describe('Test Milvus Module', () => {
+  let app: any;
+  let server: any;
+  let request: any;
+
+  // setup app and server
+  beforeAll((done) => {
+    app = express();
+    const router = express.Router();
+    router.use('/milvus', connectRouter);
+    app.use(TransformResMiddlerware);
+    app.use('/api/v1', router);
+    server = http.createServer(app);
+    server.listen(done);
+    request = supertest(server);
+  });
+
+  beforeEach(() => {
+    // Clear all instances and calls to constructor and all methods:
+    MilvusService.mockClear();
+  });
+
+  // close server
+  afterAll((done) => {
+    server.close(done);
+  });
+
+  test('check whether connected to Milvus with address', () => {
+    // with address param
+    request.get('/check').query({ address: mockAddress }).expect(200);
+    // without address param
+    request.get('/check').expect(404);
+  });
+
+  test('check request to connect to Milvus', () => {
+    request
+      .post('/connect')
+      .send({ address: mockAddress })
+      .set('Accept', 'application/json')
+      .expect(200);
+  });
+});

+ 82 - 0
express/src/__tests__/milvus/milvus.service.test.ts

@@ -0,0 +1,82 @@
+import mockMilvusClient from '../__mocks__/milvus/milvusClient';
+import { MilvusService } from '../../milvus/milvus.service';
+import { mockAddress } from '../__mocks__/consts';
+
+// mock Milvus client
+jest.mock('@zilliz/milvus2-sdk-node', () => {
+  return {
+    MilvusClient: mockMilvusClient,
+  };
+});
+
+describe('Test Milvus service', () => {
+  let service: any;
+
+  // init service
+  beforeEach(() => {
+    service = new MilvusService();
+  });
+
+  afterEach(() => {
+    service = null;
+  });
+
+  test('test connectMilvus method', async () => {
+    expect(service.milvusClientGetter).toBeUndefined();
+    expect(service.milvusAddressGetter).toBe('');
+
+    const res = await service.connectMilvus(mockAddress);
+    expect(res.address).toBe(mockAddress);
+    expect(service.milvusAddressGetter).toBe(mockAddress);
+    expect(service.milvusClientGetter).toBeDefined();
+  });
+
+  test('test connectMilvus method error', async () => {
+    try {
+      await service.connectMilvus('');
+    } catch (err) {
+      expect(err.message).toBe(
+        'Connect milvus failed, check your milvus address.'
+      );
+    }
+  });
+
+  test('test checkMilvus when not connect to Milvus', () => {
+    try {
+      service.checkMilvus();
+    } catch (err) {
+      expect(err.message).toBe('Please connect milvus first');
+    }
+  });
+
+  test('test checkConnect method', async () => {
+    // mock connect first
+    await service.connectMilvus(mockAddress);
+    // different address
+    const errorRes = await service.checkConnect('123');
+    expect(errorRes.connected).toBeFalsy();
+    const res = await service.checkConnect(mockAddress);
+    expect(res.connected).toBeTruthy();
+  });
+
+  test('test managers after connected', async () => {
+    await service.connectMilvus(mockAddress);
+    expect(service.collectionManager).toBeDefined();
+    expect(service.partitionManager).toBeDefined();
+    expect(service.indexManager).toBeDefined();
+    expect(service.dataManager).toBeDefined();
+  });
+
+  test('test flush method', async () => {
+    await service.connectMilvus(mockAddress);
+    const res = await service.flush({ collection_names: ['c1', 'c2'] });
+    const data = res.data.collection_names;
+    expect(data.length).toBe(2);
+  });
+
+  test('test getMetrics method', async () => {
+    await service.connectMilvus(mockAddress);
+    const res = await service.getMetrics();
+    expect(res.type).toBe('system_info');
+  });
+});

+ 144 - 0
express/src/__tests__/partitions/partitions.service.test.ts

@@ -0,0 +1,144 @@
+import mockMilvusClient from '../__mocks__/milvus/milvusClient';
+import { MilvusService } from '../../milvus/milvus.service';
+import { ERR_NO_COLLECTION } from '../utils/constants';
+import { PartitionsService } from '../../partitions/partitions.service';
+import {
+  mockAddress,
+  mockGetPartitionsInfoData,
+  mockPartition,
+} from '../__mocks__/consts';
+
+// mock Milvus client
+jest.mock('@zilliz/milvus2-sdk-node', () => {
+  return {
+    MilvusClient: mockMilvusClient,
+  };
+});
+
+describe('Test partitions service', () => {
+  let milvusService: any;
+  let service: any;
+
+  beforeAll(async () => {
+    // setup Milvus service and connect to mock Milvus client
+    milvusService = new MilvusService();
+    await milvusService.connectMilvus(mockAddress);
+    service = new PartitionsService(milvusService);
+  });
+
+  afterAll(() => {
+    milvusService = null;
+    service = null;
+  });
+
+  test('test manager after connected to Milvus', () => {
+    expect(service.partitionManager).toBeDefined();
+  });
+
+  test('test createPartition method', async () => {
+    const res = await service.createPartition({
+      collection_name: 'c1',
+      partition_name: 'p1',
+    });
+    expect(res.data).toBe('p1');
+
+    try {
+      await service.createPartition({
+        collection_name: '',
+        partition_name: 'p1',
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test deletePartition method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      partition_name: 'p1',
+    };
+    const res = await service.deletePartition(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.deletePartition({
+        collection_name: '',
+        partition_name: '',
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test loadPartitions method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      partition_names: ['p1', 'p2'],
+    };
+    const res = await service.loadPartitions(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.loadPartitions({
+        collection_name: '',
+        partition_names: [],
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test releasePartitions method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      partition_names: ['p1', 'p2'],
+    };
+    const res = await service.releasePartitions(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.releasePartitions({
+        collection_name: '',
+        partition_names: [],
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getPartitions method', async () => {
+    const res = await service.getPartitions({ collection_name: 'c1' });
+    const { status, ...data } = res;
+    expect(data).toEqual(mockPartition);
+
+    try {
+      await service.getPartitions({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getPartitionStatistics method', async () => {
+    const res = await service.getPartitionStatistics({
+      collection_name: 'c1',
+      partition_name: 'p1',
+    });
+    const { status, ...data } = res;
+    expect(data.name).toBe('p1');
+    expect(data.stats.length).toBe(1);
+
+    try {
+      await service.getPartitionStatistics({
+        collection_name: '',
+        partition_name: '',
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getPartitionsInfo method', async () => {
+    const res = await service.getPartitionsInfo({ collection_name: 'c1' });
+    expect(res).toEqual(mockGetPartitionsInfoData);
+  });
+});

+ 114 - 0
express/src/__tests__/schema/schema.service.test.ts

@@ -0,0 +1,114 @@
+import mockMilvusClient from '../__mocks__/milvus/milvusClient';
+import { MilvusService } from '../../milvus/milvus.service';
+import { CodeEnum, ERR_NO_COLLECTION } from '../utils/constants';
+import { SchemaService } from '../../schema/schema.service';
+import { mockAddress } from '../__mocks__/consts';
+
+// mock Milvus client
+jest.mock('@zilliz/milvus2-sdk-node', () => {
+  return {
+    MilvusClient: mockMilvusClient,
+  };
+});
+
+describe('Test schema service', () => {
+  let milvusService: any;
+  let service: any;
+
+  beforeAll(async () => {
+    // setup Milvus service and connect to mock Milvus client
+    milvusService = new MilvusService();
+    await milvusService.connectMilvus(mockAddress);
+    service = new SchemaService(milvusService);
+  });
+
+  afterAll(() => {
+    milvusService = null;
+    service = null;
+  });
+
+  test('test manager after connected to Milvus', () => {
+    expect(service.indexManager).toBeDefined();
+  });
+
+  test('test createIndex method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      field_name: 'vector_field',
+      extra_params: {
+        index_type: 'BIN_FLAT',
+        metric_type: 'HAMMING',
+        params: JSON.stringify({ nlist: 1024 }),
+      },
+    };
+    const res = await service.createIndex(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.createIndex({ ...mockParam, collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test describeIndex method', async () => {
+    const res = await service.describeIndex({
+      collection_name: 'c1',
+      field_name: 'f1',
+    });
+    expect(res.data).toEqual({ collection_name: 'c1', field_name: 'f1' });
+
+    const noExistRes = await service.describeIndex({ collection_name: 'c1' });
+    expect(noExistRes.status.error_code).toBe(CodeEnum.indexNoExist);
+
+    try {
+      await service.describeIndex({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test dropIndex method', async () => {
+    const res = await service.dropIndex({
+      collection_name: 'c1',
+    });
+    expect(res.data).toEqual({ collection_name: 'c1' });
+
+    try {
+      await service.dropIndex({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getIndexState method', async () => {
+    const res = await service.getIndexState({ collection_name: 'c1' });
+    const { status, ...data } = res;
+    expect(data).toEqual({ collection_name: 'c1', state: 3 });
+
+    try {
+      await service.getIndexState({ collection_name: '' });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+
+  test('test getIndexBuildProgress method', async () => {
+    const mockParam = {
+      collection_name: 'c1',
+      field_name: 'f1',
+      index_name: 'i1',
+    };
+    const res = await service.getIndexBuildProgress(mockParam);
+    expect(res.data).toEqual(mockParam);
+
+    try {
+      await service.getIndexBuildProgress({
+        ...mockParam,
+        collection_name: '',
+      });
+    } catch (err) {
+      expect(err).toBe(ERR_NO_COLLECTION);
+    }
+  });
+});

+ 17 - 0
express/src/__tests__/utils/constants.ts

@@ -0,0 +1,17 @@
+export enum CodeEnum {
+  success = 'Success',
+  error = 'Error',
+  indexNoExist = 'IndexNotExist',
+}
+
+export type CodeStatus = {
+  error_code: CodeEnum;
+  reason?: string;
+};
+
+// error msgs
+export const ERR_NO_COLLECTION = 'collection name is invalid';
+export const ERR_NO_ADDRESS = 'no address';
+export const ERR_NO_PARAM = 'no valid param';
+export const ERR_NO_ALIAS = 'no valid alias';
+export const ERR_NO_INDEX = 'index not exist';

+ 14 - 0
express/src/__tests__/utils/mock.util.ts

@@ -0,0 +1,14 @@
+import { CodeEnum, CodeStatus } from './constants';
+
+export const mockStatusInfo = (type: CodeEnum, msg?: string): CodeStatus => {
+  if (type === CodeEnum.success) {
+    return {
+      error_code: type,
+      reason: 'success',
+    };
+  }
+  return {
+    error_code: type,
+    reason: msg,
+  };
+};

+ 1 - 1
express/src/app.ts

@@ -25,7 +25,7 @@ const PLUGIN_DEV = process.env?.PLUGIN_DEV;
 const SRC_PLUGIN_DIR = "src/plugins";
 const DEV_PLUGIN_DIR = "../../src/*/server";
 
-const app = express();
+export const app = express();
 const PORT = 3000;
 // initialize a simple http server
 const server = http.createServer(app);

+ 10 - 10
express/src/collections/collections.service.ts

@@ -1,4 +1,4 @@
-import { MilvusService } from "../milvus/milvus.service";
+import { MilvusService } from '../milvus/milvus.service';
 import {
   CreateCollectionReq,
   DescribeCollectionReq,
@@ -9,19 +9,19 @@ import {
   LoadCollectionReq,
   ReleaseLoadCollectionReq,
   SearchReq,
-} from "@zilliz/milvus2-sdk-node/dist/milvus/types";
-import { throwErrorFromSDK } from "../utils/Error";
-import { findKeyValue } from "../utils/Helper";
-import { ROW_COUNT } from "../utils/Const";
+} from '@zilliz/milvus2-sdk-node/dist/milvus/types';
+import { throwErrorFromSDK } from '../utils/Error';
+import { findKeyValue } from '../utils/Helper';
+import { ROW_COUNT } from '../utils/Const';
 import {
   AlterAliasReq,
   CreateAliasReq,
   DropAliasReq,
   ShowCollectionsReq,
   ShowCollectionsType,
-} from "@zilliz/milvus2-sdk-node/dist/milvus/types/Collection";
-import { QueryDto } from "./dto";
-import { DeleteEntitiesReq } from "@zilliz/milvus2-sdk-node/dist/milvus/types/Data";
+} from '@zilliz/milvus2-sdk-node/dist/milvus/types/Collection';
+import { QueryDto } from './dto';
+import { DeleteEntitiesReq } from '@zilliz/milvus2-sdk-node/dist/milvus/types/Data';
 
 export class CollectionsService {
   constructor(private milvusService: MilvusService) {}
@@ -88,6 +88,7 @@ export class CollectionsService {
 
   async deleteEntities(data: DeleteEntitiesReq) {
     const res = await this.dataManager.deleteEntities(data);
+    throwErrorFromSDK(res.status);
     return res;
   }
 
@@ -150,7 +151,6 @@ export class CollectionsService {
     if (res.data.length > 0) {
       for (const item of res.data) {
         const { name } = item;
-
         const collectionInfo = await this.describeCollection({
           collection_name: name,
         });
@@ -172,7 +172,7 @@ export class CollectionsService {
         );
 
         const loadedPercentage = !loadCollection
-          ? "-1"
+          ? '-1'
           : loadCollection.loadedPercentage;
 
         data.push({

+ 27 - 17
express/src/crons/crons.service.ts

@@ -1,7 +1,7 @@
-import { CollectionsService } from "../collections/collections.service";
-import { WS_EVENTS, WS_EVENTS_TYPE } from "../utils/Const";
-import { schedule, ScheduledTask } from "node-cron";
-import { pubSub } from "../events";
+import { CollectionsService } from '../collections/collections.service';
+import { WS_EVENTS, WS_EVENTS_TYPE } from '../utils/Const';
+import { schedule, ScheduledTask } from 'node-cron';
+import { pubSub } from '../events';
 
 export class CronsService {
   constructor(
@@ -11,13 +11,22 @@ export class CronsService {
 
   async toggleCronJobByName(data: { name: string; type: WS_EVENTS_TYPE }) {
     const { name, type } = data;
-    const cronJobEntity = this.schedulerRegistry.getCronJob(name);
-    if (!cronJobEntity && Number(type) === WS_EVENTS_TYPE.START) {
-      return this.getCollections(WS_EVENTS.COLLECTION);
+
+    switch (name) {
+      case WS_EVENTS.COLLECTION:
+        const cronJobEntity = this.schedulerRegistry.getCronJob(name);
+        if (!cronJobEntity && Number(type) === WS_EVENTS_TYPE.START) {
+          return this.getCollections(WS_EVENTS.COLLECTION);
+        }
+        if (!cronJobEntity) {
+          return;
+        }
+        return Number(type) === WS_EVENTS_TYPE.STOP
+          ? cronJobEntity.stop()
+          : cronJobEntity.start();
+      default:
+        throw new Error('Unsupported event type');
     }
-    return Number(type) === WS_EVENTS_TYPE.STOP
-      ? cronJobEntity.stop()
-      : cronJobEntity.start();
   }
 
   async getCollections(name: string) {
@@ -26,17 +35,18 @@ export class CronsService {
         const res = await this.collectionService.getAllCollections();
         // TODO
         // this.eventService.server.emit("COLLECTION", res);
-        pubSub.emit("ws_pubsub", {
-          event: WS_EVENTS.COLLECTION + "",
+        pubSub.emit('ws_pubsub', {
+          event: WS_EVENTS.COLLECTION + '',
           data: res,
         });
         return res;
       } catch (error) {
         // When user not connect milvus, stop cron
-        this.toggleCronJobByName({
-          name: WS_EVENTS.COLLECTION,
-          type: WS_EVENTS_TYPE.STOP,
-        });
+        const cronJobEntity = this.schedulerRegistry.getCronJob(name);
+        if (cronJobEntity) {
+          cronJobEntity.stop();
+        }
+
         throw new Error(error);
       }
     };
@@ -54,7 +64,7 @@ export class SchedulerRegistry {
 
   setCronJobEverySecond(name: string, func: () => {}) {
     // The cron job will run every second
-    this.setCronJob(name, "* * * * * *", func);
+    this.setCronJob(name, '* * * * * *', func);
   }
 
   // ┌────────────── second (optional)

+ 8 - 8
express/src/milvus/milvus.service.ts

@@ -1,15 +1,15 @@
-import { MilvusClient } from "@zilliz/milvus2-sdk-node";
+import { MilvusClient } from '@zilliz/milvus2-sdk-node';
 import {
   FlushReq,
   GetMetricsResponse,
-} from "@zilliz/milvus2-sdk-node/dist/milvus/types";
+} from '@zilliz/milvus2-sdk-node/dist/milvus/types';
 
 export class MilvusService {
   private milvusAddress: string;
   private milvusClient: MilvusClient;
 
   constructor() {
-    this.milvusAddress = "";
+    this.milvusAddress = '';
   }
 
   get milvusAddressGetter() {
@@ -42,22 +42,22 @@ export class MilvusService {
 
   private checkMilvus() {
     if (!this.milvusClient) {
-      throw new Error("Please connect milvus first");
+      throw new Error('Please connect milvus first');
     }
   }
 
   async connectMilvus(address: string) {
     // grpc only need address without http
-    const milvusAddress = address.replace(/(http|https):\/\//, "");
+    const milvusAddress = address.replace(/(http|https):\/\//, '');
     try {
       this.milvusClient = new MilvusClient(milvusAddress);
       await this.milvusClient.collectionManager.hasCollection({
-        collection_name: "not_exist",
+        collection_name: 'not_exist',
       });
       this.milvusAddress = address;
       return { address: this.milvusAddress };
     } catch (error) {
-      throw new Error("Connect milvus failed, check your milvus address.");
+      throw new Error('Connect milvus failed, check your milvus address.');
     }
   }
 
@@ -78,7 +78,7 @@ export class MilvusService {
 
   async getMetrics(): Promise<GetMetricsResponse> {
     const res = await this.milvusClient.dataManager.getMetric({
-      request: { metric_type: "system_info" },
+      request: { metric_type: 'system_info' },
     });
     return res;
   }

+ 15 - 15
express/src/partitions/partitions.controller.ts

@@ -1,13 +1,13 @@
-import { NextFunction, Request, Response, Router } from "express";
-import { dtoValidationMiddleware } from "../middlewares/validation";
-import { PartitionsService } from "./partitions.service";
-import { milvusService } from "../milvus";
+import { NextFunction, Request, Response, Router } from 'express';
+import { dtoValidationMiddleware } from '../middlewares/validation';
+import { PartitionsService } from './partitions.service';
+import { milvusService } from '../milvus';
 
 import {
   GetPartitionsInfoDto,
   ManagePartitionDto,
   LoadPartitionsDto,
-} from "./dto";
+} from './dto';
 
 export class PartitionController {
   private router: Router;
@@ -19,22 +19,22 @@ export class PartitionController {
   }
 
   generateRoutes() {
-    this.router.get("/", this.getPatitionsInfo.bind(this));
+    this.router.get('/', this.getPartitionsInfo.bind(this));
 
     this.router.post(
-      "/",
+      '/',
       dtoValidationMiddleware(ManagePartitionDto),
       this.managePartition.bind(this)
     );
 
     this.router.put(
-      "/load",
+      '/load',
       dtoValidationMiddleware(LoadPartitionsDto),
       this.loadPartition.bind(this)
     );
 
     this.router.put(
-      "/release",
+      '/release',
       dtoValidationMiddleware(LoadPartitionsDto),
       this.releasePartition.bind(this)
     );
@@ -42,10 +42,10 @@ export class PartitionController {
     return this.router;
   }
 
-  async getPatitionsInfo(req: Request, res: Response, next: NextFunction) {
-    const collectionName = "" + req.query?.collection_name;
+  async getPartitionsInfo(req: Request, res: Response, next: NextFunction) {
+    const collectionName = '' + req.query?.collection_name;
     try {
-      const result = await this.partitionsService.getPatitionsInfo({
+      const result = await this.partitionsService.getPartitionsInfo({
         collection_name: collectionName,
       });
       res.send(result);
@@ -58,9 +58,9 @@ export class PartitionController {
     const { type, ...params } = req.body;
     try {
       const result =
-        type.toLocaleLowerCase() === "create"
-          ? await this.partitionsService.createParition(params)
-          : await this.partitionsService.deleteParition(params);
+        type.toLocaleLowerCase() === 'create'
+          ? await this.partitionsService.createPartition(params)
+          : await this.partitionsService.deletePartition(params);
       res.send(result);
     } catch (error) {
       next(error);

+ 4 - 4
express/src/partitions/partitions.service.ts

@@ -18,7 +18,7 @@ export class PartitionsService {
     return this.milvusService.partitionManager;
   }
 
-  async getPatitionsInfo(data: ShowPartitionsReq) {
+  async getPartitionsInfo(data: ShowPartitionsReq) {
     const result = [];
     const res = await this.getPartitions(data);
     if (res.partition_names && res.partition_names.length) {
@@ -44,13 +44,13 @@ export class PartitionsService {
     return res;
   }
 
-  async createParition(data: CreatePartitionReq) {
+  async createPartition(data: CreatePartitionReq) {
     const res = await this.partitionManager.createPartition(data);
     throwErrorFromSDK(res);
     return res;
   }
 
-  async deleteParition(data: DropPartitionReq) {
+  async deletePartition(data: DropPartitionReq) {
     const res = await this.partitionManager.dropPartition(data);
     throwErrorFromSDK(res);
     return res;
@@ -73,4 +73,4 @@ export class PartitionsService {
     throwErrorFromSDK(res);
     return res;
   }
-}
+}

File diff suppressed because it is too large
+ 812 - 6
express/yarn.lock


+ 4 - 4
server/src/partitions/partitions.controller.ts

@@ -20,12 +20,12 @@ import { PartitionsService } from './partitions.service';
 @ApiTags('partitions')
 @Controller('partitions')
 export class PartitionsController {
-  constructor(private partitionsService: PartitionsService) { }
+  constructor(private partitionsService: PartitionsService) {}
 
   @Get()
   @UsePipes(new ValidationPipe())
   async getPartitions(@Query() query: GetPartitionsInfo) {
-    return await this.partitionsService.getPatitionsInfo(query);
+    return await this.partitionsService.getPartitionsInfo(query);
   }
 
   @Post()
@@ -34,8 +34,8 @@ export class PartitionsController {
     const { type, ...params } = body;
 
     return type.toLocaleLowerCase() === ManageType.CREATE
-      ? await this.partitionsService.createParition(params)
-      : await this.partitionsService.deleteParition(params);
+      ? await this.partitionsService.createPartition(params)
+      : await this.partitionsService.deletePartition(params);
   }
 
   @Put('load')

+ 3 - 3
server/src/partitions/partitions.service.ts

@@ -20,7 +20,7 @@ export class PartitionsService {
     return this.milvusService.partitionManager;
   }
 
-  async getPatitionsInfo(data: ShowPartitionsReq) {
+  async getPartitionsInfo(data: ShowPartitionsReq) {
     const result = [];
     const res = await this.getPartitions(data);
     if (res.partition_names && res.partition_names.length) {
@@ -46,13 +46,13 @@ export class PartitionsService {
     return res;
   }
 
-  async createParition(data: CreatePartitionReq) {
+  async createPartition(data: CreatePartitionReq) {
     const res = await this.partitionManager.createPartition(data);
     throwErrorFromSDK(res);
     return res;
   }
 
-  async deleteParition(data: DropPartitionReq) {
+  async deletePartition(data: DropPartitionReq) {
     const res = await this.partitionManager.dropPartition(data);
     throwErrorFromSDK(res);
     return res;

Some files were not shown because too many files changed in this diff