chore: bump cortex version (#4793)

This commit is contained in:
Louis 2025-03-11 13:42:42 +07:00 committed by GitHub
parent 2a2adf2223
commit f4f4d411aa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 626 additions and 3 deletions

View File

@ -0,0 +1,199 @@
import { describe, beforeEach, it, expect, vi } from 'vitest'
import JanEngineManagementExtension from './index'
import { InferenceEngine } from '@janhq/core'
describe('API methods', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
describe('getReleasedEnginesByVersion', () => {
it('should return engines filtered by platform if provided', async () => {
const mockEngines = [
{
name: 'windows-amd64-avx2',
version: '1.0.0',
},
{
name: 'linux-amd64-avx2',
version: '1.0.0',
},
]
vi.mock('ky', () => ({
default: {
get: () => ({
json: () => Promise.resolve(mockEngines),
}),
},
}))
const mock = vi.spyOn(extension, 'getReleasedEnginesByVersion')
mock.mockImplementation(async (name, version, platform) => {
const result = await Promise.resolve(mockEngines)
return platform ? result.filter(r => r.name.includes(platform)) : result
})
const result = await extension.getReleasedEnginesByVersion(
InferenceEngine.cortex_llamacpp,
'1.0.0',
'windows'
)
expect(result).toHaveLength(1)
expect(result[0].name).toBe('windows-amd64-avx2')
})
it('should return all engines if platform is not provided', async () => {
const mockEngines = [
{
name: 'windows-amd64-avx2',
version: '1.0.0',
},
{
name: 'linux-amd64-avx2',
version: '1.0.0',
},
]
vi.mock('ky', () => ({
default: {
get: () => ({
json: () => Promise.resolve(mockEngines),
}),
},
}))
const mock = vi.spyOn(extension, 'getReleasedEnginesByVersion')
mock.mockImplementation(async (name, version, platform) => {
const result = await Promise.resolve(mockEngines)
return platform ? result.filter(r => r.name.includes(platform)) : result
})
const result = await extension.getReleasedEnginesByVersion(
InferenceEngine.cortex_llamacpp,
'1.0.0'
)
expect(result).toHaveLength(2)
})
})
describe('getLatestReleasedEngine', () => {
it('should return engines filtered by platform if provided', async () => {
const mockEngines = [
{
name: 'windows-amd64-avx2',
version: '1.0.0',
},
{
name: 'linux-amd64-avx2',
version: '1.0.0',
},
]
vi.mock('ky', () => ({
default: {
get: () => ({
json: () => Promise.resolve(mockEngines),
}),
},
}))
const mock = vi.spyOn(extension, 'getLatestReleasedEngine')
mock.mockImplementation(async (name, platform) => {
const result = await Promise.resolve(mockEngines)
return platform ? result.filter(r => r.name.includes(platform)) : result
})
const result = await extension.getLatestReleasedEngine(
InferenceEngine.cortex_llamacpp,
'linux'
)
expect(result).toHaveLength(1)
expect(result[0].name).toBe('linux-amd64-avx2')
})
})
describe('installEngine', () => {
it('should send install request with correct parameters', async () => {
const mockEngineConfig = {
variant: 'windows-amd64-avx2',
version: '1.0.0',
}
vi.mock('ky', () => ({
default: {
post: (url, options) => {
expect(url).toBe(`${API_URL}/v1/engines/${InferenceEngine.cortex_llamacpp}/install`)
expect(options.json).toEqual(mockEngineConfig)
return Promise.resolve({ messages: 'OK' })
},
},
}))
const result = await extension.installEngine(
InferenceEngine.cortex_llamacpp,
mockEngineConfig
)
expect(result).toEqual({ messages: 'OK' })
})
})
describe('uninstallEngine', () => {
it('should send uninstall request with correct parameters', async () => {
const mockEngineConfig = {
variant: 'windows-amd64-avx2',
version: '1.0.0',
}
vi.mock('ky', () => ({
default: {
delete: (url, options) => {
expect(url).toBe(`${API_URL}/v1/engines/${InferenceEngine.cortex_llamacpp}/install`)
expect(options.json).toEqual(mockEngineConfig)
return Promise.resolve({ messages: 'OK' })
},
},
}))
const result = await extension.uninstallEngine(
InferenceEngine.cortex_llamacpp,
mockEngineConfig
)
expect(result).toEqual({ messages: 'OK' })
})
})
describe('addRemoteModel', () => {
it('should send add model request with correct parameters', async () => {
const mockModel = {
id: 'gpt-4',
name: 'GPT-4',
engine: InferenceEngine.openai,
}
vi.mock('ky', () => ({
default: {
post: (url, options) => {
expect(url).toBe(`${API_URL}/v1/models/add`)
expect(options.json).toHaveProperty('id', 'gpt-4')
expect(options.json).toHaveProperty('engine', InferenceEngine.openai)
expect(options.json).toHaveProperty('inference_params')
return Promise.resolve()
},
},
}))
await extension.addRemoteModel(mockModel)
// Success is implied by no thrown exceptions
})
})
})

