Files
obsidian_ollama/tests/utils.test.ts
T
fegger 59df2f6856 Refactor Ollama client and related components
Update error handling and improve streaming capabilities in the Ollama client and related components. Key changes
include:

- Simplify error types and improve error handling
- Refactor streaming logic to use async generators
- Update tool execution and vault indexing
- Improve utility functions and types
- Update test files to reflect changes
2026-05-05 17:09:16 +02:00

198 lines
6.7 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',
lastIndexTime: 0,
});
expect(errors).toEqual([]);
});
it('should report URL validation errors', () => {
const errors = validatePluginSettings({
ollamaUrl: 'invalid-url',
model: 'llama3',
lastIndexTime: 0,
});
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',
lastIndexTime: 0,
});
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',
lastIndexTime: 0,
});
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);
});
});
});