Files
vessel/frontend/src/lib/tools/builtin.test.ts
vikingowl d81430e1aa test: extend test coverage for backend and frontend
Backend:
- Add fetcher_test.go (HTML stripping, URL fetching utilities)
- Add model_registry_test.go (parsing, size ranges, model matching)
- Add database_test.go (CRUD operations, migrations)
- Add tests for geolocation, search, tools, version handlers

Frontend unit tests (469 total):
- OllamaClient: 22 tests for API methods with mocked fetch
- Memory/RAG: tokenizer, chunker, summarizer, embeddings, vector-store
- Services: prompt-resolution, conversation-summary
- Components: Skeleton, BranchNavigator, ConfirmDialog, ThinkingBlock
- Utils: export, import, file-processor, keyboard
- Tools: builtin math parser (44 tests)

E2E tests (28 total):
- Set up Playwright with Chromium
- App loading, sidebar navigation, settings page
- Chat interface, responsive design, accessibility
- Import dialog, project modal interactions

Config changes:
- Add browser conditions to vitest.config.ts for Svelte 5 components
- Add playwright.config.ts for E2E testing
- Add test:e2e scripts to package.json
- Update .gitignore to exclude test artifacts

Closes #8
2026-01-22 11:05:49 +01:00

284 lines
7.9 KiB
TypeScript

