test: add missing tests (#3716)

This commit is contained in:
Louis 2024-09-23 13:54:52 +07:00 committed by GitHub
parent 5b7f0c1308
commit c5e0c93ab4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 418 additions and 1 deletions

View File

@ -6,4 +6,12 @@ module.exports = {
'@/(.*)': '<rootDir>/src/$1',
},
runner: './testRunner.js',
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
diagnostics: false,
},
],
},
}

View File

@ -0,0 +1,8 @@
import { AssistantExtension } from './assistant';
import { ExtensionTypeEnum } from '../extension';
it('should return the correct type', () => {
const extension = new AssistantExtension();
expect(extension.type()).toBe(ExtensionTypeEnum.Assistant);
});

View File

@ -0,0 +1,45 @@
import { MessageRequest, ThreadMessage } from '../../types'
import { BaseExtension, ExtensionTypeEnum } from '../extension'
import { InferenceExtension } from './'
// Mock the MessageRequest and ThreadMessage types
type MockMessageRequest = {
text: string
}
type MockThreadMessage = {
text: string
userId: string
}
// Mock the BaseExtension class
class MockBaseExtension extends BaseExtension {
type(): ExtensionTypeEnum | undefined {
return ExtensionTypeEnum.Base
}
}
// Create a mock implementation of InferenceExtension
class MockInferenceExtension extends InferenceExtension {
async inference(data: MessageRequest): Promise<ThreadMessage> {
return { text: 'Mock response', userId: '123' } as unknown as ThreadMessage
}
}
describe('InferenceExtension', () => {
let inferenceExtension: InferenceExtension
beforeEach(() => {
inferenceExtension = new MockInferenceExtension()
})
it('should have the correct type', () => {
expect(inferenceExtension.type()).toBe(ExtensionTypeEnum.Inference)
})
it('should implement the inference method', async () => {
const messageRequest: MessageRequest = { text: 'Hello' } as unknown as MessageRequest
const result = await inferenceExtension.inference(messageRequest)
expect(result).toEqual({ text: 'Mock response', userId: '123' } as unknown as ThreadMessage)
})
})

View File

@ -0,0 +1,42 @@
import { ExtensionTypeEnum } from '../extension';
import { MonitoringExtension } from './monitoring';
it('should have the correct type', () => {
class TestMonitoringExtension extends MonitoringExtension {
getGpuSetting(): Promise<GpuSetting | undefined> {
throw new Error('Method not implemented.');
}
getResourcesInfo(): Promise<any> {
throw new Error('Method not implemented.');
}
getCurrentLoad(): Promise<any> {
throw new Error('Method not implemented.');
}
getOsInfo(): Promise<OperatingSystemInfo> {
throw new Error('Method not implemented.');
}
}
const monitoringExtension = new TestMonitoringExtension();
expect(monitoringExtension.type()).toBe(ExtensionTypeEnum.SystemMonitoring);
});
it('should create an instance of MonitoringExtension', () => {
class TestMonitoringExtension extends MonitoringExtension {
getGpuSetting(): Promise<GpuSetting | undefined> {
throw new Error('Method not implemented.');
}
getResourcesInfo(): Promise<any> {
throw new Error('Method not implemented.');
}
getCurrentLoad(): Promise<any> {
throw new Error('Method not implemented.');
}
getOsInfo(): Promise<OperatingSystemInfo> {
throw new Error('Method not implemented.');
}
}
const monitoringExtension = new TestMonitoringExtension();
expect(monitoringExtension).toBeInstanceOf(MonitoringExtension);
});

View File

@ -0,0 +1,5 @@
it('should not throw any errors when imported', () => {
expect(() => require('./index')).not.toThrow();
})

View File

@ -52,4 +52,12 @@ it('should skip processing for disabled tools', async () => {
const result = await manager.process(request, tools)
expect(result).toBe(request)
})
})
it('should throw an error when process is called without implementation', () => {
class TestTool extends InferenceTool {
name = 'testTool'
}
const tool = new TestTool()
expect(() => tool.process({} as MessageRequest)).toThrowError()
})

7
core/src/index.test.ts Normal file
View File

@ -0,0 +1,7 @@
it('should declare global object core when importing the module and then deleting it', () => {
import('./index');
delete globalThis.core;
expect(typeof globalThis.core).toBe('undefined');
});

View File

@ -0,0 +1,7 @@
import * as restfulV1 from './restful/v1';
it('should re-export from restful/v1', () => {
const restfulV1Exports = require('./restful/v1');
expect(restfulV1Exports).toBeDefined();
})

View File

@ -0,0 +1,6 @@
import { Processor } from './Processor';
it('should be defined', () => {
expect(Processor).toBeDefined();
});

View File

