diff --git a/jest.config.js b/jest.config.js index 0aed2e29..ae544ed0 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,6 +6,10 @@ module.exports = { testTimeout: 60000, // because user will cause other test fail, but we still have user spec coveragePathIgnorePatterns: ['/milvus/User.ts'], + testPathIgnorePatterns: [ + 'cloud.spec.ts', + 'serverless.spec.ts', + ], // add this line testEnvironmentOptions: { NODE_ENV: 'production', }, diff --git a/milvus/HttpClient.ts b/milvus/HttpClient.ts new file mode 100644 index 00000000..eee5fe83 --- /dev/null +++ b/milvus/HttpClient.ts @@ -0,0 +1,97 @@ +import axios, { AxiosInstance } from 'axios'; +import { HttpClientConfig } from './types'; +import { Collection, Vector } from './http'; +import { + DEFAULT_DB, + DEFAULT_HTTP_TIMEOUT, + DEFAULT_HTTP_ENDPOINT_VERSION, +} from '../milvus/const'; + +// base class +export class HttpBaseClient { + // The client configuration. + public config: HttpClientConfig; + + // axios + public client: AxiosInstance; + + constructor(config: HttpClientConfig) { + // Assign the configuration object. + this.config = config; + + // setup axios client + this.client = axios.create({ + baseURL: this.baseURL, + timeout: this.timeout, + timeoutErrorMessage: '', + withCredentials: true, + headers: { + Authorization: this.authorization, + Accept: 'application/json', + ContentType: 'application/json', + }, + }); + + // interceptors + this.client.interceptors.request.use(request => { + // if dbName is not set, using default database + // GET + if (request.params) { + request.params.dbName = request.params.dbName || this.database; + } + // POST + if (request.data) { + request.data.dbName = request.data.dbName || this.database; + request.data = JSON.stringify(request.data); + } + + // console.log('request: ', request.data); + return request; + }); + this.client.interceptors.response.use(response => { + return response.data; + }); + } + + // baseURL + get baseURL() { + return ( + this.config.baseURL || + `${this.config.endpoint}/${ + this.config.version || DEFAULT_HTTP_ENDPOINT_VERSION + }` + ); + } + + // authorization + get authorization() { + let token = this.config.token || ''; + + if (!token && this.config.username && this.config.password) { + token = this.config.username + ':' + this.config.password; + } + + return `Bearer ${token}`; + } + + // database + get database() { + return this.config.database || DEFAULT_DB; + } + + // timeout + get timeout() { + return this.config.timeout || DEFAULT_HTTP_TIMEOUT; + } + + get POST() { + return this.client.post; + } + + get GET() { + return this.client.get; + } +} + +// mixin APIs +export class HttpClient extends Collection(Vector(HttpBaseClient)) {} diff --git a/milvus/const/defaults.ts b/milvus/const/defaults.ts index 60a726c9..5b29f962 100644 --- a/milvus/const/defaults.ts +++ b/milvus/const/defaults.ts @@ -12,3 +12,5 @@ export const DEFAULT_RESOURCE_GROUP = '__default_resource_group'; export const DEFAULT_DB = 'default'; export const DEFAULT_DYNAMIC_FIELD = '$meta'; export const DEFAULT_COUNT_QUERY_STRING = 'count(*)'; +export const DEFAULT_HTTP_TIMEOUT = 60000; // 60s +export const DEFAULT_HTTP_ENDPOINT_VERSION = 'v1'; // api version, default v1 diff --git a/milvus/http/Collection.ts b/milvus/http/Collection.ts new file mode 100644 index 00000000..dfbb5c31 --- /dev/null +++ b/milvus/http/Collection.ts @@ -0,0 +1,46 @@ +import { HttpBaseClient } from '../HttpClient'; +import { Constructor } from '../types/index'; +import { + HttpCollectionCreateReq, + HttpCollectionListReq, + HttpCollectionListResponse, + HttpCollectionDescribeResponse, + HttpBaseResponse, + HttpBaseReq, +} from '../types'; + +export function Collection>(Base: T) { + return class extends Base { + // POST create collection + async createCollection( + data: HttpCollectionCreateReq + ): Promise { + const url = `/vector/collections/create`; + return await this.POST(url, data); + } + + // GET describe collection + async describeCollection( + params: HttpBaseReq + ): Promise { + const url = `/vector/collections/describe`; + return await this.GET(url, { params }); + } + + // POST drop collection + async dropCollection(data: HttpBaseReq): Promise { + const url = `/vector/collections/drop`; + + return await this.POST(url, data); + } + + // GET list collections + async listCollections( + params: HttpCollectionListReq = {} + ): Promise { + const url = `/vector/collections`; + + return await this.GET(url, { params }); + } + }; +} diff --git a/milvus/http/Vector.ts b/milvus/http/Vector.ts new file mode 100644 index 00000000..d4d05b19 --- /dev/null +++ b/milvus/http/Vector.ts @@ -0,0 +1,47 @@ +import { HttpBaseClient } from '../HttpClient'; +import { + Constructor, + HttpVectorGetReq, + HttpVectorInsertReq, + HttpVectorInsertResponse, + HttpVectorQueryReq, + HttpVectorQueryResponse, + HttpVectorSearchReq, + HttpVectorDeleteReq, + HttpVectorSearchResponse, + HttpBaseResponse, +} from '../types'; + +export function Vector>(Base: T) { + return class extends Base { + // GET get data + async get(params: HttpVectorGetReq): Promise { + const url = `/vector/get`; + return await this.GET(url, { params }); + } + + // POST insert data + async insert(data: HttpVectorInsertReq): Promise { + const url = `/vector/insert`; + return await this.POST(url, data); + } + + // POST query data + async query(data: HttpVectorQueryReq): Promise { + const url = `/vector/query`; + return await this.client.post(url, data); + } + + // POST search data + async search(data: HttpVectorSearchReq): Promise { + const url = `/vector/search`; + return await this.POST(url, data); + } + + // POST delete collection + async delete(data: HttpVectorDeleteReq): Promise { + const url = `/vector/delete`; + return await this.POST(url, data); + } + }; +} diff --git a/milvus/http/index.ts b/milvus/http/index.ts new file mode 100644 index 00000000..9297875e --- /dev/null +++ b/milvus/http/index.ts @@ -0,0 +1,2 @@ +export * from './Collection'; +export * from './Vector'; diff --git a/milvus/index.ts b/milvus/index.ts index 9f9e60c5..9147c666 100644 --- a/milvus/index.ts +++ b/milvus/index.ts @@ -8,3 +8,4 @@ export * from './types'; export * from './grpc/GrpcClient'; export * from './MilvusClient'; export * from './OrmClient'; +export * from './HttpClient'; diff --git a/milvus/types.ts b/milvus/types.ts index b9901d7e..866cb96b 100644 --- a/milvus/types.ts +++ b/milvus/types.ts @@ -10,3 +10,4 @@ export * from './types/User'; export * from './types/Resource'; export * from './types/Client'; export * from './types/HighLevel'; +export * from './types/Http'; diff --git a/milvus/types/Http.ts b/milvus/types/Http.ts new file mode 100644 index 00000000..ef74faca --- /dev/null +++ b/milvus/types/Http.ts @@ -0,0 +1,136 @@ +import { FloatVectors } from '..'; +// Class types +export type Constructor = new (...args: any[]) => T; + +type HttpClientConfigBase = { + // database name + database?: string; + // version + version?: string; + // token + token?: string; + // The username to use for authentication. + username?: string; + // The password to use for authentication. + password?: string; + // request timeout, number in milliseconds. + timeout?: number; +}; + +type HttpClientConfigAddress = HttpClientConfigBase & { + // The address in the format of ${MILVUS_HOST}:${MILVUS_PORT}, for example: 127.0.0.1:19530. It is used to build the baseURL for the HTTP client. + endpoint: string; + baseURL?: string; +}; + +type HttpClientConfigBaseURL = HttpClientConfigBase & { + endpoint?: string; + // The baseURL is the endpoint's base URL. It is in the format https://${MILVUS_HOST}:${MILVUS_PORT}/v1/. If baseURL is set, it will override the address property. + baseURL: string; +}; + +export type HttpClientConfig = + | HttpClientConfigAddress + | HttpClientConfigBaseURL; + +// http base request +export interface HttpBaseReq { + dbName?: string; + collectionName: string; +} +// http base response +export interface HttpBaseResponse { + code: number; + data: T; + message?: string; +} + +// collection operations +export interface HttpCollectionCreateReq extends HttpBaseReq { + dimension: number; + metricType: string; + primaryField: string; + vectorField: string; + description?: string; +} +// list collection request +export interface HttpCollectionListReq + extends Omit {} + +type Field = { + autoId?: boolean; + description: string; + primaryKey?: boolean; + type: string; +}; + +type Index = { + fieldName: string; + indexName: string; + metricType: string; +}; + +// describe collection response +export interface HttpCollectionDescribeResponse + extends HttpBaseResponse<{ + collectionName: string; + description: string; + fields: Field[]; + indexes: Index[]; + load: string; + shardsNum: number; + enableDynamic: boolean; + }> {} + +// list collections response +export interface HttpCollectionListResponse + extends HttpBaseResponse {} + +// vector operations +// insert data request +export interface HttpVectorInsertReq extends HttpBaseReq { + data: Record[]; +} + +// insert data response +export interface HttpVectorInsertResponse + extends HttpBaseResponse<{ + insertCount: number; + insertIds: number | string[]; + }> {} + +// get vector request +export interface HttpVectorGetReq extends HttpBaseReq { + id: number | number[] | string | string[]; + outputFields: string[]; +} + +// delete vector request +export interface HttpVectorDeleteReq + extends Omit {} + +// query data request +export interface HttpVectorQueryReq extends HttpBaseReq { + outputFields: string[]; + filter?: string; + limit?: number; + offset?: number; + params?: Record; +} + +type QueryResult = Record[]; + +// query response +export interface HttpVectorQueryResponse + extends HttpBaseResponse {} + +// search request +export interface HttpVectorSearchReq + extends Omit { + vector: FloatVectors; + filter?: string; +} + +export interface HttpVectorSearchResponse extends HttpVectorQueryResponse { + data: QueryResult & { distance: number | string }; +} diff --git a/milvus/types/index.ts b/milvus/types/index.ts index efde871a..28cd84d0 100644 --- a/milvus/types/index.ts +++ b/milvus/types/index.ts @@ -11,3 +11,4 @@ export * from './Resource'; export * from './Client'; export * from './MilvusIndex'; export * from './HighLevel'; +export * from './Http'; diff --git a/package.json b/package.json index a3edd017..90b0f53e 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,10 @@ "scripts": { "pre": "git submodule update --remote && rm -rf proto/proto/google && mkdir -p proto/proto/google/protobuf && wget https://raw.githubusercontent.com/protocolbuffers/protobuf/main/src/google/protobuf/descriptor.proto -O proto/proto/google/protobuf/descriptor.proto", "build": "rm -rf dist && tsc --declaration && node build.js", - "testAll": "jest --testPathIgnorePatterns=/test/build/", "test": "NODE_ENV=dev jest", "bench": "ts-node test/tools/bench.ts", - "coverage": "NODE_ENV=dev jest --coverage=true --config jest.config.js --no-cache --testPathIgnorePatterns=/test/build/", - "build-test": " yarn build && jest test/build/Collection.spec.ts", + "coverage": "NODE_ENV=dev jest --coverage=true --config jest.config.js --no-cache", + "build-test": " yarn build && NODE_ENV=dev jest test/build/Collection.spec.ts --testPathIgnorePatterns=none", "example": "npx ts-node", "doc": "rm -rf docs && npx typedoc", "doc-json": "npx typedoc milvus --json" @@ -22,6 +21,7 @@ "dependencies": { "@grpc/grpc-js": "1.8.17", "@grpc/proto-loader": "0.7.7", + "axios": "^1.5.1", "dayjs": "^1.11.7", "lru-cache": "^9.1.2", "protobufjs": "7.2.4", diff --git a/test/Alias.spec.ts b/test/grpc/Alias.spec.ts similarity index 93% rename from test/Alias.spec.ts rename to test/grpc/Alias.spec.ts index aeca1948..d2f66e33 100644 --- a/test/Alias.spec.ts +++ b/test/grpc/Alias.spec.ts @@ -1,5 +1,5 @@ -import { MilvusClient, ERROR_REASONS, ErrorCode } from '../milvus'; -import { IP, genCollectionParams, GENERATE_NAME } from './tools'; +import { MilvusClient, ERROR_REASONS, ErrorCode } from '../../milvus'; +import { IP, genCollectionParams, GENERATE_NAME } from '../tools'; let milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Basic.spec.ts b/test/grpc/Basic.spec.ts similarity index 95% rename from test/Basic.spec.ts rename to test/grpc/Basic.spec.ts index 41cef380..05e2b13c 100644 --- a/test/Basic.spec.ts +++ b/test/grpc/Basic.spec.ts @@ -1,5 +1,5 @@ -import { MilvusClient, ErrorCode, DataType } from '../milvus'; -import { IP, GENERATE_NAME, generateInsertData } from './tools'; +import { MilvusClient, ErrorCode, DataType } from '../../milvus'; +import { IP, GENERATE_NAME, generateInsertData } from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Collection.spec.ts b/test/grpc/Collection.spec.ts similarity index 99% rename from test/Collection.spec.ts rename to test/grpc/Collection.spec.ts index ca329946..97acd30c 100644 --- a/test/Collection.spec.ts +++ b/test/grpc/Collection.spec.ts @@ -6,14 +6,14 @@ import { ERROR_REASONS, LoadState, formatKeyValueData, -} from '../milvus'; +} from '../../milvus'; import { IP, genCollectionParams, VECTOR_FIELD_NAME, GENERATE_NAME, -} from './tools'; -import { timeoutTest } from './tools'; +} from '../tools'; +import { timeoutTest } from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Data.spec.ts b/test/grpc/Data.spec.ts similarity index 99% rename from test/Data.spec.ts rename to test/grpc/Data.spec.ts index ad480d69..8b45ca9e 100644 --- a/test/Data.spec.ts +++ b/test/grpc/Data.spec.ts @@ -5,7 +5,7 @@ import { ERROR_REASONS, DEFAULT_TOPK, DEFAULT_COUNT_QUERY_STRING, -} from '../milvus'; +} from '../../milvus'; import { IP, generateInsertData, @@ -13,8 +13,8 @@ import { VECTOR_FIELD_NAME, GENERATE_NAME, // DEFAULT_VALUE, -} from './tools'; -import { timeoutTest } from './tools'; +} from '../tools'; +import { timeoutTest } from '../tools'; const milvusClient = new MilvusClient({ address: IP, diff --git a/test/Database.spec.ts b/test/grpc/Database.spec.ts similarity index 94% rename from test/Database.spec.ts rename to test/grpc/Database.spec.ts index 5ce92e5c..36f96e05 100644 --- a/test/Database.spec.ts +++ b/test/grpc/Database.spec.ts @@ -1,5 +1,5 @@ -import { MilvusClient, ErrorCode, DEFAULT_DB } from '../milvus'; -import { IP, genCollectionParams, GENERATE_NAME } from './tools'; +import { MilvusClient, ErrorCode, DEFAULT_DB } from '../../milvus'; +import { IP, genCollectionParams, GENERATE_NAME } from '../tools'; let milvusClient = new MilvusClient({ address: IP }); const DB_NAME = GENERATE_NAME('database'); diff --git a/test/DynamicSchema.spec.ts b/test/grpc/DynamicSchema.spec.ts similarity index 98% rename from test/DynamicSchema.spec.ts rename to test/grpc/DynamicSchema.spec.ts index 8118e07c..9f05e0cc 100644 --- a/test/DynamicSchema.spec.ts +++ b/test/grpc/DynamicSchema.spec.ts @@ -3,14 +3,14 @@ import { DataType, ErrorCode, ConsistencyLevelEnum, -} from '../milvus'; +} from '../../milvus'; import { IP, genCollectionParams, GENERATE_NAME, generateInsertData, dynamicFields, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION = GENERATE_NAME(); diff --git a/test/Import.spec.ts b/test/grpc/Import.spec.ts similarity index 95% rename from test/Import.spec.ts rename to test/grpc/Import.spec.ts index 1a803f2b..1f2ea9fe 100644 --- a/test/Import.spec.ts +++ b/test/grpc/Import.spec.ts @@ -1,11 +1,11 @@ import * as path from 'path'; -import { MilvusClient, DataType, ErrorCode } from '../milvus'; +import { MilvusClient, DataType, ErrorCode } from '../../milvus'; import { IP, genCollectionParams, VECTOR_FIELD_NAME, GENERATE_NAME, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Index.spec.ts b/test/grpc/Index.spec.ts similarity index 99% rename from test/Index.spec.ts rename to test/grpc/Index.spec.ts index d9aed2ef..5ce55a26 100644 --- a/test/Index.spec.ts +++ b/test/grpc/Index.spec.ts @@ -1,12 +1,12 @@ -import { MilvusClient, ErrorCode, MetricType, IndexType } from '../milvus'; +import { MilvusClient, ErrorCode, MetricType, IndexType } from '../../milvus'; import { IP, genCollectionParams, VECTOR_FIELD_NAME, INDEX_NAME, GENERATE_NAME, -} from './tools'; -import { timeoutTest } from './tools'; +} from '../tools'; +import { timeoutTest } from '../tools'; const milvusClient = new MilvusClient({ address: IP }); // names diff --git a/test/Insert.spec.ts b/test/grpc/Insert.spec.ts similarity index 99% rename from test/Insert.spec.ts rename to test/grpc/Insert.spec.ts index e0ca1c9c..a010a456 100644 --- a/test/Insert.spec.ts +++ b/test/grpc/Insert.spec.ts @@ -4,7 +4,7 @@ import { ErrorCode, InsertReq, ERROR_REASONS, -} from '../milvus'; +} from '../../milvus'; import { IP, generateInsertData, @@ -12,7 +12,7 @@ import { VECTOR_FIELD_NAME, GENERATE_NAME, genFloatVector, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/MilvusClient.spec.ts b/test/grpc/MilvusClient.spec.ts similarity index 97% rename from test/MilvusClient.spec.ts rename to test/grpc/MilvusClient.spec.ts index 0b09e242..030f44b5 100644 --- a/test/MilvusClient.spec.ts +++ b/test/grpc/MilvusClient.spec.ts @@ -1,6 +1,6 @@ -import { MilvusClient, ERROR_REASONS, CONNECT_STATUS } from '../milvus'; -import sdkInfo from '../sdk.json'; -import { IP } from './tools'; +import { MilvusClient, ERROR_REASONS, CONNECT_STATUS } from '../../milvus'; +import sdkInfo from '../../sdk.json'; +import { IP } from '../tools'; const milvusClient = new MilvusClient({ address: IP, diff --git a/test/Orm.spec.ts b/test/grpc/Orm.spec.ts similarity index 98% rename from test/Orm.spec.ts rename to test/grpc/Orm.spec.ts index cc8f4af6..d5f7b5a9 100644 --- a/test/Orm.spec.ts +++ b/test/grpc/Orm.spec.ts @@ -3,15 +3,15 @@ import { ErrorCode, DataType, ConsistencyLevelEnum, -} from '../milvus'; +} from '../../milvus'; import { IP, genCollectionParams, GENERATE_NAME, generateInsertData, dynamicFields, -} from './tools'; -import { Collection } from '../milvus/orm'; +} from '../tools'; +import { Collection } from '../../milvus/orm'; let milvusClient = new MilvusClient({ address: IP }); const EXIST_COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Partition.spec.ts b/test/grpc/Partition.spec.ts similarity index 97% rename from test/Partition.spec.ts rename to test/grpc/Partition.spec.ts index f2eda795..0b020c2a 100644 --- a/test/Partition.spec.ts +++ b/test/grpc/Partition.spec.ts @@ -1,11 +1,11 @@ -import { MilvusClient, ErrorCode, ERROR_REASONS } from '../milvus'; +import { MilvusClient, ErrorCode, ERROR_REASONS } from '../../milvus'; import { IP, genCollectionParams, VECTOR_FIELD_NAME, GENERATE_NAME, -} from './tools'; -import { timeoutTest } from './tools'; +} from '../tools'; +import { timeoutTest } from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/PartitionKey.spec.ts b/test/grpc/PartitionKey.spec.ts similarity index 99% rename from test/PartitionKey.spec.ts rename to test/grpc/PartitionKey.spec.ts index 23f9c73f..76b0de0f 100644 --- a/test/PartitionKey.spec.ts +++ b/test/grpc/PartitionKey.spec.ts @@ -4,13 +4,13 @@ import { ErrorCode, ERROR_REASONS, DEFAULT_PARTITIONS_NUMBER, -} from '../milvus'; +} from '../../milvus'; import { IP, genCollectionParams, GENERATE_NAME, generateInsertData, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Replica.spec.ts b/test/grpc/Replica.spec.ts similarity index 94% rename from test/Replica.spec.ts rename to test/grpc/Replica.spec.ts index 95b587fb..e2d6fc95 100644 --- a/test/Replica.spec.ts +++ b/test/grpc/Replica.spec.ts @@ -1,10 +1,10 @@ -import { MilvusClient, ERROR_REASONS, ErrorCode } from '../milvus'; +import { MilvusClient, ERROR_REASONS, ErrorCode } from '../../milvus'; import { IP, genCollectionParams, VECTOR_FIELD_NAME, GENERATE_NAME, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/Resource.spec.ts b/test/grpc/Resource.spec.ts similarity index 98% rename from test/Resource.spec.ts rename to test/grpc/Resource.spec.ts index 5f33762a..968e2cdc 100644 --- a/test/Resource.spec.ts +++ b/test/grpc/Resource.spec.ts @@ -1,10 +1,10 @@ -import { MilvusClient, ErrorCode } from '../milvus'; +import { MilvusClient, ErrorCode } from '../../milvus'; import { IP, genCollectionParams, VECTOR_FIELD_NAME, GENERATE_NAME, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); diff --git a/test/Upsert.spec.ts b/test/grpc/Upsert.spec.ts similarity index 99% rename from test/Upsert.spec.ts rename to test/grpc/Upsert.spec.ts index 6c77729b..5b757c94 100644 --- a/test/Upsert.spec.ts +++ b/test/grpc/Upsert.spec.ts @@ -4,14 +4,14 @@ import { ErrorCode, InsertReq, ERROR_REASONS, -} from '../milvus'; +} from '../../milvus'; import { IP, generateInsertData, genCollectionParams, VECTOR_FIELD_NAME, GENERATE_NAME, -} from './tools'; +} from '../tools'; const milvusClient = new MilvusClient({ address: IP }); const COLLECTION_NAME = GENERATE_NAME(); diff --git a/test/User.spec.ts b/test/grpc/User.spec.ts similarity index 98% rename from test/User.spec.ts rename to test/grpc/User.spec.ts index bd28cb33..77a7f291 100644 --- a/test/User.spec.ts +++ b/test/grpc/User.spec.ts @@ -6,9 +6,9 @@ import { Roles, Privileges, RbacObjects, -} from '../milvus'; -import { timeoutTest } from './tools'; -import { IP, genCollectionParams, GENERATE_NAME } from './tools'; +} from '../../milvus'; +import { timeoutTest } from '../tools'; +import { IP, genCollectionParams, GENERATE_NAME } from '../tools'; const milvusClient = new MilvusClient({ address: IP }); let authClient: MilvusClient; diff --git a/test/http/api.spec.ts b/test/http/api.spec.ts new file mode 100644 index 00000000..97e12732 --- /dev/null +++ b/test/http/api.spec.ts @@ -0,0 +1,129 @@ +import { HttpClient, MilvusClient } from '../../milvus'; +import { + IP, + ENDPOINT, + genCollectionParams, + generateInsertData, + dynamicFields, +} from '../tools'; + +const milvusClient = new MilvusClient({ address: IP }); +const dbParam = { + db_name: 'HttpClient_collections', +}; + +describe(`HTTP API tests`, () => { + // Mock configuration object + const config = { + endpoint: ENDPOINT, + database: dbParam.db_name, + }; + + const createParams = { + dimension: 4, + collectionName: 'my_collection', + metricType: 'L2', + primaryField: 'id', + vectorField: 'vector', + description: 'des', + }; + + const count = 10; + const data = generateInsertData( + [ + ...genCollectionParams({ + collectionName: createParams.collectionName, + dim: createParams.dimension, + enableDynamic: true, + }).fields, + ...dynamicFields, + ], + count + ); + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + + beforeAll(async () => { + await milvusClient.createDatabase(dbParam); + await milvusClient.use(dbParam); + }); + + afterAll(async () => { + // await milvusClient.dropCollection({ + // collection_name: createParams.collectionName, + // }); + await milvusClient.dropDatabase(dbParam); + }); + + it('should create collection successfully', async () => { + const create = await client.createCollection(createParams); + + const hasCollection = await milvusClient.hasCollection({ + collection_name: createParams.collectionName, + }); + + expect(create.code).toEqual(200); + expect(hasCollection.value).toEqual(true); + }); + + it('should describe collection successfully', async () => { + const describe = await client.describeCollection({ + collectionName: createParams.collectionName, + }); + + expect(describe.code).toEqual(200); + expect(describe.data.description).toEqual(createParams.description); + expect(describe.data.shardsNum).toEqual(1); + expect(describe.data.enableDynamic).toEqual(true); + expect(describe.data.fields.length).toEqual(2); + }); + + it('should list collections successfully', async () => { + const list = await client.listCollections(); + expect(list.code).toEqual(200); + expect(list.data[0]).toEqual(createParams.collectionName); + }); + + it('should insert data successfully', async () => { + const insert = await client.insert({ + collectionName: createParams.collectionName, + data: data, + }); + + expect(insert.code).toEqual(200); + expect(insert.data.insertCount).toEqual(count); + }); + + it('should query data successfully', async () => { + const query = await client.query({ + collectionName: createParams.collectionName, + outputFields: ['id'], + filter: 'id > 0', + }); + + expect(query.code).toEqual(200); + expect(query.data.length).toEqual(data.length); + }); + + it('should search data successfully', async () => { + const search = await client.search({ + collectionName: createParams.collectionName, + outputFields: ['*'], + vector: [1, 2, 3, 4], + limit: 5, + }); + + expect(search.code).toEqual(200); + expect(search.data.length).toEqual(5); + expect(typeof search.data[0].distance).toEqual('number'); + }); + + it('should drop collection successfully', async () => { + const drop = await client.dropCollection({ + collectionName: createParams.collectionName, + }); + + expect(drop.code).toEqual(200); + }); +}); diff --git a/test/http/client.spec.ts b/test/http/client.spec.ts new file mode 100644 index 00000000..254363a1 --- /dev/null +++ b/test/http/client.spec.ts @@ -0,0 +1,115 @@ +import { + HttpClient, + DEFAULT_DB, + DEFAULT_HTTP_TIMEOUT, + DEFAULT_HTTP_ENDPOINT_VERSION, +} from '../../milvus'; + +describe(`HTTP Client test`, () => { + const baseURL = 'http://192.168.0.1:19530'; + const version = 'v3'; + const username = 'user'; + const password = 'pass'; + const token = 'token'; + const database = 'db'; + const timeout = 5000; + + it('should return the correct baseURL', () => { + // Mock configuration object + const config = { + baseURL, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + expect(client.baseURL).toBe(baseURL); + }); + + it('should return the correct baseURL if only provide endpoint', () => { + // Mock configuration object + const config = { + endpoint: baseURL, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + expect(client.baseURL).toBe( + `${config.endpoint}/${DEFAULT_HTTP_ENDPOINT_VERSION}` + ); + }); + + it('should return the correct baseURL if version is defined', () => { + // Mock configuration object + const config = { + endpoint: baseURL, + version, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + expect(client.baseURL).toBe(`${config.endpoint}/${version}`); + }); + + it('should return the correct authorization header', () => { + // Mock configuration object + const config = { + baseURL, + username, + password, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + const expectedAuthorization = `Bearer ${config.username}:${config.password}`; + expect(client.authorization).toBe(expectedAuthorization); + + const config2 = { + baseURL, + token, + }; + + const client2 = new HttpClient(config2); + const expectedAuthorization2 = `Bearer ${config2.token}`; + expect(client2.authorization).toBe(expectedAuthorization2); + }); + + it('should return the correct database', () => { + // Mock configuration object + const config = { + baseURL, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + expect(client.database).toBe(DEFAULT_DB); + // Mock configuration object + const config2 = { + baseURL, + database, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client2 = new HttpClient(config2); + expect(client2.database).toBe(database); + }); + + it('should return the correct timeout', () => { + // Mock configuration object + const config = { + baseURL, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + expect(client.timeout).toBe(DEFAULT_HTTP_TIMEOUT); + // Mock configuration object + const config2 = { + baseURL, + timeout, + }; + + // Create an instance of HttpBaseClient with the mock configuration + const client2 = new HttpClient(config2); + expect(client2.timeout).toBe(timeout); + }); +}); diff --git a/test/http/cloud.spec.ts b/test/http/cloud.spec.ts new file mode 100644 index 00000000..b06c2dce --- /dev/null +++ b/test/http/cloud.spec.ts @@ -0,0 +1,104 @@ +import { HttpClient } from '../../milvus'; +import { + genFloatVector, + genCollectionParams, + generateInsertData, + dynamicFields, +} from '../tools'; + +describe(`Dedicated restful API tests`, () => { + const config = { + endpoint: 'dedicated endpoint', + username: 'username', + password: 'password', + }; + + const createParams = { + dimension: 32, + collectionName: 'my_collection', + metricType: 'L2', + primaryField: 'id', + vectorField: 'vector', + description: 'des', + }; + + const count = 10; + const data = generateInsertData( + [ + ...genCollectionParams({ + collectionName: createParams.collectionName, + dim: createParams.dimension, + enableDynamic: true, + }).fields, + ...dynamicFields, + ], + count + ); + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + + it('should create collection successfully', async () => { + const create = await client.createCollection(createParams); + expect(create.code).toEqual(200); + }); + + it('should describe collection successfully', async () => { + const describe = await client.describeCollection({ + collectionName: createParams.collectionName, + }); + + expect(describe.code).toEqual(200); + expect(describe.data.description).toEqual(createParams.description); + expect(describe.data.shardsNum).toEqual(1); + expect(describe.data.fields.length).toEqual(2); + }); + + it('should list collections successfully', async () => { + const list = await client.listCollections(); + expect(list.code).toEqual(200); + expect(list.data.indexOf(createParams.collectionName) !== -1).toEqual(true); + }); + + it('should insert data successfully', async () => { + const insert = await client.insert({ + collectionName: createParams.collectionName, + data: data, + }); + + expect(insert.code).toEqual(200); + expect(insert.data.insertCount).toEqual(count); + }); + + it('should query data successfully', async () => { + const query = await client.query({ + collectionName: createParams.collectionName, + outputFields: ['id'], + filter: 'id > 0', + }); + + expect(query.code).toEqual(200); + expect(query.data.length).toEqual(data.length); + }); + + it('should search data successfully', async () => { + const search = await client.search({ + collectionName: createParams.collectionName, + outputFields: ['*'], + vector: genFloatVector({ dim: createParams.dimension }) as number[], + limit: 5, + }); + + expect(search.code).toEqual(200); + expect(search.data.length).toEqual(5); + expect(typeof search.data[0].distance).toEqual('number'); + }); + + it('should drop collection successfully', async () => { + const drop = await client.dropCollection({ + collectionName: createParams.collectionName, + }); + + expect(drop.code).toEqual(200); + }); +}); diff --git a/test/http/serverless.spec.ts b/test/http/serverless.spec.ts new file mode 100644 index 00000000..454d9338 --- /dev/null +++ b/test/http/serverless.spec.ts @@ -0,0 +1,104 @@ +import { HttpClient } from '../../milvus'; +import { + genFloatVector, + genCollectionParams, + generateInsertData, + dynamicFields, +} from '../tools'; + +describe(`Serverless restful API tests`, () => { + const config = { + endpoint: 'serverless endpoint', + token: 'serverless api key', + }; + + const createParams = { + dimension: 32, + collectionName: 'my_collection', + metricType: 'L2', + primaryField: 'id', + vectorField: 'vector', + description: 'des', + }; + + const count = 10; + const data = generateInsertData( + [ + ...genCollectionParams({ + collectionName: createParams.collectionName, + dim: createParams.dimension, + enableDynamic: true, + }).fields, + ...dynamicFields, + ], + count + ); + + // Create an instance of HttpBaseClient with the mock configuration + const client = new HttpClient(config); + + it('should create collection successfully', async () => { + const create = await client.createCollection(createParams); + + expect(create.code).toEqual(200); + }); + + it('should describe collection successfully', async () => { + const describe = await client.describeCollection({ + collectionName: createParams.collectionName, + }); + + expect(describe.code).toEqual(200); + expect(describe.data.description).toEqual(createParams.description); + expect(describe.data.shardsNum).toEqual(1); + expect(describe.data.fields.length).toEqual(2); + }); + + it('should list collections successfully', async () => { + const list = await client.listCollections(); + expect(list.code).toEqual(200); + expect(list.data.indexOf(createParams.collectionName) !== -1).toEqual(true); + }); + + it('should insert data successfully', async () => { + const insert = await client.insert({ + collectionName: createParams.collectionName, + data: data, + }); + + expect(insert.code).toEqual(200); + expect(insert.data.insertCount).toEqual(count); + }); + + it('should query data successfully', async () => { + const query = await client.query({ + collectionName: createParams.collectionName, + outputFields: ['id'], + filter: 'id > 0', + }); + + expect(query.code).toEqual(200); + expect(query.data.length).toEqual(data.length); + }); + + it('should search data successfully', async () => { + const search = await client.search({ + collectionName: createParams.collectionName, + outputFields: ['*'], + vector: genFloatVector({ dim: createParams.dimension }) as number[], + limit: 5, + }); + + expect(search.code).toEqual(200); + expect(search.data.length).toEqual(5); + expect(typeof search.data[0].distance).toEqual('number'); + }); + + it('should drop collection successfully', async () => { + const drop = await client.dropCollection({ + collectionName: createParams.collectionName, + }); + + expect(drop.code).toEqual(200); + }); +}); diff --git a/test/tools/index.ts b/test/tools/index.ts index b9368bc5..0880eda8 100644 --- a/test/tools/index.ts +++ b/test/tools/index.ts @@ -5,3 +5,4 @@ export * from './utils'; // test IP export const IP = '127.0.0.1:19530'; +export const ENDPOINT = `http://${IP}`; diff --git a/yarn.lock b/yarn.lock index 53b0142b..c0902a22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -982,6 +982,20 @@ async@^3.2.3: resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.1.tgz#11fbaa11fc35f431193a9564109c88c1f27b585f" + integrity sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-jest@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.5.0.tgz#3fe3ddb109198e78b1c88f9ebdecd5e4fc2f50a5" @@ -1226,6 +1240,13 @@ colorspace@1.1.x: color "^3.1.3" text-hex "1.0.x" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1277,6 +1298,11 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -1423,6 +1449,20 @@ fn.name@1.x.x: resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== +follow-redirects@^1.15.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2183,6 +2223,18 @@ micromatch@^4.0.4: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" @@ -2382,6 +2434,11 @@ protobufjs@7.2.4, protobufjs@^7.0.0: "@types/node" ">=13.7.0" long "^5.0.0" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pure-rand@^6.0.0: version "6.0.2" resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306"