/**
* Built-in tools tests
*
* Tests the MathParser and tool definitions
*/
import { describe, it, expect } from 'vitest';
import { builtinTools, getBuiltinToolDefinitions } from './builtin';
// We need to test the MathParser through the calculate handler
// since MathParser is not exported directly
function calculate(expression: string, precision?: number): unknown {
const entry = builtinTools.get('calculate');
if (!entry) throw new Error('Calculate tool not found');
return entry.handler({ expression, precision });
}
describe('MathParser (via calculate tool)', () => {
describe('basic arithmetic', () => {
it('handles addition', () => {
expect(calculate('2+3')).toBe(5);
expect(calculate('100+200')).toBe(300);
expect(calculate('1+2+3+4')).toBe(10);
});
it('handles subtraction', () => {
expect(calculate('10-3')).toBe(7);
expect(calculate('100-50-25')).toBe(25);
});
it('handles multiplication', () => {
expect(calculate('3*4')).toBe(12);
expect(calculate('2*3*4')).toBe(24);
});
it('handles division', () => {
expect(calculate('10/2')).toBe(5);
expect(calculate('100/4/5')).toBe(5);
});
it('handles modulo', () => {
expect(calculate('10%3')).toBe(1);
expect(calculate('17%5')).toBe(2);
});
it('handles mixed operations with precedence', () => {
expect(calculate('2+3*4')).toBe(14);
expect(calculate('10-2*3')).toBe(4);
expect(calculate('10/2+3')).toBe(8);
});
});
describe('parentheses', () => {
it('handles simple parentheses', () => {
expect(calculate('(2+3)*4')).toBe(20);
expect(calculate('(10-2)*3')).toBe(24);
});
it('handles nested parentheses', () => {
expect(calculate('((2+3)*4)+1')).toBe(21);
expect(calculate('2*((3+4)*2)')).toBe(28);
});
});
describe('power/exponentiation', () => {
it('handles caret operator', () => {
expect(calculate('2^3')).toBe(8);
expect(calculate('3^2')).toBe(9);
expect(calculate('10^0')).toBe(1);
});
it('handles double star operator', () => {
expect(calculate('2**3')).toBe(8);
expect(calculate('5**2')).toBe(25);
});
it('handles right associativity', () => {
// 2^3^2 should be 2^(3^2) = 2^9 = 512
expect(calculate('2^3^2')).toBe(512);
});
});
describe('unary operators', () => {
it('handles negative numbers', () => {
expect(calculate('-5')).toBe(-5);
expect(calculate('-5+3')).toBe(-2);
expect(calculate('3+-5')).toBe(-2);
});
it('handles positive prefix', () => {
expect(calculate('+5')).toBe(5);
expect(calculate('3++5')).toBe(8);
});
it('handles double negation', () => {
expect(calculate('--5')).toBe(5);
});
});
describe('mathematical functions', () => {
it('handles sqrt', () => {
expect(calculate('sqrt(16)')).toBe(4);
expect(calculate('sqrt(2)')).toBeCloseTo(1.41421356, 5);
});
it('handles abs', () => {
expect(calculate('abs(-5)')).toBe(5);
expect(calculate('abs(5)')).toBe(5);
});
it('handles sign', () => {
expect(calculate('sign(-10)')).toBe(-1);
expect(calculate('sign(10)')).toBe(1);
expect(calculate('sign(0)')).toBe(0);
});
it('handles trigonometric functions', () => {
expect(calculate('sin(0)')).toBe(0);
expect(calculate('cos(0)')).toBe(1);
expect(calculate('tan(0)')).toBe(0);
});
it('handles inverse trig functions', () => {
expect(calculate('asin(0)')).toBe(0);
expect(calculate('acos(1)')).toBe(0);
expect(calculate('atan(0)')).toBe(0);
});
it('handles hyperbolic functions', () => {
expect(calculate('sinh(0)')).toBe(0);
expect(calculate('cosh(0)')).toBe(1);
expect(calculate('tanh(0)')).toBe(0);
});
it('handles logarithms', () => {
expect(calculate('log(1)')).toBe(0);
expect(calculate('log10(100)')).toBe(2);
expect(calculate('log2(8)')).toBe(3);
});
it('handles exp', () => {
expect(calculate('exp(0)')).toBe(1);
expect(calculate('exp(1)')).toBeCloseTo(Math.E, 5);
});
it('handles rounding functions', () => {
expect(calculate('round(1.5)')).toBe(2);
expect(calculate('floor(1.9)')).toBe(1);
expect(calculate('ceil(1.1)')).toBe(2);
expect(calculate('trunc(-1.9)')).toBe(-1);
});
});
describe('constants', () => {
it('handles PI', () => {
expect(calculate('PI')).toBeCloseTo(Math.PI, 5);
expect(calculate('pi')).toBeCloseTo(Math.PI, 5);
});
it('handles E', () => {
expect(calculate('E')).toBeCloseTo(Math.E, 5);
expect(calculate('e')).toBeCloseTo(Math.E, 5);
});
it('handles TAU', () => {
expect(calculate('TAU')).toBeCloseTo(Math.PI * 2, 5);
expect(calculate('tau')).toBeCloseTo(Math.PI * 2, 5);
});
it('handles PHI (golden ratio)', () => {
expect(calculate('PHI')).toBeCloseTo(1.618033988, 5);
});
it('handles LN2 and LN10', () => {
expect(calculate('LN2')).toBeCloseTo(Math.LN2, 5);
expect(calculate('LN10')).toBeCloseTo(Math.LN10, 5);
});
});
describe('complex expressions', () => {
it('handles PI-based calculations', () => {
expect(calculate('sin(PI/2)')).toBeCloseTo(1, 5);
expect(calculate('cos(PI)')).toBeCloseTo(-1, 5);
});
it('handles nested functions', () => {
expect(calculate('sqrt(abs(-16))')).toBe(4);
expect(calculate('log2(2^10)')).toBe(10);
});
it('handles function with complex argument', () => {
expect(calculate('sqrt(3^2+4^2)')).toBe(5); // Pythagorean: 3-4-5 triangle
});
});
describe('precision handling', () => {
it('defaults to 10 decimal places', () => {
const result = calculate('1/3');
expect(result).toBeCloseTo(0.3333333333, 9);
});
it('respects custom precision', () => {
const result = calculate('1/3', 2);
expect(result).toBe(0.33);
});
});
describe('error handling', () => {
it('handles division by zero', () => {
const result = calculate('1/0') as { error: string };
expect(result.error).toContain('Division by zero');
});
it('handles unknown functions', () => {
const result = calculate('unknown(5)') as { error: string };
expect(result.error).toContain('Unknown function');
});
it('handles missing closing parenthesis', () => {
const result = calculate('(2+3') as { error: string };
expect(result.error).toContain('parenthesis');
});
it('handles unexpected characters', () => {
const result = calculate('2+@3') as { error: string };
expect(result.error).toContain('Unexpected character');
});
it('handles infinity result', () => {
const result = calculate('exp(1000)') as { error: string };
expect(result.error).toContain('invalid number');
});
});
describe('whitespace handling', () => {
it('ignores whitespace', () => {
expect(calculate('2 + 3')).toBe(5);
expect(calculate(' 2 * 3 ')).toBe(6);
expect(calculate('sqrt( 16 )')).toBe(4);
});
});
});
describe('builtinTools registry', () => {
it('contains all expected tools', () => {
expect(builtinTools.has('get_current_time')).toBe(true);
expect(builtinTools.has('calculate')).toBe(true);
expect(builtinTools.has('fetch_url')).toBe(true);
expect(builtinTools.has('get_location')).toBe(true);
expect(builtinTools.has('web_search')).toBe(true);
});
it('marks all tools as builtin', () => {
for (const [, entry] of builtinTools) {
expect(entry.isBuiltin).toBe(true);
}
});
it('has valid definitions for all tools', () => {
for (const [name, entry] of builtinTools) {
expect(entry.definition.type).toBe('function');
expect(entry.definition.function.name).toBe(name);
expect(typeof entry.definition.function.description).toBe('string');
}
});
});
describe('getBuiltinToolDefinitions', () => {
it('returns array of tool definitions', () => {
const definitions = getBuiltinToolDefinitions();
expect(Array.isArray(definitions)).toBe(true);
expect(definitions.length).toBe(5);
});
it('returns valid definitions', () => {
const definitions = getBuiltinToolDefinitions();
for (const def of definitions) {
expect(def.type).toBe('function');
expect(def.function).toBeDefined();
expect(typeof def.function.name).toBe('string');
}
});
});