@ -7,3 +7,34 @@ it('should call function associated with key in process method', () => {
extension.process('testKey', 'arg1', 'arg2');
expect(mockFunc).toHaveBeenCalledWith('arg1', 'arg2');
});
it('should_handle_empty_extension_list_for_install', async () => {
jest.mock('../../extension/store', () => ({
installExtensions: jest.fn(() => Promise.resolve([])),
}));
const extension = new Extension();
const result = await extension.installExtension([]);
expect(result).toEqual([]);
});
it('should_handle_empty_extension_list_for_update', async () => {
jest.mock('../../extension/store', () => ({
getExtension: jest.fn(() => ({ update: jest.fn(() => Promise.resolve(true)) })),
}));
const extension = new Extension();
const result = await extension.updateExtension([]);
expect(result).toEqual([]);
});
it('should_handle_empty_extension_list', async () => {
jest.mock('../../extension/store', () => ({
getExtension: jest.fn(() => ({ uninstall: jest.fn(() => Promise.resolve(true)) })),
removeExtension: jest.fn(),
}));
const extension = new Extension();
const result = await extension.uninstallExtension([]);
expect(result).toBe(true);
});

View File

@ -0,0 +1,6 @@
import { NITRO_DEFAULT_PORT } from './consts';
it('should test NITRO_DEFAULT_PORT', () => {
expect(NITRO_DEFAULT_PORT).toBe(3928);
});

View File

@ -1,6 +1,8 @@
import { getEngineConfiguration } from './config';
import { getAppConfigurations, defaultAppConfig } from './config';
import { getJanExtensionsPath } from './config';
import { getJanDataFolderPath } from './config';
it('should return undefined for invalid engine ID', async () => {
const config = await getEngineConfiguration('invalid_engine');
expect(config).toBeUndefined();
@ -12,3 +14,15 @@ it('should return default config when CI is e2e', () => {
const config = getAppConfigurations();
expect(config).toEqual(defaultAppConfig());
});
it('should return extensions path when retrieved successfully', () => {
const extensionsPath = getJanExtensionsPath();
expect(extensionsPath).not.toBeUndefined();
});
it('should return data folder path when retrieved successfully', () => {
const dataFolderPath = getJanDataFolderPath();
expect(dataFolderPath).not.toBeUndefined();
});

View File

@ -0,0 +1,9 @@
import { MessageStatus } from './messageEntity';
it('should have correct values', () => {
expect(MessageStatus.Ready).toBe('ready');
expect(MessageStatus.Pending).toBe('pending');
expect(MessageStatus.Error).toBe('error');
expect(MessageStatus.Stopped).toBe('stopped');
})

View File

@ -0,0 +1,6 @@
import { SupportedPlatforms } from './systemResourceInfo';
it('should contain the correct values', () => {
expect(SupportedPlatforms).toEqual(['win32', 'linux', 'darwin']);
});

View File

@ -0,0 +1,16 @@
import * as monitoringInterface from './monitoringInterface';
import * as resourceInfo from './resourceInfo';
import * as index from './index';
import * as monitoringInterface from './monitoringInterface';
import * as resourceInfo from './resourceInfo';
it('should re-export all symbols from monitoringInterface and resourceInfo', () => {
for (const key in monitoringInterface) {
expect(index[key]).toBe(monitoringInterface[key]);
}
for (const key in resourceInfo) {
expect(index[key]).toBe(resourceInfo[key]);
}
});

View File

@ -0,0 +1,5 @@
it('should not throw any errors', () => {
expect(() => require('./index')).not.toThrow();
});

View File

@ -0,0 +1,19 @@
import { createSettingComponent } from './settingComponent';
it('should throw an error when creating a setting component with invalid controller type', () => {
const props: SettingComponentProps = {
key: 'invalidControllerKey',
title: 'Invalid Controller Title',
description: 'Invalid Controller Description',
controllerType: 'invalid' as any,
controllerProps: {
placeholder: 'Enter text',
value: 'Initial Value',
type: 'text',
textAlign: 'left',
inputActions: ['unobscure'],
},
};
expect(() => createSettingComponent(props)).toThrowError();
});

View File

@ -0,0 +1,6 @@
import { ThreadEvent } from './threadEvent';
it('should have the correct values', () => {
expect(ThreadEvent.OnThreadStarted).toBe('OnThreadStarted');
});

18
electron/jest.config.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
modulePathIgnorePatterns: ['<rootDir>/tests'],
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
},
runner: './testRunner.js',
transform: {
'^.+\\.tsx?$': [
'ts-jest',
{
diagnostics: false,
},
],
},
}

10
electron/testRunner.js Normal file
View File

@ -0,0 +1,10 @@
const jestRunner = require('jest-runner');
class EmptyTestFileRunner extends jestRunner.default {
async runTests(tests, watcher, onStart, onResult, onFailure, options) {
const nonEmptyTests = tests.filter(test => test.context.hasteFS.getSize(test.path) > 0);
return super.runTests(nonEmptyTests, watcher, onStart, onResult, onFailure, options);
}
}
module.exports = EmptyTestFileRunner;

View File

