Files
waggle-os/packages/hive-mind-core/tests/mind/fts-sanitize.test.ts
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

112 lines
3.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect } from 'vitest';
import {
FTS_STOP_WORDS,
sanitizeFtsToken,
hasUnsegmentedScript,
buildFtsOrQuery,
} from '../../src/mind/fts-sanitize.js';
/**
* S1 — Unicode FTS sanitizer. The legacy `[^\w]` strip destroyed every
* non-ASCII letter; the shared helper must preserve Cyrillic/diacritics,
* exclude CJK (unsegmented by unicode61), and stay byte-identical to the
* legacy pipeline for pure-ASCII queries (LoCoMo invariance).
*/
/** Verbatim copy of the legacy sanitizer (search.ts W3.6 / multi-mind F6). */
function legacyFtsOrQuery(query: string): string {
return query
.split(/\s+/)
.map(w => w.replace(/[^\w]/g, ''))
.filter(w => w.length > 2 && !FTS_STOP_WORDS.has(w.toLowerCase()))
.map(w => `"${w.replace(/"/g, '')}"`)
.join(' OR ');
}
describe('fts-sanitize (S1)', () => {
describe('sanitizeFtsToken', () => {
it('is a no-op strip for ASCII words (identical to [^\\w])', () => {
for (const w of ['hello', 'world_2', 'GPT4', 'machine-learning,', '"quoted"']) {
expect(sanitizeFtsToken(w)).toBe(w.replace(/[^\w]/g, ''));
}
});
it('preserves Cyrillic letters', () => {
expect(sanitizeFtsToken('Београд,')).toBe('Београд');
});
it('preserves Latin diacritics', () => {
expect(sanitizeFtsToken('čokolada!')).toBe('čokolada');
expect(sanitizeFtsToken('žurka')).toBe('žurka');
});
it('strips emoji and punctuation', () => {
expect(sanitizeFtsToken('🚀!!')).toBe('');
expect(sanitizeFtsToken('a🚀b')).toBe('ab');
});
});
describe('hasUnsegmentedScript', () => {
it('detects Han, Hiragana, Katakana, Hangul', () => {
expect(hasUnsegmentedScript('北京')).toBe(true);
expect(hasUnsegmentedScript('ひらがな')).toBe(true);
expect(hasUnsegmentedScript('カタカナ')).toBe(true);
expect(hasUnsegmentedScript('한국어')).toBe(true);
expect(hasUnsegmentedScript('meeting 北京')).toBe(true);
});
it('is false for ASCII, Cyrillic, and diacritics', () => {
expect(hasUnsegmentedScript('meeting notes')).toBe(false);
expect(hasUnsegmentedScript('Београд')).toBe(false);
expect(hasUnsegmentedScript('čačak žurka')).toBe(false);
});
});
describe('buildFtsOrQuery', () => {
it('is byte-identical to the legacy sanitizer for English queries (regression lock)', () => {
const representative = [
'machine learning',
'quantum computing spacetime',
'hiring decisions this month',
'the a an of to in for on with',
'What did we decide about the deployment?',
'error-handling in production!',
'TypeScript preferences',
'launch date',
'API design patterns REST GraphQL and gRPC services',
'ab cd ef',
'a1 b2c3 d_4',
];
for (const q of representative) {
expect(buildFtsOrQuery(q)).toBe(legacyFtsOrQuery(q));
}
});
it('keeps Cyrillic tokens', () => {
expect(buildFtsOrQuery('Београд конференција')).toBe('"Београд" OR "конференција"');
});
it('keeps diacritic tokens', () => {
expect(buildFtsOrQuery('čokolada žurka')).toBe('"čokolada" OR "žurka"');
});
it('drops short (≤2 char) tokens regardless of script', () => {
expect(buildFtsOrQuery('је Београд')).toBe('"Београд"');
});
it('returns empty for pure-CJK queries (routed to LIKE by callers)', () => {
expect(buildFtsOrQuery('北京旅行')).toBe('');
expect(buildFtsOrQuery('ひらがなのテスト')).toBe('');
});
it('drops CJK tokens from mixed queries but keeps the rest', () => {
expect(buildFtsOrQuery('会議 meeting notes')).toBe('"meeting" OR "notes"');
});
it('returns empty for stop-word-only and punctuation-only queries', () => {
expect(buildFtsOrQuery('the a an')).toBe('');
expect(buildFtsOrQuery('!!! ???')).toBe('');
});
});
});