View File

@ -0,0 +1,19 @@
import { describe, it, expect } from 'vitest'
import { EngineError } from './error'
describe('EngineError', () => {
it('should create an error with the correct message', () => {
const errorMessage = 'Test error message'
const error = new EngineError(errorMessage)
expect(error).toBeInstanceOf(Error)
expect(error.message).toBe(errorMessage)
expect(error.name).toBe('EngineError')
})
it('should create an error with default message if none provided', () => {
const error = new EngineError()
expect(error.message).toBe('Engine error occurred')
})
})

View File

@ -1,6 +1,8 @@
import { describe, beforeEach, it, expect, vi } from 'vitest'
import JanEngineManagementExtension from './index'
import { Engines, InferenceEngine } from '@janhq/core'
import { EngineError } from './error'
import { HTTPError } from 'ky'
vi.stubGlobal('API_URL', 'http://localhost:3000')
@ -13,7 +15,27 @@ const mockEngines: Engines = [
},
]
const mockRemoteEngines: Engines = [
{
name: 'openai',
version: '1.0.0',
type: 'remote',
engine: InferenceEngine.openai,
},
]
const mockRemoteModels = {
data: [
{
id: 'gpt-4',
name: 'GPT-4',
engine: InferenceEngine.openai,
},
],
}
vi.stubGlobal('DEFAULT_REMOTE_ENGINES', mockEngines)
vi.stubGlobal('DEFAULT_REMOTE_MODELS', mockRemoteModels.data)
describe('migrate engine settings', () => {
let extension: JanEngineManagementExtension
@ -21,6 +43,7 @@ describe('migrate engine settings', () => {
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('engines should be migrated', async () => {
@ -41,7 +64,7 @@ describe('migrate engine settings', () => {
expect(mockUpdateEngines).toBeCalled()
})
it('should not migrate when extesion version is not updated', async () => {
it('should not migrate when extension version is not updated', async () => {
vi.stubGlobal('VERSION', '0.0.0')
vi.spyOn(extension, 'getEngines').mockResolvedValue([])
const mockUpdateEngines = vi
@ -65,6 +88,7 @@ describe('getEngines', () => {
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should return a list of engines', async () => {
@ -77,12 +101,103 @@ describe('getEngines', () => {
})
})
describe('getRemoteModels', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should return a list of remote models', async () => {
vi.mock('ky', () => ({
default: {
get: () => ({
json: () => Promise.resolve(mockRemoteModels),
}),
},
}))
const models = await extension.getRemoteModels('openai')
expect(models).toEqual(mockRemoteModels)
})
it('should return empty data array when request fails', async () => {
vi.mock('ky', () => ({
default: {
get: () => ({
json: () => Promise.reject(new Error('Failed to fetch')),
}),
},
}))
const models = await extension.getRemoteModels('openai')
expect(models).toEqual({ data: [] })
})
})
describe('getInstalledEngines', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should return a list of installed engines', async () => {
const mockEngineVariants = [
{
name: 'windows-amd64-noavx',
version: '1.0.0',
},
]
vi.mock('ky', () => ({
default: {
get: () => ({
json: () => Promise.resolve(mockEngineVariants),
}),
},
}))
const mock = vi.spyOn(extension, 'getInstalledEngines')
mock.mockResolvedValue(mockEngineVariants)
const engines = await extension.getInstalledEngines(InferenceEngine.cortex_llamacpp)
expect(engines).toEqual(mockEngineVariants)
})
})
describe('healthz', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should perform health check successfully', async () => {
vi.mock('ky', () => ({
default: {
get: () => Promise.resolve(),
},
}))
await extension.healthz()
expect(extension.queue.concurrency).toBe(Infinity)
})
})
describe('updateDefaultEngine', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should set default engine variant if not installed', async () => {
@ -131,7 +246,7 @@ describe('updateDefaultEngine', () => {
})
})
it('should not reset default engine variant if not installed', async () => {
it('should not reset default engine variant if installed', async () => {
vi.stubGlobal('PLATFORM', 'win32')
vi.stubGlobal('CORTEX_ENGINE_VERSION', '1.0.0')
@ -180,4 +295,155 @@ describe('updateDefaultEngine', () => {
expect(mockSetDefaultEngineVariant).not.toBeCalled()
})
it('should handle HTTPError when getting default engine variant', async () => {
vi.stubGlobal('PLATFORM', 'win32')
vi.stubGlobal('CORTEX_ENGINE_VERSION', '1.0.0')
const httpError = new Error('HTTP Error') as HTTPError
httpError.response = { status: 400 } as Response
const mockGetDefaultEngineVariant = vi.spyOn(
extension,
'getDefaultEngineVariant'
)
mockGetDefaultEngineVariant.mockRejectedValue(httpError)
const mockSetDefaultEngineVariant = vi.spyOn(
extension,
'setDefaultEngineVariant'
)
mockSetDefaultEngineVariant.mockResolvedValue({ messages: 'OK' })
vi.mock('@janhq/core', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
systemInformation: vi.fn().mockResolvedValue({ gpuSetting: 'high' }),
}
})
vi.mock('./utils', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
engineVariant: vi.fn().mockResolvedValue('windows-amd64-noavx'),
}
})
await extension.updateDefaultEngine()
expect(mockSetDefaultEngineVariant).toHaveBeenCalledWith('llama-cpp', {
variant: 'windows-amd64-noavx',
version: '1.0.0',
})
})
it('should handle EngineError when getting default engine variant', async () => {
vi.stubGlobal('PLATFORM', 'win32')
vi.stubGlobal('CORTEX_ENGINE_VERSION', '1.0.0')
const mockGetDefaultEngineVariant = vi.spyOn(
extension,
'getDefaultEngineVariant'
)
mockGetDefaultEngineVariant.mockRejectedValue(new EngineError('Test error'))
const mockSetDefaultEngineVariant = vi.spyOn(
extension,
'setDefaultEngineVariant'
)
mockSetDefaultEngineVariant.mockResolvedValue({ messages: 'OK' })
vi.mock('@janhq/core', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
systemInformation: vi.fn().mockResolvedValue({ gpuSetting: 'high' }),
}
})
vi.mock('./utils', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
engineVariant: vi.fn().mockResolvedValue('windows-amd64-noavx'),
}
})
await extension.updateDefaultEngine()
expect(mockSetDefaultEngineVariant).toHaveBeenCalledWith('llama-cpp', {
variant: 'windows-amd64-noavx',
version: '1.0.0',
})
})
it('should handle unexpected errors gracefully', async () => {
vi.stubGlobal('PLATFORM', 'win32')
const mockGetDefaultEngineVariant = vi.spyOn(
extension,
'getDefaultEngineVariant'
)
mockGetDefaultEngineVariant.mockRejectedValue(new Error('Unexpected error'))
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
await extension.updateDefaultEngine()
expect(consoleSpy).toHaveBeenCalled()
})
})
describe('populateDefaultRemoteEngines', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should not add default remote engines if remote engines already exist', async () => {
const mockGetEngines = vi.spyOn(extension, 'getEngines')
mockGetEngines.mockResolvedValue(mockRemoteEngines)
const mockAddRemoteEngine = vi.spyOn(extension, 'addRemoteEngine')
await extension.populateDefaultRemoteEngines()
expect(mockAddRemoteEngine).not.toBeCalled()
})
it('should add default remote engines if no remote engines exist', async () => {
const mockGetEngines = vi.spyOn(extension, 'getEngines')
mockGetEngines.mockResolvedValue([])
const mockAddRemoteEngine = vi.spyOn(extension, 'addRemoteEngine')
mockAddRemoteEngine.mockResolvedValue({ messages: 'OK' })
const mockAddRemoteModel = vi.spyOn(extension, 'addRemoteModel')
mockAddRemoteModel.mockResolvedValue(undefined)
vi.mock('@janhq/core', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
events: {
emit: vi.fn(),
},
joinPath: vi.fn().mockResolvedValue('/path/to/settings.json'),
getJanDataFolderPath: vi.fn().mockResolvedValue('/path/to/data'),
fs: {
existsSync: vi.fn().mockResolvedValue(false),
},
}
})
await extension.populateDefaultRemoteEngines()
expect(mockAddRemoteEngine).toHaveBeenCalled()
expect(mockAddRemoteModel).toHaveBeenCalled()
})
})

