Files
obsidian_ollama/tests/utils.test.ts
T
fegger ccb0603d0c Update streaming handling and improve model validation
Add MAX_STREAM_CHUNKS constant and update message handling to mark streaming as complete even without tool results.
Improve model name validation to support colons and add comprehensive test suite. Refactor VaultIndexer to simplify
constructor and remove unused methods. Fix path normalization in tool executor and remove unused safeWriteFile export.
Update error handling to remove redundant console suppressions.
2026-05-06 21:48:27 +02:00

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, underscores, and colons'
);
});
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);
});
});
});