UNPKG

@n2flowjs/nbase

Version:

Neural Vector Database for efficient similarity search

184 lines (149 loc) 5.61 kB
import { expect } from 'chai'; import express from 'express'; import { beforeEach, describe, it } from 'mocha'; import * as sinon from 'sinon'; import request from 'supertest'; import { searchRoutes } from '../../src/server/routes/search'; import { ApiContext } from '../../src/types'; describe('Search Routes', () => { let app: express.Application; let context: ApiContext; let mockDatabase: any; let mockTimer: any; let mockCreateFilterFunction: any; let originalConsoleError: any; beforeEach(() => { // Save original console.error to restore later originalConsoleError = console.error; console.error = sinon.stub(); // Create mock database mockDatabase = { findNearest: sinon.stub().resolves([ { id: '1', score: 0.95, metadata: { title: 'Test 1' } }, { id: '2', score: 0.85, metadata: { title: 'Test 2' } }, ]), }; // Create mock timer mockTimer = { start: sinon.stub().returns(undefined), stop: sinon.stub().returns({ total: 123 }), }; // Create mock filter function mockCreateFilterFunction = sinon.stub().returns(() => true); // Create mock context context = { database: mockDatabase, timer: mockTimer, createFilterFunction: mockCreateFilterFunction, } as any; // Setup express app with search routes app = express(); app.use(express.json()); app.use('/search', searchRoutes(context)); }); afterEach(() => { console.error = originalConsoleError; sinon.restore(); }); it('should return 400 when query is missing', async () => { const response = await request(app).post('/search').send({}); expect(response.status).to.equal(400); expect(response.body.error).to.equal('Invalid request: query vector array is required'); }); it('should return 400 when query is not an array', async () => { const response = await request(app).post('/search').send({ query: 'not an array' }); expect(response.status).to.equal(400); expect(response.body.error).to.equal('Invalid request: query vector array is required'); }); it('should return search results with default options', async () => { const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3], k: 2 }); expect(response.status).to.equal(200); expect(response.body.results).to.be.an('array').with.lengthOf(2); expect(response.body.count).to.equal(2); expect(response.body.duration).to.equal(123); expect(response.body.searchOptions).to.deep.include({ k: 2, method: 'clustered', partitionsSearched: 'all', filtersApplied: false, cacheUsed: true, }); expect(mockTimer.start.calledWith('search')).to.be.true; expect(mockTimer.stop.calledWith('search')).to.be.true; expect(mockDatabase.findNearest.calledOnce).to.be.true; }); it('should handle search with HNSW method', async () => { const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3], k: 5, method: 'hnsw', efSearch: 100, }); expect(response.status).to.equal(200); expect(response.body.searchOptions.method).to.equal('hnsw'); // Verify correct options were passed to database.findNearest const searchOptionsArg = mockDatabase.findNearest.args[0][2]; expect(searchOptionsArg.useHNSW).to.be.true; expect(searchOptionsArg.efSearch).to.equal(100); }); it('should handle search with filters', async () => { const filters = { category: 'test' }; const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3], filters: filters, }); expect(response.status).to.equal(200); expect(response.body.searchOptions.filtersApplied).to.be.true; expect(mockCreateFilterFunction.calledWith(filters)).to.be.true; }); it('should handle search with array filters', async () => { const filters = [{ field: 'category', value: 'test' }]; const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3], filters: filters, }); expect(response.status).to.equal(200); expect(response.body.searchOptions.filtersApplied).to.be.true; expect(mockCreateFilterFunction.calledWith(filters)).to.be.true; }); it('should handle search with partitionIds', async () => { const partitionIds = ['partition1', 'partition2']; const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3], partitionIds: partitionIds, }); expect(response.status).to.equal(200); expect(response.body.searchOptions.partitionsSearched).to.equal(2); }); it('should handle database errors', async () => { const errorMessage = 'Database error'; mockDatabase.findNearest.rejects(new Error(errorMessage)); const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3] }); expect(response.status).to.equal(500); expect(response.body.error).to.equal(errorMessage); expect(response.body.duration).to.equal(123); }); it('should include stack trace in development mode', async () => { process.env.NODE_ENV = 'development'; mockDatabase.findNearest.rejects(new Error('Test error')); const response = await request(app) .post('/search') .send({ query: [0.1, 0.2, 0.3] }); expect(response.status).to.equal(500); expect(response.body.stack).to.be.a('string'); // Reset NODE_ENV delete process.env.NODE_ENV; }); });