From d932adc6510f2185740297e41d6f22eb9a714f5c Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 6 Jun 2024 16:25:47 +0700 Subject: [PATCH 01/23] refactor: add models list test cases --- cortex-js/constant.ts | 9 --- cortex-js/package.json | 12 ++-- .../src/file-manager/file-manager.service.ts | 2 +- .../commanders/serve.command.ts | 5 +- .../commanders/shortcuts/run.command.ts | 2 +- .../test/model-list.command.spec.ts | 61 +++++++++++++++++++ .../commanders/usecases/ps.cli.usecases.ts | 2 +- .../src/infrastructure/constants/cortex.ts | 9 ++- .../database/mysql-database.providers.ts | 2 +- .../database/sqlite-database.providers.ts | 2 +- .../dtos/cortex/start-cortex.dto.ts | 2 +- .../providers/cortex/cortex.provider.ts | 2 +- cortex-js/src/main.ts | 5 +- .../src/usecases/cortex/cortex.usecases.ts | 2 +- 14 files changed, 92 insertions(+), 25 deletions(-) delete mode 100644 cortex-js/constant.ts create mode 100644 cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts diff --git a/cortex-js/constant.ts b/cortex-js/constant.ts deleted file mode 100644 index 14bda4837..000000000 --- a/cortex-js/constant.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const databaseName = 'cortex'; - -export const databaseFile = `${databaseName}.db`; - -export const defaultCortexJsHost = 'localhost'; -export const defaultCortexJsPort = 1337; - -export const defaultCortexCppHost = '127.0.0.1'; -export const defaultCortexCppPort = 3928; diff --git a/cortex-js/package.json b/cortex-js/package.json index e795f942a..1e4b08149 100644 --- a/cortex-js/package.json +++ b/cortex-js/package.json @@ -55,12 +55,11 @@ "sqlite3": "^5.1.7", "typeorm": "^0.3.20", "ulid": "^2.3.0", - "yaml": "^2.4.2", + "update-notifier": "^5.0.0", "uuid": "^9.0.1", - "update-notifier": "^5.0.0" + "yaml": "^2.4.2" }, "devDependencies": { - "cpx": "^1.5.0", "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.0.0", @@ -76,18 +75,21 @@ "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", + "cpx": "^1.5.0", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", + "hanbi": "^1.0.3", "jest": "^29.5.0", + "nest-commander-testing": "^3.3.0", "prettier": "^3.0.0", + "run-script-os": "^1.1.6", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3", - "run-script-os": "^1.1.6" + "typescript": "^5.1.3" }, "files": [ "dist" diff --git a/cortex-js/src/file-manager/file-manager.service.ts b/cortex-js/src/file-manager/file-manager.service.ts index 4d838db6f..78b29270c 100644 --- a/cortex-js/src/file-manager/file-manager.service.ts +++ b/cortex-js/src/file-manager/file-manager.service.ts @@ -43,7 +43,7 @@ export class FileManagerService { } } - private async writeConfigFile(config: Config): Promise { + async writeConfigFile(config: Config): Promise { const homeDir = os.homedir(); const configPath = join(homeDir, this.configFile); diff --git a/cortex-js/src/infrastructure/commanders/serve.command.ts b/cortex-js/src/infrastructure/commanders/serve.command.ts index 7e49ad590..f839b4a0b 100644 --- a/cortex-js/src/infrastructure/commanders/serve.command.ts +++ b/cortex-js/src/infrastructure/commanders/serve.command.ts @@ -1,5 +1,8 @@ import { spawn } from 'child_process'; -import { defaultCortexJsHost, defaultCortexJsPort } from 'constant'; +import { + defaultCortexJsHost, + defaultCortexJsPort, +} from '@/infrastructure/constants/cortex'; import { CommandRunner, SubCommand, Option } from 'nest-commander'; import { join } from 'path'; diff --git a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts index 760b16ba3..118212590 100644 --- a/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts +++ b/cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts @@ -7,7 +7,7 @@ import { } from 'nest-commander'; import { exit } from 'node:process'; import { ChatCliUsecases } from '../usecases/chat.cli.usecases'; -import { defaultCortexCppHost, defaultCortexCppPort } from 'constant'; +import { defaultCortexCppHost, defaultCortexCppPort } from '@/infrastructure/constants/cortex'; import { ModelsCliUsecases } from '../usecases/models.cli.usecases'; import { isLocalModel } from '../utils/normalize-model-id'; import { ModelNotFoundException } from '@/infrastructure/exception/model-not-found.exception'; diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts new file mode 100644 index 000000000..4013a7bfe --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -0,0 +1,61 @@ +import { TestingModule } from '@nestjs/testing'; +import { stubMethod } from 'hanbi'; +import { CommandTestFactory } from 'nest-commander-testing'; +import { CommandModule } from '@/command.module'; +import { FileManagerService } from '@/file-manager/file-manager.service'; +import { join } from 'path'; +import { mkdirSync, rmSync, writeFileSync } from 'fs'; + +let commandInstance: TestingModule; + +beforeEach(async () => { + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + // .overrideProvider(LogService) + // .useValue({}) + .compile(); + const fileService = + commandInstance.resolve(FileManagerService); + + // Attempt to create test folder + (await fileService).writeConfigFile({ + dataFolderPath: join(__dirname, 'test_data'), + }); +}); + +afterEach(async () => { + // Attempt to clean test folder + try { + await rmSync(join(__dirname, 'test_data'), { + recursive: true, + force: true, + }); + } catch (e) {} +}); + +describe('models list returns array of models', () => { + test('empty model list', async () => { + const logMock = stubMethod(console, 'table'); + + await CommandTestFactory.run(commandInstance, ['models', 'list']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('many models in the list', async () => { + const logMock = stubMethod(console, 'table'); + + mkdirSync(join(__dirname, 'test_data', 'models'), { recursive: true }); + writeFileSync( + join(__dirname, 'test_data', 'models', 'test.yaml'), + 'model: test', + 'utf8', + ); + + await CommandTestFactory.run(commandInstance, ['models', 'list']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(1); + expect(logMock.firstCall?.args[0][0].id).toBe('test'); + }); +}); diff --git a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts index 51e097e3e..70d20a16b 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/ps.cli.usecases.ts @@ -1,5 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { defaultCortexCppHost, defaultCortexCppPort } from 'constant'; +import { defaultCortexCppHost, defaultCortexCppPort } from '@/infrastructure/constants/cortex'; export interface ModelStat { modelId: string; diff --git a/cortex-js/src/infrastructure/constants/cortex.ts b/cortex-js/src/infrastructure/constants/cortex.ts index 5fab81ecc..6e6b4c400 100644 --- a/cortex-js/src/infrastructure/constants/cortex.ts +++ b/cortex-js/src/infrastructure/constants/cortex.ts @@ -1,5 +1,12 @@ -import { defaultCortexCppHost, defaultCortexCppPort } from '@/../constant'; +export const databaseName = 'cortex'; +export const databaseFile = `${databaseName}.db`; + +export const defaultCortexJsHost = 'localhost'; +export const defaultCortexJsPort = 1337; + +export const defaultCortexCppHost = '127.0.0.1'; +export const defaultCortexCppPort = 3928; // CORTEX CPP export const CORTEX_CPP_EMBEDDINGS_URL = ( host: string = defaultCortexCppHost, diff --git a/cortex-js/src/infrastructure/database/mysql-database.providers.ts b/cortex-js/src/infrastructure/database/mysql-database.providers.ts index 006b726a3..347c82ef5 100644 --- a/cortex-js/src/infrastructure/database/mysql-database.providers.ts +++ b/cortex-js/src/infrastructure/database/mysql-database.providers.ts @@ -1,4 +1,4 @@ -import { databaseName } from 'constant'; +import { databaseName } from '@/infrastructure/constants/cortex'; import { DataSource } from 'typeorm'; export const mysqlDatabaseProviders = [ diff --git a/cortex-js/src/infrastructure/database/sqlite-database.providers.ts b/cortex-js/src/infrastructure/database/sqlite-database.providers.ts index bcedf7b0c..cd3bb54f8 100644 --- a/cortex-js/src/infrastructure/database/sqlite-database.providers.ts +++ b/cortex-js/src/infrastructure/database/sqlite-database.providers.ts @@ -1,5 +1,5 @@ import { FileManagerService } from '@/file-manager/file-manager.service'; -import { databaseFile } from '@/../constant'; +import { databaseFile } from '@/infrastructure/constants/cortex'; import { join } from 'path'; import { DataSource } from 'typeorm'; diff --git a/cortex-js/src/infrastructure/dtos/cortex/start-cortex.dto.ts b/cortex-js/src/infrastructure/dtos/cortex/start-cortex.dto.ts index 3cf6e6b11..6a8536bfc 100644 --- a/cortex-js/src/infrastructure/dtos/cortex/start-cortex.dto.ts +++ b/cortex-js/src/infrastructure/dtos/cortex/start-cortex.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsIP, IsNumber, IsString, Max, Min } from 'class-validator'; -import { defaultCortexCppHost, defaultCortexCppPort } from 'constant'; +import { defaultCortexCppHost, defaultCortexCppPort } from '@/infrastructure/constants/cortex'; export class StartCortexDto { @ApiProperty({ diff --git a/cortex-js/src/infrastructure/providers/cortex/cortex.provider.ts b/cortex-js/src/infrastructure/providers/cortex/cortex.provider.ts index 33aee645e..6d0b26e6b 100644 --- a/cortex-js/src/infrastructure/providers/cortex/cortex.provider.ts +++ b/cortex-js/src/infrastructure/providers/cortex/cortex.provider.ts @@ -4,7 +4,7 @@ import { PromptTemplate } from '@/domain/models/prompt-template.interface'; import { join } from 'path'; import { Model, ModelSettingParams } from '@/domain/models/model.interface'; import { HttpService } from '@nestjs/axios'; -import { defaultCortexCppHost, defaultCortexCppPort } from '@/../constant'; +import { defaultCortexCppHost, defaultCortexCppPort } from '@/infrastructure/constants/cortex'; import { readdirSync } from 'node:fs'; import { normalizeModelId } from '@/infrastructure/commanders/utils/normalize-model-id'; import { firstValueFrom } from 'rxjs'; diff --git a/cortex-js/src/main.ts b/cortex-js/src/main.ts index 9ec978be7..fa7564669 100644 --- a/cortex-js/src/main.ts +++ b/cortex-js/src/main.ts @@ -2,7 +2,10 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { INestApplication, ValidationPipe } from '@nestjs/common'; -import { defaultCortexJsHost, defaultCortexJsPort } from 'constant'; +import { + defaultCortexJsHost, + defaultCortexJsPort, +} from '@/infrastructure/constants/cortex'; import { SeedService } from './usecases/seed/seed.service'; import { FileManagerService } from './file-manager/file-manager.service'; diff --git a/cortex-js/src/usecases/cortex/cortex.usecases.ts b/cortex-js/src/usecases/cortex/cortex.usecases.ts index 6369be8cd..f867667f8 100644 --- a/cortex-js/src/usecases/cortex/cortex.usecases.ts +++ b/cortex-js/src/usecases/cortex/cortex.usecases.ts @@ -3,7 +3,7 @@ import { ChildProcess, spawn } from 'child_process'; import { join } from 'path'; import { CortexOperationSuccessfullyDto } from '@/infrastructure/dtos/cortex/cortex-operation-successfully.dto'; import { HttpService } from '@nestjs/axios'; -import { defaultCortexCppHost, defaultCortexCppPort } from '@/../constant'; +import { defaultCortexCppHost, defaultCortexCppPort } from '@/infrastructure/constants/cortex'; import { existsSync } from 'node:fs'; import { firstValueFrom } from 'rxjs'; import { FileManagerService } from '@/file-manager/file-manager.service'; From 79c400803e332b9ece754a4cdfbdbc28e5f43519 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 6 Jun 2024 17:54:18 +0700 Subject: [PATCH 02/23] fix: async test preparation --- .../test/model-list.command.spec.ts | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index 4013a7bfe..b90b26006 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -8,31 +8,37 @@ import { mkdirSync, rmSync, writeFileSync } from 'fs'; let commandInstance: TestingModule; -beforeEach(async () => { - commandInstance = await CommandTestFactory.createTestingCommand({ - imports: [CommandModule], - }) - // .overrideProvider(LogService) - // .useValue({}) - .compile(); - const fileService = - commandInstance.resolve(FileManagerService); - - // Attempt to create test folder - (await fileService).writeConfigFile({ - dataFolderPath: join(__dirname, 'test_data'), - }); -}); +beforeEach( + () => + new Promise(async (res) => { + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + // .overrideProvider(LogService) + // .useValue({}) + .compile(); + const fileService = + await commandInstance.resolve(FileManagerService); -afterEach(async () => { - // Attempt to clean test folder - try { - await rmSync(join(__dirname, 'test_data'), { - recursive: true, - force: true, - }); - } catch (e) {} -}); + // Attempt to create test folder + await fileService.writeConfigFile({ + dataFolderPath: join(__dirname, 'test_data'), + }); + res(); + }), +); + +afterEach( + () => + new Promise(async (res) => { + // Attempt to clean test folder + rmSync(join(__dirname, 'test_data'), { + recursive: true, + force: true, + }); + res(); + }), +); describe('models list returns array of models', () => { test('empty model list', async () => { From cd8275f68199e10cf1b0fdaa093c1182c0699bf1 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 6 Jun 2024 19:01:03 +0700 Subject: [PATCH 03/23] fix: ignore preset --- .../infrastructure/commanders/usecases/models.cli.usecases.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cortex-js/src/infrastructure/commanders/usecases/models.cli.usecases.ts b/cortex-js/src/infrastructure/commanders/usecases/models.cli.usecases.ts index df60ad0c0..da07499a2 100644 --- a/cortex-js/src/infrastructure/commanders/usecases/models.cli.usecases.ts +++ b/cortex-js/src/infrastructure/commanders/usecases/models.cli.usecases.ts @@ -397,12 +397,14 @@ export class ModelsCliUsecases { private async parsePreset(preset?: string): Promise { const presetsFolder = await this.fileService.getPresetsPath(); + if (!existsSync(presetsFolder)) return {}; + const presetFile = readdirSync(presetsFolder).find( (file) => file.toLowerCase() === `${preset?.toLowerCase()}.yaml` || file.toLowerCase() === `${preset?.toLocaleLowerCase()}.yml`, ); - if (!presetFile) throw new Error(`Preset ${preset} not found`); + if (!presetFile) return {}; const presetPath = join(presetsFolder, presetFile); if (!preset || !existsSync(presetPath)) return {}; From 17fd84c5294f1109fd0d2e6890432e47f1b75bad Mon Sep 17 00:00:00 2001 From: Van-QA Date: Thu, 6 Jun 2024 19:47:16 +0700 Subject: [PATCH 04/23] feat: add models test for CLI --- cortex-js/.eslintrc.js | 6 ++++ .../test/model-list.command.spec.ts | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/cortex-js/.eslintrc.js b/cortex-js/.eslintrc.js index 259de13c7..448a2d910 100644 --- a/cortex-js/.eslintrc.js +++ b/cortex-js/.eslintrc.js @@ -21,5 +21,11 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + }, + ], }, }; diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index b90b26006..da5097c3b 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,6 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; +import { stdout } from 'node:process'; let commandInstance: TestingModule; @@ -64,4 +65,32 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0].length).toBe(1); expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); + + test('run model', async () => { + const logMock = stubMethod(console, 'table'); + + await CommandTestFactory.run(commandInstance, ['run', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('get model', async () => { + const logMock = stubMethod(console, 'log'); + + await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('hello world', async () => { + const logMock = stubMethod(stdout, 'write'); + + await CommandTestFactory.run(commandInstance, [ + 'chat', + '-m', + 'hello world', + ]); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); }); From dff5cd600eb1c2759ee4efc34b388fedea4eb9d5 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Thu, 6 Jun 2024 19:47:16 +0700 Subject: [PATCH 05/23] feat: add models test for CLI --- cortex-js/.eslintrc.js | 6 ++++ .../test/model-list.command.spec.ts | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/cortex-js/.eslintrc.js b/cortex-js/.eslintrc.js index 259de13c7..448a2d910 100644 --- a/cortex-js/.eslintrc.js +++ b/cortex-js/.eslintrc.js @@ -21,5 +21,11 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + }, + ], }, }; diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index b90b26006..da5097c3b 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,6 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; +import { stdout } from 'node:process'; let commandInstance: TestingModule; @@ -64,4 +65,32 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0].length).toBe(1); expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); + + test('run model', async () => { + const logMock = stubMethod(console, 'table'); + + await CommandTestFactory.run(commandInstance, ['run', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('get model', async () => { + const logMock = stubMethod(console, 'log'); + + await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('hello world', async () => { + const logMock = stubMethod(stdout, 'write'); + + await CommandTestFactory.run(commandInstance, [ + 'chat', + '-m', + 'hello world', + ]); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); }); From 7e8b0f40aa8beebb490e860f61b6b19ed46971a0 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Fri, 7 Jun 2024 14:45:50 +0700 Subject: [PATCH 06/23] feat: add cortex chat --- .../test/model-list.command.spec.ts | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index da5097c3b..e6dd60b9a 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,7 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; -import { stdout } from 'node:process'; +import * as process from 'node:process'; let commandInstance: TestingModule; @@ -66,30 +66,39 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); + test('pull existing model', async () => { + const logMock = stubMethod(console, 'error'); + + await CommandTestFactory.run(commandInstance, ['pull', 'test']); + expect(logMock.firstCall?.args[0]).toBe('Model already exists'); + }); + test('run model', async () => { - const logMock = stubMethod(console, 'table'); + const logMock = stubMethod(console, 'log'); await CommandTestFactory.run(commandInstance, ['run', 'llama3']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); + expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); }); test('get model', async () => { const logMock = stubMethod(console, 'log'); await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); + expect(logMock.firstCall?.args[0].files.length).toBe(1); }); test('hello world', async () => { - const logMock = stubMethod(stdout, 'write'); + const logMock = stubMethod(process.stdout, 'write'); + + await CommandTestFactory.run(commandInstance, ['chat', '-m', 'hello']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + }); + + test('local API server via localhost:1337/api', async () => { + const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, [ - 'chat', - '-m', - 'hello world', - ]); + await CommandTestFactory.run(commandInstance, ['serve']); expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); expect(logMock.firstCall?.args[0].length).toBe(0); }); From fd1eb196317605ad943d492c2f4b77b3f7d00dc8 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sat, 8 Jun 2024 00:29:10 +0700 Subject: [PATCH 07/23] feat: add cortex h --- .../commanders/test/help.command.spec.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 cortex-js/src/infrastructure/commanders/test/help.command.spec.ts diff --git a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts new file mode 100644 index 000000000..d4585cabb --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts @@ -0,0 +1,38 @@ +import { TestingModule } from '@nestjs/testing'; +import { spy, Stub, stubMethod } from 'hanbi'; +import { CommandTestFactory } from 'nest-commander-testing'; +import { CommandModule } from '@/command.module'; +import { LogService } from '@/infrastructure/commanders/test/log.service'; + +let commandInstance: TestingModule, + exitSpy: Stub, + stdoutSpy: Stub; + +beforeEach( + () => + new Promise(async (res) => { + stubMethod(process.stderr, 'write'); + exitSpy = stubMethod(process, 'exit'); + stdoutSpy = stubMethod(process.stdout, 'write'); + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + .overrideProvider(LogService) + .useValue({ log: spy().handler }) + .compile(); + res(); + exitSpy.reset(); + stdoutSpy.reset(); + }), +); + +describe('Help command return guideline to users ', () => { + test('should return guideline', async () => { + const stdoutSpy = stubMethod(process.stdout, 'write'); + + await CommandTestFactory.run(commandInstance, ['-h']); + expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); + expect(stdoutSpy.firstCall?.args.length).toBe(1); + }); +}); From 686c097c65dcd0f54f5b50ee2aff4376ca3cf7e8 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sat, 8 Jun 2024 00:31:15 +0700 Subject: [PATCH 08/23] feat: add cortex h --- .../src/infrastructure/commanders/test/help.command.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts index d4585cabb..995747fe2 100644 --- a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts @@ -28,11 +28,9 @@ beforeEach( describe('Help command return guideline to users ', () => { test('should return guideline', async () => { - const stdoutSpy = stubMethod(process.stdout, 'write'); - await CommandTestFactory.run(commandInstance, ['-h']); expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); - expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); expect(stdoutSpy.firstCall?.args.length).toBe(1); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); }); }); From b8ba7bbffa84d13cd2bbfbdfec45312b81182755 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sat, 8 Jun 2024 10:34:12 +0700 Subject: [PATCH 09/23] feat: add log service --- .../src/infrastructure/commanders/test/log.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cortex-js/src/infrastructure/commanders/test/log.service.ts diff --git a/cortex-js/src/infrastructure/commanders/test/log.service.ts b/cortex-js/src/infrastructure/commanders/test/log.service.ts new file mode 100644 index 000000000..1151f5fb5 --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/log.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class LogService { + log(...args: any[]): void { + console.log(...args); + } +} From e93671685562117cf70de3da20e4b012faec5426 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Mon, 10 Jun 2024 21:10:34 +0700 Subject: [PATCH 10/23] feat: local API server test --- .../commanders/serve.command.ts | 24 ++++++++++++------- .../test/model-list.command.spec.ts | 20 ++++++++++------ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/serve.command.ts b/cortex-js/src/infrastructure/commanders/serve.command.ts index f839b4a0b..99575bcb2 100644 --- a/cortex-js/src/infrastructure/commanders/serve.command.ts +++ b/cortex-js/src/infrastructure/commanders/serve.command.ts @@ -20,16 +20,22 @@ export class ServeCommand extends CommandRunner { const host = options?.host || defaultCortexJsHost; const port = options?.port || defaultCortexJsPort; - spawn('node', [join(__dirname, '../../main.js')], { - env: { - ...process.env, - CORTEX_JS_HOST: host, - CORTEX_JS_PORT: port.toString(), - NODE_ENV: 'production', + spawn( + 'node', + process.env.TEST + ? [join(__dirname, '../../../dist/src/main.js')] + : [join(__dirname, '../../main.js')], + { + env: { + ...process.env, + CORTEX_JS_HOST: host, + CORTEX_JS_PORT: port.toString(), + NODE_ENV: 'production', + }, + stdio: 'inherit', + detached: false, }, - stdio: 'inherit', - detached: false, - }); + ); } @Option({ diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index e6dd60b9a..01e0a15fe 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -6,6 +6,7 @@ import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; import * as process from 'node:process'; +import axios from 'axios'; let commandInstance: TestingModule; @@ -41,6 +42,7 @@ afterEach( }), ); +const llama3 = 'llama3'; describe('models list returns array of models', () => { test('empty model list', async () => { const logMock = stubMethod(console, 'table'); @@ -69,21 +71,25 @@ describe('models list returns array of models', () => { test('pull existing model', async () => { const logMock = stubMethod(console, 'error'); - await CommandTestFactory.run(commandInstance, ['pull', 'test']); + await CommandTestFactory.run(commandInstance, ['pull', llama3]); expect(logMock.firstCall?.args[0]).toBe('Model already exists'); }); test('run model', async () => { const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['run', 'llama3']); + await CommandTestFactory.run(commandInstance, ['run', llama3]); expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); + + const tableMock = stubMethod(console, 'table'); + await CommandTestFactory.run(commandInstance, ['ps']); + expect(tableMock.firstCall?.args[0].length).toBeGreaterThan(0); }); test('get model', async () => { const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); + await CommandTestFactory.run(commandInstance, ['models', 'get', llama3]); expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); expect(logMock.firstCall?.args[0].files.length).toBe(1); }); @@ -96,10 +102,10 @@ describe('models list returns array of models', () => { }); test('local API server via localhost:1337/api', async () => { - const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['serve']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); + + // Send a request to the API server to check if it's running + const response = await axios.get('http://localhost:1337/api'); + expect(response.status).toBe(200); }); }); From 489b396fefee99d347558d520c28474188dcef16 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Tue, 11 Jun 2024 17:45:21 +0700 Subject: [PATCH 11/23] feat: cortex init test --- .../commanders/test/help.command.spec.ts | 36 ------ .../commanders/test/helpers.command.spec.ts | 114 ++++++++++++++++++ ...command.spec.ts => models.command.spec.ts} | 47 +++----- 3 files changed, 129 insertions(+), 68 deletions(-) delete mode 100644 cortex-js/src/infrastructure/commanders/test/help.command.spec.ts create mode 100644 cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts rename cortex-js/src/infrastructure/commanders/test/{model-list.command.spec.ts => models.command.spec.ts} (65%) diff --git a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts deleted file mode 100644 index 995747fe2..000000000 --- a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { TestingModule } from '@nestjs/testing'; -import { spy, Stub, stubMethod } from 'hanbi'; -import { CommandTestFactory } from 'nest-commander-testing'; -import { CommandModule } from '@/command.module'; -import { LogService } from '@/infrastructure/commanders/test/log.service'; - -let commandInstance: TestingModule, - exitSpy: Stub, - stdoutSpy: Stub; - -beforeEach( - () => - new Promise(async (res) => { - stubMethod(process.stderr, 'write'); - exitSpy = stubMethod(process, 'exit'); - stdoutSpy = stubMethod(process.stdout, 'write'); - commandInstance = await CommandTestFactory.createTestingCommand({ - imports: [CommandModule], - }) - .overrideProvider(LogService) - .useValue({ log: spy().handler }) - .compile(); - res(); - exitSpy.reset(); - stdoutSpy.reset(); - }), -); - -describe('Help command return guideline to users ', () => { - test('should return guideline', async () => { - await CommandTestFactory.run(commandInstance, ['-h']); - expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); - expect(stdoutSpy.firstCall?.args.length).toBe(1); - expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); - }); -}); diff --git a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts new file mode 100644 index 000000000..4e7b1d170 --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts @@ -0,0 +1,114 @@ +import { TestingModule } from '@nestjs/testing'; +import { spy, Stub, stubMethod } from 'hanbi'; +import { CommandTestFactory } from 'nest-commander-testing'; +import { CommandModule } from '@/command.module'; +import { LogService } from '@/infrastructure/commanders/test/log.service'; +import axios from 'axios'; + +let commandInstance: TestingModule, + exitSpy: Stub, + stdoutSpy: Stub, + stderrSpy: Stub; +export const timeout = 500000; + +beforeEach( + () => + new Promise(async (res) => { + stubMethod(process.stderr, 'write'); + exitSpy = stubMethod(process, 'exit'); + stdoutSpy = stubMethod(process.stdout, 'write'); + stderrSpy = stubMethod(process.stderr, 'write'); + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + .overrideProvider(LogService) + .useValue({ log: spy().handler }) + .compile(); + res(); + stdoutSpy.reset(); + stderrSpy.reset(); + }), +); + +describe('Helper commands', () => { + test('Help command return guideline to users ', async () => { + await CommandTestFactory.run(commandInstance, ['-h']); + expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); + expect(stdoutSpy.firstCall?.args.length).toBe(1); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); + + expect(exitSpy.callCount).toBeGreaterThan(1); + expect(exitSpy.firstCall?.args[0]).toBe(0); + }); + + test('Should handle missing command', async () => { + await CommandTestFactory.run(commandInstance, ['--unknown']); + expect(stderrSpy.firstCall?.args[0]).toContain('error: unknown option'); + expect(stderrSpy.firstCall?.args[0]).toContain('--unknown'); + expect(exitSpy.callCount).toBe(1); + expect(exitSpy.firstCall?.args[0]).toBe(1); + }); + + test('Chat with option -m', async () => { + const logMock = stubMethod(console, 'log'); + + await CommandTestFactory.run(commandInstance, [ + 'chat', + // '-m', + // 'hello', + // '>output.txt', + ]); + expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); + // expect(exitSpy.callCount).toBe(1); + // expect(exitSpy.firstCall?.args[0]).toBe(1); + }); + + test( + 'Model already exists', + async () => { + await CommandTestFactory.run(commandInstance, ['pull', 'llama3']); + expect(stdoutSpy.firstCall?.args[0]).toContain('Model already exists'); + expect(exitSpy.firstCall?.args[0]).toBe(1); + }, + timeout, + ); + + test('Show / kill running models', async () => { + const tableMock = stubMethod(console, 'table'); + + const logMock = stubMethod(console, 'log'); + await CommandTestFactory.run(commandInstance, ['kill']); + await CommandTestFactory.run(commandInstance, ['ps']); + + expect(logMock.firstCall?.args[0]).toEqual({ + message: 'Cortex stopped successfully', + status: 'success', + }); + expect(tableMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(tableMock.firstCall?.args[0].length).toEqual(0); + }); + + test( + 'Init with hardware auto detection', + async () => { + await CommandTestFactory.run(commandInstance, ['init', '-s']); + + expect(stdoutSpy.firstCall?.args.length).toBeGreaterThan(0); + }, + timeout, + ); + + test('Local API server via localhost:1337/api', async () => { + await CommandTestFactory.run(commandInstance, ['serve']); + + // Add a delay of 1000 milliseconds (1 second) + return new Promise(async (resolve) => { + setTimeout(async () => { + // Send a request to the API server to check if it's running + const response = await axios.get('http://localhost:1337/api'); + expect(response.status).toBe(200); + resolve(); + }, 1000); + }); + }); +}); diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts similarity index 65% rename from cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts rename to cortex-js/src/infrastructure/commanders/test/models.command.spec.ts index 01e0a15fe..3e9b5ea39 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts @@ -5,8 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; -import * as process from 'node:process'; -import axios from 'axios'; +import { timeout } from '@/infrastructure/commanders/test/helpers.command.spec'; let commandInstance: TestingModule; @@ -68,23 +67,22 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); - test('pull existing model', async () => { - const logMock = stubMethod(console, 'error'); + test( + 'run model', + async () => { + const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['pull', llama3]); - expect(logMock.firstCall?.args[0]).toBe('Model already exists'); - }); - - test('run model', async () => { - const logMock = stubMethod(console, 'log'); + await CommandTestFactory.run(commandInstance, ['run', llama3]); + expect(logMock.firstCall?.args[0]).toBe( + "Inorder to exit, type 'exit()'.", + ); - await CommandTestFactory.run(commandInstance, ['run', llama3]); - expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); - - const tableMock = stubMethod(console, 'table'); - await CommandTestFactory.run(commandInstance, ['ps']); - expect(tableMock.firstCall?.args[0].length).toBeGreaterThan(0); - }); + const tableMock = stubMethod(console, 'table'); + await CommandTestFactory.run(commandInstance, ['ps']); + expect(tableMock.firstCall?.args[0].length).toBeGreaterThan(0); + }, + timeout, + ); test('get model', async () => { const logMock = stubMethod(console, 'log'); @@ -93,19 +91,4 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); expect(logMock.firstCall?.args[0].files.length).toBe(1); }); - - test('hello world', async () => { - const logMock = stubMethod(process.stdout, 'write'); - - await CommandTestFactory.run(commandInstance, ['chat', '-m', 'hello']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - }); - - test('local API server via localhost:1337/api', async () => { - await CommandTestFactory.run(commandInstance, ['serve']); - - // Send a request to the API server to check if it's running - const response = await axios.get('http://localhost:1337/api'); - expect(response.status).toBe(200); - }); }); From 8af9ebbc530f3610ffc3337bf909f1ca95a6b187 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Thu, 6 Jun 2024 19:47:16 +0700 Subject: [PATCH 12/23] feat: add models test for CLI --- cortex-js/.eslintrc.js | 6 ++++ .../test/model-list.command.spec.ts | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/cortex-js/.eslintrc.js b/cortex-js/.eslintrc.js index 259de13c7..448a2d910 100644 --- a/cortex-js/.eslintrc.js +++ b/cortex-js/.eslintrc.js @@ -21,5 +21,11 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-explicit-any': 'off', + "prettier/prettier": [ + "error", + { + "endOfLine": "auto" + }, + ], }, }; diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index b90b26006..da5097c3b 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,6 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; +import { stdout } from 'node:process'; let commandInstance: TestingModule; @@ -64,4 +65,32 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0].length).toBe(1); expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); + + test('run model', async () => { + const logMock = stubMethod(console, 'table'); + + await CommandTestFactory.run(commandInstance, ['run', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('get model', async () => { + const logMock = stubMethod(console, 'log'); + + await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('hello world', async () => { + const logMock = stubMethod(stdout, 'write'); + + await CommandTestFactory.run(commandInstance, [ + 'chat', + '-m', + 'hello world', + ]); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); }); From 23106c1e63d1cfdc3bcbf3983669200e408b3688 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 6 Jun 2024 16:25:47 +0700 Subject: [PATCH 13/23] refactor: add models list test cases --- .../test/model-list.command.spec.ts | 83 ++++++------------- 1 file changed, 24 insertions(+), 59 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index da5097c3b..4013a7bfe 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,41 +5,34 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; -import { stdout } from 'node:process'; let commandInstance: TestingModule; -beforeEach( - () => - new Promise(async (res) => { - commandInstance = await CommandTestFactory.createTestingCommand({ - imports: [CommandModule], - }) - // .overrideProvider(LogService) - // .useValue({}) - .compile(); - const fileService = - await commandInstance.resolve(FileManagerService); - - // Attempt to create test folder - await fileService.writeConfigFile({ - dataFolderPath: join(__dirname, 'test_data'), - }); - res(); - }), -); +beforeEach(async () => { + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + // .overrideProvider(LogService) + // .useValue({}) + .compile(); + const fileService = + commandInstance.resolve(FileManagerService); + + // Attempt to create test folder + (await fileService).writeConfigFile({ + dataFolderPath: join(__dirname, 'test_data'), + }); +}); -afterEach( - () => - new Promise(async (res) => { - // Attempt to clean test folder - rmSync(join(__dirname, 'test_data'), { - recursive: true, - force: true, - }); - res(); - }), -); +afterEach(async () => { + // Attempt to clean test folder + try { + await rmSync(join(__dirname, 'test_data'), { + recursive: true, + force: true, + }); + } catch (e) {} +}); describe('models list returns array of models', () => { test('empty model list', async () => { @@ -65,32 +58,4 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0].length).toBe(1); expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); - - test('run model', async () => { - const logMock = stubMethod(console, 'table'); - - await CommandTestFactory.run(commandInstance, ['run', 'llama3']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); - }); - - test('get model', async () => { - const logMock = stubMethod(console, 'log'); - - await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); - }); - - test('hello world', async () => { - const logMock = stubMethod(stdout, 'write'); - - await CommandTestFactory.run(commandInstance, [ - 'chat', - '-m', - 'hello world', - ]); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); - }); }); From 248858cf929ec6d9cef1c2e182a1210a7e5219f2 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 6 Jun 2024 17:54:18 +0700 Subject: [PATCH 14/23] fix: async test preparation --- .../test/model-list.command.spec.ts | 54 ++++++++++--------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index 4013a7bfe..b90b26006 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -8,31 +8,37 @@ import { mkdirSync, rmSync, writeFileSync } from 'fs'; let commandInstance: TestingModule; -beforeEach(async () => { - commandInstance = await CommandTestFactory.createTestingCommand({ - imports: [CommandModule], - }) - // .overrideProvider(LogService) - // .useValue({}) - .compile(); - const fileService = - commandInstance.resolve(FileManagerService); - - // Attempt to create test folder - (await fileService).writeConfigFile({ - dataFolderPath: join(__dirname, 'test_data'), - }); -}); +beforeEach( + () => + new Promise(async (res) => { + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + // .overrideProvider(LogService) + // .useValue({}) + .compile(); + const fileService = + await commandInstance.resolve(FileManagerService); -afterEach(async () => { - // Attempt to clean test folder - try { - await rmSync(join(__dirname, 'test_data'), { - recursive: true, - force: true, - }); - } catch (e) {} -}); + // Attempt to create test folder + await fileService.writeConfigFile({ + dataFolderPath: join(__dirname, 'test_data'), + }); + res(); + }), +); + +afterEach( + () => + new Promise(async (res) => { + // Attempt to clean test folder + rmSync(join(__dirname, 'test_data'), { + recursive: true, + force: true, + }); + res(); + }), +); describe('models list returns array of models', () => { test('empty model list', async () => { From c329db1558a2899302e483ea2fa123cfdde56888 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Thu, 6 Jun 2024 19:47:16 +0700 Subject: [PATCH 15/23] feat: add models test for CLI --- .../test/model-list.command.spec.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index b90b26006..da5097c3b 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,6 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; +import { stdout } from 'node:process'; let commandInstance: TestingModule; @@ -64,4 +65,32 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0].length).toBe(1); expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); + + test('run model', async () => { + const logMock = stubMethod(console, 'table'); + + await CommandTestFactory.run(commandInstance, ['run', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('get model', async () => { + const logMock = stubMethod(console, 'log'); + + await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); + + test('hello world', async () => { + const logMock = stubMethod(stdout, 'write'); + + await CommandTestFactory.run(commandInstance, [ + 'chat', + '-m', + 'hello world', + ]); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(0); + }); }); From ed67bcc62c9c4f7e1d2cf3a4d4a2c100fd9b4ed1 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Fri, 7 Jun 2024 14:45:50 +0700 Subject: [PATCH 16/23] feat: add cortex chat --- .../test/model-list.command.spec.ts | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index da5097c3b..e6dd60b9a 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -5,7 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; -import { stdout } from 'node:process'; +import * as process from 'node:process'; let commandInstance: TestingModule; @@ -66,30 +66,39 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); + test('pull existing model', async () => { + const logMock = stubMethod(console, 'error'); + + await CommandTestFactory.run(commandInstance, ['pull', 'test']); + expect(logMock.firstCall?.args[0]).toBe('Model already exists'); + }); + test('run model', async () => { - const logMock = stubMethod(console, 'table'); + const logMock = stubMethod(console, 'log'); await CommandTestFactory.run(commandInstance, ['run', 'llama3']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); + expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); }); test('get model', async () => { const logMock = stubMethod(console, 'log'); await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); + expect(logMock.firstCall?.args[0].files.length).toBe(1); }); test('hello world', async () => { - const logMock = stubMethod(stdout, 'write'); + const logMock = stubMethod(process.stdout, 'write'); + + await CommandTestFactory.run(commandInstance, ['chat', '-m', 'hello']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + }); + + test('local API server via localhost:1337/api', async () => { + const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, [ - 'chat', - '-m', - 'hello world', - ]); + await CommandTestFactory.run(commandInstance, ['serve']); expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); expect(logMock.firstCall?.args[0].length).toBe(0); }); From 0846397a036653197002a42b76e22f5ae5ede651 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sat, 8 Jun 2024 00:29:10 +0700 Subject: [PATCH 17/23] feat: add cortex h --- .../commanders/test/help.command.spec.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 cortex-js/src/infrastructure/commanders/test/help.command.spec.ts diff --git a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts new file mode 100644 index 000000000..d4585cabb --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts @@ -0,0 +1,38 @@ +import { TestingModule } from '@nestjs/testing'; +import { spy, Stub, stubMethod } from 'hanbi'; +import { CommandTestFactory } from 'nest-commander-testing'; +import { CommandModule } from '@/command.module'; +import { LogService } from '@/infrastructure/commanders/test/log.service'; + +let commandInstance: TestingModule, + exitSpy: Stub, + stdoutSpy: Stub; + +beforeEach( + () => + new Promise(async (res) => { + stubMethod(process.stderr, 'write'); + exitSpy = stubMethod(process, 'exit'); + stdoutSpy = stubMethod(process.stdout, 'write'); + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + .overrideProvider(LogService) + .useValue({ log: spy().handler }) + .compile(); + res(); + exitSpy.reset(); + stdoutSpy.reset(); + }), +); + +describe('Help command return guideline to users ', () => { + test('should return guideline', async () => { + const stdoutSpy = stubMethod(process.stdout, 'write'); + + await CommandTestFactory.run(commandInstance, ['-h']); + expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); + expect(stdoutSpy.firstCall?.args.length).toBe(1); + }); +}); From 54f9a9cce9f64c61962d25ad58d7fcd6c11b734e Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sat, 8 Jun 2024 00:31:15 +0700 Subject: [PATCH 18/23] feat: add cortex h --- .../src/infrastructure/commanders/test/help.command.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts index d4585cabb..995747fe2 100644 --- a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts @@ -28,11 +28,9 @@ beforeEach( describe('Help command return guideline to users ', () => { test('should return guideline', async () => { - const stdoutSpy = stubMethod(process.stdout, 'write'); - await CommandTestFactory.run(commandInstance, ['-h']); expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); - expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); expect(stdoutSpy.firstCall?.args.length).toBe(1); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); }); }); From 279b256c3262f164f896cc867ff95bb6e567011b Mon Sep 17 00:00:00 2001 From: Van-QA Date: Sat, 8 Jun 2024 10:34:12 +0700 Subject: [PATCH 19/23] feat: add log service --- .../src/infrastructure/commanders/test/log.service.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cortex-js/src/infrastructure/commanders/test/log.service.ts diff --git a/cortex-js/src/infrastructure/commanders/test/log.service.ts b/cortex-js/src/infrastructure/commanders/test/log.service.ts new file mode 100644 index 000000000..1151f5fb5 --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/log.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class LogService { + log(...args: any[]): void { + console.log(...args); + } +} From 01e320c67ecbeb0f190f2ccfe26a001bbeb2a0d9 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Mon, 10 Jun 2024 21:10:34 +0700 Subject: [PATCH 20/23] feat: local API server test --- .../commanders/serve.command.ts | 24 ++++++++++++------- .../test/model-list.command.spec.ts | 20 ++++++++++------ 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/serve.command.ts b/cortex-js/src/infrastructure/commanders/serve.command.ts index f839b4a0b..99575bcb2 100644 --- a/cortex-js/src/infrastructure/commanders/serve.command.ts +++ b/cortex-js/src/infrastructure/commanders/serve.command.ts @@ -20,16 +20,22 @@ export class ServeCommand extends CommandRunner { const host = options?.host || defaultCortexJsHost; const port = options?.port || defaultCortexJsPort; - spawn('node', [join(__dirname, '../../main.js')], { - env: { - ...process.env, - CORTEX_JS_HOST: host, - CORTEX_JS_PORT: port.toString(), - NODE_ENV: 'production', + spawn( + 'node', + process.env.TEST + ? [join(__dirname, '../../../dist/src/main.js')] + : [join(__dirname, '../../main.js')], + { + env: { + ...process.env, + CORTEX_JS_HOST: host, + CORTEX_JS_PORT: port.toString(), + NODE_ENV: 'production', + }, + stdio: 'inherit', + detached: false, }, - stdio: 'inherit', - detached: false, - }); + ); } @Option({ diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts index e6dd60b9a..01e0a15fe 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts @@ -6,6 +6,7 @@ import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; import * as process from 'node:process'; +import axios from 'axios'; let commandInstance: TestingModule; @@ -41,6 +42,7 @@ afterEach( }), ); +const llama3 = 'llama3'; describe('models list returns array of models', () => { test('empty model list', async () => { const logMock = stubMethod(console, 'table'); @@ -69,21 +71,25 @@ describe('models list returns array of models', () => { test('pull existing model', async () => { const logMock = stubMethod(console, 'error'); - await CommandTestFactory.run(commandInstance, ['pull', 'test']); + await CommandTestFactory.run(commandInstance, ['pull', llama3]); expect(logMock.firstCall?.args[0]).toBe('Model already exists'); }); test('run model', async () => { const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['run', 'llama3']); + await CommandTestFactory.run(commandInstance, ['run', llama3]); expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); + + const tableMock = stubMethod(console, 'table'); + await CommandTestFactory.run(commandInstance, ['ps']); + expect(tableMock.firstCall?.args[0].length).toBeGreaterThan(0); }); test('get model', async () => { const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['models', 'get', 'llama3']); + await CommandTestFactory.run(commandInstance, ['models', 'get', llama3]); expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); expect(logMock.firstCall?.args[0].files.length).toBe(1); }); @@ -96,10 +102,10 @@ describe('models list returns array of models', () => { }); test('local API server via localhost:1337/api', async () => { - const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['serve']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); + + // Send a request to the API server to check if it's running + const response = await axios.get('http://localhost:1337/api'); + expect(response.status).toBe(200); }); }); From b1da92d42439f7978b3a5a90fd08d780f65410a1 Mon Sep 17 00:00:00 2001 From: Van-QA Date: Tue, 11 Jun 2024 17:45:21 +0700 Subject: [PATCH 21/23] feat: cortex init test --- .../commanders/test/help.command.spec.ts | 36 ------ .../commanders/test/helpers.command.spec.ts | 114 ++++++++++++++++++ ...command.spec.ts => models.command.spec.ts} | 47 +++----- 3 files changed, 129 insertions(+), 68 deletions(-) delete mode 100644 cortex-js/src/infrastructure/commanders/test/help.command.spec.ts create mode 100644 cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts rename cortex-js/src/infrastructure/commanders/test/{model-list.command.spec.ts => models.command.spec.ts} (65%) diff --git a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts deleted file mode 100644 index 995747fe2..000000000 --- a/cortex-js/src/infrastructure/commanders/test/help.command.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { TestingModule } from '@nestjs/testing'; -import { spy, Stub, stubMethod } from 'hanbi'; -import { CommandTestFactory } from 'nest-commander-testing'; -import { CommandModule } from '@/command.module'; -import { LogService } from '@/infrastructure/commanders/test/log.service'; - -let commandInstance: TestingModule, - exitSpy: Stub, - stdoutSpy: Stub; - -beforeEach( - () => - new Promise(async (res) => { - stubMethod(process.stderr, 'write'); - exitSpy = stubMethod(process, 'exit'); - stdoutSpy = stubMethod(process.stdout, 'write'); - commandInstance = await CommandTestFactory.createTestingCommand({ - imports: [CommandModule], - }) - .overrideProvider(LogService) - .useValue({ log: spy().handler }) - .compile(); - res(); - exitSpy.reset(); - stdoutSpy.reset(); - }), -); - -describe('Help command return guideline to users ', () => { - test('should return guideline', async () => { - await CommandTestFactory.run(commandInstance, ['-h']); - expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); - expect(stdoutSpy.firstCall?.args.length).toBe(1); - expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); - }); -}); diff --git a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts new file mode 100644 index 000000000..4e7b1d170 --- /dev/null +++ b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts @@ -0,0 +1,114 @@ +import { TestingModule } from '@nestjs/testing'; +import { spy, Stub, stubMethod } from 'hanbi'; +import { CommandTestFactory } from 'nest-commander-testing'; +import { CommandModule } from '@/command.module'; +import { LogService } from '@/infrastructure/commanders/test/log.service'; +import axios from 'axios'; + +let commandInstance: TestingModule, + exitSpy: Stub, + stdoutSpy: Stub, + stderrSpy: Stub; +export const timeout = 500000; + +beforeEach( + () => + new Promise(async (res) => { + stubMethod(process.stderr, 'write'); + exitSpy = stubMethod(process, 'exit'); + stdoutSpy = stubMethod(process.stdout, 'write'); + stderrSpy = stubMethod(process.stderr, 'write'); + commandInstance = await CommandTestFactory.createTestingCommand({ + imports: [CommandModule], + }) + .overrideProvider(LogService) + .useValue({ log: spy().handler }) + .compile(); + res(); + stdoutSpy.reset(); + stderrSpy.reset(); + }), +); + +describe('Helper commands', () => { + test('Help command return guideline to users ', async () => { + await CommandTestFactory.run(commandInstance, ['-h']); + expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); + expect(stdoutSpy.firstCall?.args.length).toBe(1); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); + + expect(exitSpy.callCount).toBeGreaterThan(1); + expect(exitSpy.firstCall?.args[0]).toBe(0); + }); + + test('Should handle missing command', async () => { + await CommandTestFactory.run(commandInstance, ['--unknown']); + expect(stderrSpy.firstCall?.args[0]).toContain('error: unknown option'); + expect(stderrSpy.firstCall?.args[0]).toContain('--unknown'); + expect(exitSpy.callCount).toBe(1); + expect(exitSpy.firstCall?.args[0]).toBe(1); + }); + + test('Chat with option -m', async () => { + const logMock = stubMethod(console, 'log'); + + await CommandTestFactory.run(commandInstance, [ + 'chat', + // '-m', + // 'hello', + // '>output.txt', + ]); + expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); + // expect(exitSpy.callCount).toBe(1); + // expect(exitSpy.firstCall?.args[0]).toBe(1); + }); + + test( + 'Model already exists', + async () => { + await CommandTestFactory.run(commandInstance, ['pull', 'llama3']); + expect(stdoutSpy.firstCall?.args[0]).toContain('Model already exists'); + expect(exitSpy.firstCall?.args[0]).toBe(1); + }, + timeout, + ); + + test('Show / kill running models', async () => { + const tableMock = stubMethod(console, 'table'); + + const logMock = stubMethod(console, 'log'); + await CommandTestFactory.run(commandInstance, ['kill']); + await CommandTestFactory.run(commandInstance, ['ps']); + + expect(logMock.firstCall?.args[0]).toEqual({ + message: 'Cortex stopped successfully', + status: 'success', + }); + expect(tableMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(tableMock.firstCall?.args[0].length).toEqual(0); + }); + + test( + 'Init with hardware auto detection', + async () => { + await CommandTestFactory.run(commandInstance, ['init', '-s']); + + expect(stdoutSpy.firstCall?.args.length).toBeGreaterThan(0); + }, + timeout, + ); + + test('Local API server via localhost:1337/api', async () => { + await CommandTestFactory.run(commandInstance, ['serve']); + + // Add a delay of 1000 milliseconds (1 second) + return new Promise(async (resolve) => { + setTimeout(async () => { + // Send a request to the API server to check if it's running + const response = await axios.get('http://localhost:1337/api'); + expect(response.status).toBe(200); + resolve(); + }, 1000); + }); + }); +}); diff --git a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts similarity index 65% rename from cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts rename to cortex-js/src/infrastructure/commanders/test/models.command.spec.ts index 01e0a15fe..3e9b5ea39 100644 --- a/cortex-js/src/infrastructure/commanders/test/model-list.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts @@ -5,8 +5,7 @@ import { CommandModule } from '@/command.module'; import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; import { mkdirSync, rmSync, writeFileSync } from 'fs'; -import * as process from 'node:process'; -import axios from 'axios'; +import { timeout } from '@/infrastructure/commanders/test/helpers.command.spec'; let commandInstance: TestingModule; @@ -68,23 +67,22 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0][0].id).toBe('test'); }); - test('pull existing model', async () => { - const logMock = stubMethod(console, 'error'); + test( + 'run model', + async () => { + const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['pull', llama3]); - expect(logMock.firstCall?.args[0]).toBe('Model already exists'); - }); - - test('run model', async () => { - const logMock = stubMethod(console, 'log'); + await CommandTestFactory.run(commandInstance, ['run', llama3]); + expect(logMock.firstCall?.args[0]).toBe( + "Inorder to exit, type 'exit()'.", + ); - await CommandTestFactory.run(commandInstance, ['run', llama3]); - expect(logMock.firstCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); - - const tableMock = stubMethod(console, 'table'); - await CommandTestFactory.run(commandInstance, ['ps']); - expect(tableMock.firstCall?.args[0].length).toBeGreaterThan(0); - }); + const tableMock = stubMethod(console, 'table'); + await CommandTestFactory.run(commandInstance, ['ps']); + expect(tableMock.firstCall?.args[0].length).toBeGreaterThan(0); + }, + timeout, + ); test('get model', async () => { const logMock = stubMethod(console, 'log'); @@ -93,19 +91,4 @@ describe('models list returns array of models', () => { expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); expect(logMock.firstCall?.args[0].files.length).toBe(1); }); - - test('hello world', async () => { - const logMock = stubMethod(process.stdout, 'write'); - - await CommandTestFactory.run(commandInstance, ['chat', '-m', 'hello']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - }); - - test('local API server via localhost:1337/api', async () => { - await CommandTestFactory.run(commandInstance, ['serve']); - - // Send a request to the API server to check if it's running - const response = await axios.get('http://localhost:1337/api'); - expect(response.status).toBe(200); - }); }); From c1d3709179714fec3a9e88549910219eac11fadd Mon Sep 17 00:00:00 2001 From: Van-QA Date: Tue, 11 Jun 2024 18:05:24 +0700 Subject: [PATCH 22/23] feat: run all --- .../commanders/test/helpers.command.spec.ts | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts index 4e7b1d170..91784bd95 100644 --- a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts @@ -31,24 +31,6 @@ beforeEach( ); describe('Helper commands', () => { - test('Help command return guideline to users ', async () => { - await CommandTestFactory.run(commandInstance, ['-h']); - expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); - expect(stdoutSpy.firstCall?.args.length).toBe(1); - expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); - - expect(exitSpy.callCount).toBeGreaterThan(1); - expect(exitSpy.firstCall?.args[0]).toBe(0); - }); - - test('Should handle missing command', async () => { - await CommandTestFactory.run(commandInstance, ['--unknown']); - expect(stderrSpy.firstCall?.args[0]).toContain('error: unknown option'); - expect(stderrSpy.firstCall?.args[0]).toContain('--unknown'); - expect(exitSpy.callCount).toBe(1); - expect(exitSpy.firstCall?.args[0]).toBe(1); - }); - test('Chat with option -m', async () => { const logMock = stubMethod(console, 'log'); @@ -88,11 +70,32 @@ describe('Helper commands', () => { expect(tableMock.firstCall?.args[0].length).toEqual(0); }); + test('Help command return guideline to users ', async () => { + await CommandTestFactory.run(commandInstance, ['-h']); + expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); + expect(stdoutSpy.firstCall?.args.length).toBe(1); + expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); + + expect(exitSpy.callCount).toBeGreaterThan(1); + expect(exitSpy.firstCall?.args[0]).toBe(0); + }); + + test('Should handle missing command', async () => { + await CommandTestFactory.run(commandInstance, ['--unknown']); + expect(stderrSpy.firstCall?.args[0]).toContain('error: unknown option'); + expect(stderrSpy.firstCall?.args[0]).toContain('--unknown'); + expect(exitSpy.callCount).toBe(1); + expect(exitSpy.firstCall?.args[0]).toBe(1); + }); + test( 'Init with hardware auto detection', async () => { await CommandTestFactory.run(commandInstance, ['init', '-s']); + // Wait for a brief period to allow the command to execute + await new Promise((resolve) => setTimeout(resolve, 1000)); + expect(stdoutSpy.firstCall?.args.length).toBeGreaterThan(0); }, timeout, From a30ae2611ae28faa44c536eeccb18dee4619713c Mon Sep 17 00:00:00 2001 From: Van-QA Date: Wed, 12 Jun 2024 15:36:53 +0700 Subject: [PATCH 23/23] feat: rearrange test case --- .../commanders/test/helpers.command.spec.ts | 50 +++---------- .../commanders/test/models.command.spec.ts | 74 ++++++++++--------- 2 files changed, 51 insertions(+), 73 deletions(-) diff --git a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts index 9fcec0217..05d9562d6 100644 --- a/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/helpers.command.spec.ts @@ -31,23 +31,18 @@ beforeEach( ); describe('Helper commands', () => { - test('Help command return guideline to users ', async () => { - await CommandTestFactory.run(commandInstance, ['-h']); - expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); - expect(stdoutSpy.firstCall?.args.length).toBe(1); - expect(stdoutSpy.firstCall?.args[0]).toContain('display help for command'); + test( + 'Init with hardware auto detection', + async () => { + await CommandTestFactory.run(commandInstance, ['init', '-s']); - expect(exitSpy.callCount).toBeGreaterThan(1); - expect(exitSpy.firstCall?.args[0]).toBe(0); - }); + // Wait for a brief period to allow the command to execute + await new Promise((resolve) => setTimeout(resolve, 1000)); - test('Should handle missing command', async () => { - await CommandTestFactory.run(commandInstance, ['--unknown']); - expect(stderrSpy.firstCall?.args[0]).toContain('error: unknown option'); - expect(stderrSpy.firstCall?.args[0]).toContain('--unknown'); - expect(exitSpy.callCount).toBe(1); - expect(exitSpy.firstCall?.args[0]).toBe(1); - }); + expect(stdoutSpy.firstCall?.args.length).toBeGreaterThan(0); + }, + timeout, + ); test('Chat with option -m', async () => { const logMock = stubMethod(console, 'log'); @@ -63,16 +58,6 @@ describe('Helper commands', () => { // expect(exitSpy.firstCall?.args[0]).toBe(1); }); - test( - 'Model already exists', - async () => { - await CommandTestFactory.run(commandInstance, ['pull', 'llama3']); - expect(stdoutSpy.firstCall?.args[0]).toContain('Model already exists'); - expect(exitSpy.firstCall?.args[0]).toBe(1); - }, - timeout, - ); - test('Show / kill running models', async () => { const tableMock = stubMethod(console, 'table'); @@ -88,7 +73,7 @@ describe('Helper commands', () => { expect(tableMock.firstCall?.args[0].length).toEqual(0); }); - test('Help command return guideline to users ', async () => { + test('Help command return guideline to users', async () => { await CommandTestFactory.run(commandInstance, ['-h']); expect(stdoutSpy.firstCall?.args).toBeInstanceOf(Array); expect(stdoutSpy.firstCall?.args.length).toBe(1); @@ -106,19 +91,6 @@ describe('Helper commands', () => { expect(exitSpy.firstCall?.args[0]).toBe(1); }); - test( - 'Init with hardware auto detection', - async () => { - await CommandTestFactory.run(commandInstance, ['init', '-s']); - - // Wait for a brief period to allow the command to execute - await new Promise((resolve) => setTimeout(resolve, 1000)); - - expect(stdoutSpy.firstCall?.args.length).toBeGreaterThan(0); - }, - timeout, - ); - test('Local API server via localhost:1337/api', async () => { await CommandTestFactory.run(commandInstance, ['serve']); diff --git a/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts index 3e9b5ea39..7d512d2be 100644 --- a/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts +++ b/cortex-js/src/infrastructure/commanders/test/models.command.spec.ts @@ -2,9 +2,8 @@ import { TestingModule } from '@nestjs/testing'; import { stubMethod } from 'hanbi'; import { CommandTestFactory } from 'nest-commander-testing'; import { CommandModule } from '@/command.module'; -import { FileManagerService } from '@/file-manager/file-manager.service'; import { join } from 'path'; -import { mkdirSync, rmSync, writeFileSync } from 'fs'; +import { rmSync } from 'fs'; import { timeout } from '@/infrastructure/commanders/test/helpers.command.spec'; let commandInstance: TestingModule; @@ -18,13 +17,6 @@ beforeEach( // .overrideProvider(LogService) // .useValue({}) .compile(); - const fileService = - await commandInstance.resolve(FileManagerService); - - // Attempt to create test folder - await fileService.writeConfigFile({ - dataFolderPath: join(__dirname, 'test_data'), - }); res(); }), ); @@ -41,41 +33,35 @@ afterEach( }), ); -const llama3 = 'llama3'; -describe('models list returns array of models', () => { - test('empty model list', async () => { - const logMock = stubMethod(console, 'table'); - - await CommandTestFactory.run(commandInstance, ['models', 'list']); - expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(0); - }); +export const modelName = 'tinyllama'; +describe('Models list returns array of models', () => { + test('Init with CPU', async () => { + const logMock = stubMethod(console, 'log'); - test('many models in the list', async () => { - const logMock = stubMethod(console, 'table'); + logMock.passThrough(); + CommandTestFactory.setAnswers(['CPU', '', 'AVX2']); - mkdirSync(join(__dirname, 'test_data', 'models'), { recursive: true }); - writeFileSync( - join(__dirname, 'test_data', 'models', 'test.yaml'), - 'model: test', - 'utf8', + await CommandTestFactory.run(commandInstance, ['init']); + expect(logMock.firstCall?.args[0]).toBe( + 'Downloading engine file windows-amd64-avx2.tar.gz', ); + }, 50000); + + test('Empty model list', async () => { + const logMock = stubMethod(console, 'table'); await CommandTestFactory.run(commandInstance, ['models', 'list']); expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); - expect(logMock.firstCall?.args[0].length).toBe(1); - expect(logMock.firstCall?.args[0][0].id).toBe('test'); + expect(logMock.firstCall?.args[0].length).toBe(0); }); test( - 'run model', + 'Run model and check with cortex ps', async () => { const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['run', llama3]); - expect(logMock.firstCall?.args[0]).toBe( - "Inorder to exit, type 'exit()'.", - ); + await CommandTestFactory.run(commandInstance, ['run', modelName]); + expect(logMock.lastCall?.args[0]).toBe("Inorder to exit, type 'exit()'."); const tableMock = stubMethod(console, 'table'); await CommandTestFactory.run(commandInstance, ['ps']); @@ -84,11 +70,31 @@ describe('models list returns array of models', () => { timeout, ); - test('get model', async () => { + test('Get model', async () => { const logMock = stubMethod(console, 'log'); - await CommandTestFactory.run(commandInstance, ['models', 'get', llama3]); + await CommandTestFactory.run(commandInstance, ['models', 'get', modelName]); expect(logMock.firstCall?.args[0]).toBeInstanceOf(Object); expect(logMock.firstCall?.args[0].files.length).toBe(1); }); + + test('Many models in the list', async () => { + const logMock = stubMethod(console, 'table'); + await CommandTestFactory.run(commandInstance, ['models', 'list']); + expect(logMock.firstCall?.args[0]).toBeInstanceOf(Array); + expect(logMock.firstCall?.args[0].length).toBe(1); + expect(logMock.firstCall?.args[0][0].id).toBe(modelName); + }); + + test( + 'Model already exists', + async () => { + const stdoutSpy = stubMethod(process.stdout, 'write'); + const exitSpy = stubMethod(process, 'exit'); + await CommandTestFactory.run(commandInstance, ['pull', modelName]); + expect(stdoutSpy.firstCall?.args[0]).toContain('Model already exists'); + expect(exitSpy.firstCall?.args[0]).toBe(1); + }, + timeout, + ); });