Files
Momento/memento-note/tests/search/semantic-threshold.spec.ts
Sepehr Ramezani aa6a214f37 feat: rename keep-notes to memento-note, migrate to PostgreSQL, fix MCP bugs
- Rename directory keep-notes -> memento-note with all code references
- Prisma: SQLite -> PostgreSQL (both app and MCP server schemas)
- Sync MCP schema with main app (add missing fields, relations, indexes)
- Delete 17 SQLite migrations (clean slate for PostgreSQL)
- Remove SQLite dependencies (@libsql/client, better-sqlite3, etc.)
- Fix MCP server: hardcoded Windows DB paths -> DATABASE_URL env var
- Fix MCP server: .dockerignore excluded index-sse.js (SSE mode broken)
- MCP Dockerfile: node:20 -> node:22
- Docker Compose: add postgres service, remove SQLite volume
- Generate favicon.ico, icon-192.png, icon-512.png, apple-icon.png
- Update layout.tsx icons and manifest.json for PNG icons
- Update all .env files for PostgreSQL
- Rewrite README.md with updated sections
- Remove mcp-server/node_modules and prisma/client-generated from git tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-20 20:58:04 +02:00

126 lines
4.9 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('Semantic Search Threshold Tests', () => {
test.beforeEach(async ({ page }) => {
// Login before each test
await page.goto('http://localhost:3000');
// Wait for potential redirect or login
await page.waitForLoadState('networkidle');
});
test('should use default threshold of 0.65 for semantic matching', async ({ page }) => {
// This test verifies that the default threshold is applied
// We'll search for something and check that only relevant results appear
await page.goto('http://localhost:3000');
// Get initial note count
const initialNotes = await page.locator('.note-card').count();
// Search for something specific
const searchInput = page.locator('input[placeholder*="Search"]');
await searchInput.fill('programming code');
await page.waitForTimeout(500); // Wait for search debounce
// Get search results
const searchResults = await page.locator('.note-card').count();
// With threshold 0.65, we should have fewer false positives
// This is a basic sanity check - exact assertions depend on your data
expect(searchResults).toBeLessThanOrEqual(initialNotes);
// Clear search
await searchInput.fill('');
await page.waitForTimeout(500);
});
test('should not show unrelated notes when searching for specific terms', async ({ page }) => {
await page.goto('http://localhost:3000');
// Search for technical terms
const searchInput = page.locator('input[placeholder*="Search"]');
await searchInput.fill('javascript programming');
await page.waitForTimeout(500);
// Get all search result titles
const resultTitles = await page.locator('.note-card .note-title').allTextContents();
// Check that results are related to the search (basic keyword check)
// With higher threshold (0.65), semantic matches should be more relevant
const hasIrrelevantResults = resultTitles.some(title => {
const lowerTitle = title.toLowerCase();
// If a result mentions completely unrelated topics like cooking/food
// when searching for programming, that's a false positive
return lowerTitle.includes('recipe') ||
lowerTitle.includes('cooking') ||
lowerTitle.includes('food') ||
lowerTitle.includes('shopping');
});
// With threshold 0.65, we expect fewer false positives
// This test may need adjustment based on your actual data
expect(hasIrrelevantResults).toBe(false);
});
test('should still show highly relevant semantic matches', async ({ page }) => {
await page.goto('http://localhost:3000');
// Search for a concept and its close synonyms
const searchInput = page.locator('input[placeholder*="Search"]');
await searchInput.fill('coding');
await page.waitForTimeout(500);
// Get search results
const searchResults = await page.locator('.note-card');
const count = await searchResults.count();
// Even with higher threshold, we should find semantic matches
// for closely related terms like "programming", "development", etc.
if (count > 0) {
const titles = await page.locator('.note-card .note-title').allTextContents();
const hasRelevantContent = titles.some(title => {
const lowerTitle = title.toLowerCase();
return lowerTitle.includes('code') ||
lowerTitle.includes('program') ||
lowerTitle.includes('develop') ||
lowerTitle.includes('software') ||
lowerTitle.includes('app');
});
// Should find semantically related content
expect(hasRelevantContent).toBe(true);
}
});
test('threshold can be configured via SystemConfig', async ({ page }) => {
// This is an integration test verifying the configuration system
// In a real scenario, you'd make an API call to update the config
// Default threshold should be 0.65
const response = await page.request.get('/api/admin/embeddings/validate');
// This endpoint requires admin auth, so we're just checking it exists
// The actual threshold value is tested in unit tests
expect(response.ok()).toBeFalsy(); // Should be 401/403 without auth
});
});
test.describe('Semantic Threshold Unit Tests', () => {
test('default threshold should be 0.65', () => {
// This is a compile-time check
// The actual value is in SEARCH_DEFAULTS.SEMANTIC_THRESHOLD
const { SEARCH_DEFAULTS } = require('../../lib/config');
expect(SEARCH_DEFAULTS.SEMANTIC_THRESHOLD).toBe(0.65);
});
test('threshold should be higher than old value', () => {
const { SEARCH_DEFAULTS } = require('../../lib/config');
expect(SEARCH_DEFAULTS.SEMANTIC_THRESHOLD).toBeGreaterThan(0.40);
});
test('threshold should be less than 1.0', () => {
const { SEARCH_DEFAULTS } = require('../../lib/config');
expect(SEARCH_DEFAULTS.SEMANTIC_THRESHOLD).toBeLessThan(1.0);
});
});