View File

@ -0,0 +1,139 @@
import { describe, beforeEach, it, expect, vi } from 'vitest'
import JanEngineManagementExtension from './index'
import { InferenceEngine } from '@janhq/core'
describe('populateRemoteModels', () => {
let extension: JanEngineManagementExtension
beforeEach(() => {
// @ts-ignore
extension = new JanEngineManagementExtension()
vi.resetAllMocks()
})
it('should populate remote models successfully', async () => {
const mockEngineConfig = {
engine: InferenceEngine.openai,
}
const mockRemoteModels = {
data: [
{
id: 'gpt-4',
name: 'GPT-4',
},
],
}
const mockGetRemoteModels = vi.spyOn(extension, 'getRemoteModels')
mockGetRemoteModels.mockResolvedValue(mockRemoteModels)
const mockAddRemoteModel = vi.spyOn(extension, 'addRemoteModel')
mockAddRemoteModel.mockResolvedValue(undefined)
vi.mock('@janhq/core', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
events: {
emit: vi.fn(),
},
}
})
// Use the private method through index.ts
// @ts-ignore - Accessing private method for testing
await extension.populateRemoteModels(mockEngineConfig)
expect(mockGetRemoteModels).toHaveBeenCalledWith(mockEngineConfig.engine)
expect(mockAddRemoteModel).toHaveBeenCalledWith({
...mockRemoteModels.data[0],
engine: mockEngineConfig.engine,
model: 'gpt-4',
})
})
it('should handle empty data from remote models', async () => {
const mockEngineConfig = {
engine: InferenceEngine.openai,
}
const mockGetRemoteModels = vi.spyOn(extension, 'getRemoteModels')
mockGetRemoteModels.mockResolvedValue({ data: [] })
const mockAddRemoteModel = vi.spyOn(extension, 'addRemoteModel')
vi.mock('@janhq/core', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
events: {
emit: vi.fn(),
},
}
})
// @ts-ignore - Accessing private method for testing
await extension.populateRemoteModels(mockEngineConfig)
expect(mockGetRemoteModels).toHaveBeenCalledWith(mockEngineConfig.engine)
expect(mockAddRemoteModel).not.toHaveBeenCalled()
})
it('should handle errors when getting remote models', async () => {
const mockEngineConfig = {
engine: InferenceEngine.openai,
}
const mockGetRemoteModels = vi.spyOn(extension, 'getRemoteModels')
mockGetRemoteModels.mockRejectedValue(new Error('Failed to fetch models'))
const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {})
// @ts-ignore - Accessing private method for testing
await extension.populateRemoteModels(mockEngineConfig)
expect(mockGetRemoteModels).toHaveBeenCalledWith(mockEngineConfig.engine)
expect(consoleSpy).toHaveBeenCalled()
})
it('should handle errors when adding remote models', async () => {
const mockEngineConfig = {
engine: InferenceEngine.openai,
}
const mockRemoteModels = {
data: [
{
id: 'gpt-4',
name: 'GPT-4',
},
],
}
const mockGetRemoteModels = vi.spyOn(extension, 'getRemoteModels')
mockGetRemoteModels.mockResolvedValue(mockRemoteModels)
const mockAddRemoteModel = vi.spyOn(extension, 'addRemoteModel')
mockAddRemoteModel.mockRejectedValue(new Error('Failed to add model'))
const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {})
vi.mock('@janhq/core', async (importOriginal) => {
const actual = (await importOriginal()) as any
return {
...actual,
events: {
emit: vi.fn(),
},
}
})
// @ts-ignore - Accessing private method for testing
await extension.populateRemoteModels(mockEngineConfig)
expect(mockGetRemoteModels).toHaveBeenCalledWith(mockEngineConfig.engine)
expect(mockAddRemoteModel).toHaveBeenCalled()
expect(consoleSpy).toHaveBeenCalled()
})
})

View File

@ -1 +1 @@
1.0.11-rc7
1.0.11-rc8