@ -18,6 +18,14 @@ const config = {
// setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
runner: './testRunner.js',
collectCoverageFrom: ['./**/*.{ts,tsx}'],
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
diagnostics: false,
},
],
},
}
// https://stackoverflow.com/a/72926763/5078746

8
web/utils/base64.test.ts Normal file
View File

@ -0,0 +1,8 @@
import { getBase64 } from './base64';
test('getBase64_converts_file_to_base64', async () => {
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
const base64String = await getBase64(file);
expect(base64String).toBe('data:text/plain;base64,dGVzdA==');
});

View File

@ -0,0 +1,33 @@
import { formatDownloadSpeed } from './converter';
import { formatExtensionsName } from './converter';
import { formatTwoDigits } from './converter';
test('formatDownloadSpeed_should_return_correct_output_when_input_is_undefined', () => {
expect(formatDownloadSpeed(undefined)).toBe('0B/s');
});
test('formatExtensionsName_should_return_correct_output_for_string_with_janhq_and_dash', () => {
expect(formatExtensionsName('@janhq/extension-name')).toBe('extension name');
});
test('formatTwoDigits_should_return_correct_output_for_single_digit_number', () => {
expect(formatTwoDigits(5)).toBe('5.00');
});
test('formatDownloadSpeed_should_return_correct_output_for_gigabytes', () => {
expect(formatDownloadSpeed(1500000000)).toBe('1.40GB/s');
});
test('formatDownloadSpeed_should_return_correct_output_for_megabytes', () => {
expect(formatDownloadSpeed(1500000)).toBe('1.43MB/s');
});
test('formatDownloadSpeed_should_return_correct_output_for_kilobytes', () => {
expect(formatDownloadSpeed(1500)).toBe('1.46KB/s');
});

View File

@ -1,5 +1,7 @@
// web/utils/modelParam.test.ts
import { normalizeValue, validationRules } from './modelParam'
import { extractModelLoadParams } from './modelParam';
import { extractInferenceParams } from './modelParam';
describe('validationRules', () => {
it('should validate temperature correctly', () => {
@ -189,3 +191,20 @@ describe('normalizeValue', () => {
expect(normalizeValue('cpu_threads', 0)).toBe(0)
})
})
it('should handle invalid values correctly by falling back to originParams', () => {
const modelParams = { temperature: 'invalid', token_limit: -1 };
const originParams = { temperature: 0.5, token_limit: 100 };
expect(extractInferenceParams(modelParams, originParams)).toEqual(originParams);
});
it('should return an empty object when no modelParams are provided', () => {
expect(extractModelLoadParams()).toEqual({});
});
it('should return an empty object when no modelParams are provided', () => {
expect(extractInferenceParams()).toEqual({});
});

View File

@ -4,6 +4,7 @@ import { ChatCompletionRole, MessageStatus } from '@janhq/core'
import { ThreadMessageBuilder } from './threadMessageBuilder'
import { MessageRequestBuilder } from './messageRequestBuilder'
import { ContentType } from '@janhq/core';
describe('ThreadMessageBuilder', () => {
it('testBuildMethod', () => {
const msgRequest = new MessageRequestBuilder(
@ -25,3 +26,75 @@ import { ChatCompletionRole, MessageStatus } from '@janhq/core'
expect(result.content).toEqual([])
})
})
it('testPushMessageWithPromptOnly', () => {
const msgRequest = new MessageRequestBuilder(
'type',
{ model: 'model' },
{ id: 'thread-id' },
[]
);
const builder = new ThreadMessageBuilder(msgRequest);
const prompt = 'test prompt';
builder.pushMessage(prompt, undefined, []);
expect(builder.content).toEqual([
{
type: ContentType.Text,
text: {
value: prompt,
annotations: [],
},
},
]);
});
it('testPushMessageWithPdf', () => {
const msgRequest = new MessageRequestBuilder(
'type',
{ model: 'model' },
{ id: 'thread-id' },
[]
);
const builder = new ThreadMessageBuilder(msgRequest);
const prompt = 'test prompt';
const base64 = 'test base64';
const fileUpload = [{ type: 'pdf', file: { name: 'test.pdf', size: 1000 } }];
builder.pushMessage(prompt, base64, fileUpload);
expect(builder.content).toEqual([
{
type: ContentType.Pdf,
text: {
value: prompt,
annotations: [base64],
name: fileUpload[0].file.name,
size: fileUpload[0].file.size,
},
},
]);
});
it('testPushMessageWithImage', () => {
const msgRequest = new MessageRequestBuilder(
'type',
{ model: 'model' },
{ id: 'thread-id' },
[]
);
const builder = new ThreadMessageBuilder(msgRequest);
const prompt = 'test prompt';
const base64 = 'test base64';
const fileUpload = [{ type: 'image', file: { name: 'test.jpg', size: 1000 } }];
builder.pushMessage(prompt, base64, fileUpload);
expect(builder.content).toEqual([
{
type: ContentType.Image,
text: {
value: prompt,
annotations: [base64],
},
},
]);
});