194 lines
6.6 KiB
TypeScript
Executable File
194 lines
6.6 KiB
TypeScript
Executable File
/**
|
|
* Unit tests for utility functions, particularly validation functions
|
|
*/
|
|
import {
|
|
validateOllamaUrl,
|
|
validateModelName,
|
|
validatePluginSettings,
|
|
safeParseJson,
|
|
isValidHttpUrl,
|
|
} from '../src/utils';
|
|
|
|
describe('Validation Functions', () => {
|
|
describe('validateOllamaUrl', () => {
|
|
it('should accept valid HTTP URLs', () => {
|
|
expect(validateOllamaUrl('http://localhost:11434').valid).toBe(true);
|
|
expect(validateOllamaUrl('https://ollama.example.com').valid).toBe(true);
|
|
expect(validateOllamaUrl('http://192.168.1.100:8080').valid).toBe(true);
|
|
});
|
|
|
|
it('should reject invalid URLs', () => {
|
|
const invalidUrl = validateOllamaUrl('not-a-url');
|
|
expect(invalidUrl.valid).toBe(false);
|
|
expect(invalidUrl.error).toContain('valid HTTP or HTTPS URL');
|
|
});
|
|
|
|
it('should reject URLs ending with slash', () => {
|
|
const urlWithSlash = validateOllamaUrl('http://localhost:11434/');
|
|
expect(urlWithSlash.valid).toBe(false);
|
|
expect(urlWithSlash.error).toContain('should not end with a slash');
|
|
});
|
|
|
|
it('should reject empty URLs', () => {
|
|
const emptyUrl = validateOllamaUrl('');
|
|
expect(emptyUrl.valid).toBe(false);
|
|
expect(emptyUrl.error).toContain('cannot be empty');
|
|
});
|
|
|
|
it('should reject non-string inputs', () => {
|
|
const nonString = validateOllamaUrl(123 as any);
|
|
expect(nonString.valid).toBe(false);
|
|
});
|
|
|
|
it('should trim whitespace from URLs', () => {
|
|
expect(validateOllamaUrl(' http://localhost:11434 ').valid).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validateModelName', () => {
|
|
it('should accept valid model names', () => {
|
|
expect(validateModelName('llama3').valid).toBe(true);
|
|
expect(validateModelName('llama-2').valid).toBe(true);
|
|
expect(validateModelName('llama.2').valid).toBe(true);
|
|
expect(validateModelName('llama_2').valid).toBe(true);
|
|
expect(validateModelName('llama-2-7b-instruct').valid).toBe(true);
|
|
});
|
|
|
|
it('should reject invalid model names', () => {
|
|
const invalidModel = validateModelName('llama@2');
|
|
expect(invalidModel.valid).toBe(false);
|
|
expect(invalidModel.error).toContain(
|
|
'only contain letters, numbers, dots, dashes, and underscores'
|
|
);
|
|
});
|
|
|
|
it('should reject empty model names', () => {
|
|
const emptyModel = validateModelName('');
|
|
expect(emptyModel.valid).toBe(false);
|
|
expect(emptyModel.error).toContain('cannot be empty');
|
|
});
|
|
|
|
it('should reject model names that are too short', () => {
|
|
const shortModel = validateModelName('a');
|
|
expect(shortModel.valid).toBe(false);
|
|
expect(shortModel.error).toContain('at least 2 characters long');
|
|
});
|
|
|
|
it('should reject model names that are too long', () => {
|
|
const longModel = validateModelName('a'.repeat(101));
|
|
expect(longModel.valid).toBe(false);
|
|
expect(longModel.error).toContain('less than 100 characters long');
|
|
});
|
|
|
|
it('should reject non-string inputs', () => {
|
|
const nonString = validateModelName(123 as any);
|
|
expect(nonString.valid).toBe(false);
|
|
});
|
|
|
|
it('should trim whitespace from model names', () => {
|
|
expect(validateModelName(' llama3 ').valid).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('validatePluginSettings', () => {
|
|
it('should return empty array for valid settings', () => {
|
|
const errors = validatePluginSettings({
|
|
ollamaUrl: 'http://localhost:11434',
|
|
model: 'llama3',
|
|
});
|
|
expect(errors).toEqual([]);
|
|
});
|
|
|
|
it('should report URL validation errors', () => {
|
|
const errors = validatePluginSettings({
|
|
ollamaUrl: 'invalid-url',
|
|
model: 'llama3',
|
|
});
|
|
expect(errors.length).toBe(1);
|
|
expect(errors[0]).toContain('Ollama URL');
|
|
});
|
|
|
|
it('should report model validation errors', () => {
|
|
const errors = validatePluginSettings({
|
|
ollamaUrl: 'http://localhost:11434',
|
|
model: 'invalid@model',
|
|
});
|
|
expect(errors.length).toBe(1);
|
|
expect(errors[0]).toContain('Model');
|
|
});
|
|
|
|
it('should report multiple validation errors', () => {
|
|
const errors = validatePluginSettings({
|
|
ollamaUrl: 'invalid-url',
|
|
model: 'invalid@model',
|
|
});
|
|
expect(errors.length).toBe(2);
|
|
expect(errors[0]).toContain('Ollama URL');
|
|
expect(errors[1]).toContain('Model');
|
|
});
|
|
});
|
|
|
|
describe('safeParseJson', () => {
|
|
it('should parse valid JSON', () => {
|
|
const result = safeParseJson('{"key": "value"}');
|
|
expect(result).toEqual({ key: 'value' });
|
|
});
|
|
|
|
it('should throw on invalid JSON', () => {
|
|
expect(() => safeParseJson('{invalid json}')).toThrow('Invalid JSON');
|
|
});
|
|
|
|
it('should throw on code injection patterns', () => {
|
|
expect(() => safeParseJson('{"constructor": "function(){}"}')).toThrow('dangerous');
|
|
expect(() => safeParseJson('{"prototype": "something"}')).toThrow('dangerous');
|
|
expect(() => safeParseJson('{"__proto__": "something"}')).toThrow('dangerous');
|
|
expect(() => safeParseJson('{"function": "alert"}')).toThrow('dangerous');
|
|
});
|
|
|
|
it('should throw on deeply nested JSON', () => {
|
|
const deeplyNested = JSON.stringify({ value: 1 });
|
|
let current = deeplyNested;
|
|
for (let i = 0; i < 25; i++) {
|
|
current = `{ "nested": ${current} }`;
|
|
}
|
|
expect(() => safeParseJson(current)).toThrow('nesting too deep');
|
|
});
|
|
|
|
it('should throw on very large JSON', () => {
|
|
const largeJson = '"' + 'x'.repeat(1000001) + '"'; // 1,000,001 characters
|
|
expect(() => safeParseJson(largeJson)).toThrow('input too large');
|
|
});
|
|
|
|
it('should handle string vs object arguments correctly', () => {
|
|
const stringArgs = '{"path": "test.md", "content": "test"}';
|
|
const objectArgs = { path: 'test.md', content: 'test' };
|
|
|
|
const stringResult = safeParseJson(stringArgs);
|
|
const objectResult = safeParseJson(JSON.stringify(objectArgs));
|
|
|
|
expect(stringResult).toEqual(objectResult);
|
|
});
|
|
|
|
it('should throw on non-string input', () => {
|
|
expect(() => safeParseJson(123 as any)).toThrow('Input must be a string');
|
|
});
|
|
});
|
|
|
|
describe('isValidHttpUrl', () => {
|
|
it('should accept valid HTTP URLs', () => {
|
|
expect(isValidHttpUrl('http://localhost:11434')).toBe(true);
|
|
expect(isValidHttpUrl('https://example.com')).toBe(true);
|
|
});
|
|
|
|
it('should reject non-HTTP URLs', () => {
|
|
expect(isValidHttpUrl('ftp://example.com')).toBe(false);
|
|
expect(isValidHttpUrl('file:///path')).toBe(false);
|
|
});
|
|
|
|
it('should reject invalid URLs', () => {
|
|
expect(isValidHttpUrl('not-a-url')).toBe(false);
|
|
expect(isValidHttpUrl('')).toBe(false);
|
|
});
|
|
});
|
|
});
|