| import { describe, it, expect } from 'vitest'; |
| import { normalizeQuery, queryTerms, buildFtsMatch } from '@/lib/rag/query'; |
|
|
| describe('normalizeQuery', () => { |
| it('collapses whitespace and trims', () => { |
| expect(normalizeQuery(' chest pain\n\there ')).toBe('chest pain here'); |
| }); |
|
|
| it('caps length at 500 chars', () => { |
| expect(normalizeQuery('a'.repeat(900)).length).toBe(500); |
| }); |
|
|
| it('handles empty / nullish input', () => { |
| expect(normalizeQuery('')).toBe(''); |
| |
| expect(normalizeQuery(undefined)).toBe(''); |
| }); |
| }); |
|
|
| describe('queryTerms', () => { |
| it('drops stopwords and short tokens, lowercases, dedupes', () => { |
| const terms = queryTerms('What are the BEST best treatments for pain'); |
| expect(terms).toContain('best'); |
| expect(terms).toContain('treatments'); |
| expect(terms).toContain('pain'); |
| expect(terms).not.toContain('the'); |
| expect(terms).not.toContain('for'); |
| |
| expect(terms.filter((t) => t === 'best')).toHaveLength(1); |
| }); |
|
|
| it('returns [] when nothing usable remains', () => { |
| expect(queryTerms('the and a an ?? !!')).toEqual([]); |
| }); |
| }); |
|
|
| describe('buildFtsMatch (FTS5 injection safety)', () => { |
| it('produces only quoted terms joined by OR', () => { |
| const m = buildFtsMatch('chest pain breathing'); |
| expect(m).toMatch(/^"[a-z0-9]+"( OR "[a-z0-9]+")*$/); |
| expect(m).toContain('"chest"'); |
| }); |
|
|
| it('neutralises FTS operators and punctuation', () => { |
| |
| const m = buildFtsMatch('x" OR rag_chunks_fts MATCH "y NEAR(z) -drop'); |
| |
| expect((m.match(/"/g) || []).length % 2).toBe(0); |
| |
| if (m) expect(m).toMatch(/^"[a-z0-9]+"( OR "[a-z0-9]+")*$/); |
| }); |
|
|
| it('returns empty string when there are no usable terms', () => { |
| expect(buildFtsMatch('?? a an')).toBe(''); |
| }); |
| }); |
|
|