Add registry client that queries Official, Glama, and Smithery MCP registries with caching, request deduplication, retry logic, and result ranking/dedup. Includes 53 tests covering all components. Also fix null priority values in cancelled tasks (19-21) that broke Task Master, and add new tasks 25-27 for registry completion and CLI discover/install commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
92 lines
2.8 KiB
TypeScript
92 lines
2.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { rankResults } from '../../src/registry/ranking.js';
|
|
import type { RegistryServer } from '../../src/registry/types.js';
|
|
|
|
function makeServer(overrides: Partial<RegistryServer> = {}): RegistryServer {
|
|
return {
|
|
name: 'test-server',
|
|
description: 'A test server',
|
|
packages: {},
|
|
envTemplate: [],
|
|
transport: 'stdio',
|
|
popularityScore: 0,
|
|
verified: false,
|
|
sourceRegistry: 'official',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('rankResults', () => {
|
|
it('puts exact name match first', () => {
|
|
const servers = [
|
|
makeServer({ name: 'slack-extended-tools' }),
|
|
makeServer({ name: 'slack' }),
|
|
makeServer({ name: 'my-slack-bot' }),
|
|
];
|
|
const ranked = rankResults(servers, 'slack');
|
|
expect(ranked[0]?.name).toBe('slack');
|
|
});
|
|
|
|
it('ranks verified servers higher than unverified', () => {
|
|
const servers = [
|
|
makeServer({ name: 'server-a', verified: false }),
|
|
makeServer({ name: 'server-b', verified: true }),
|
|
];
|
|
const ranked = rankResults(servers, 'server');
|
|
expect(ranked[0]?.name).toBe('server-b');
|
|
});
|
|
|
|
it('ranks popular servers higher', () => {
|
|
const servers = [
|
|
makeServer({ name: 'unpopular', popularityScore: 1 }),
|
|
makeServer({ name: 'popular', popularityScore: 10000 }),
|
|
];
|
|
const ranked = rankResults(servers, 'test');
|
|
expect(ranked[0]?.name).toBe('popular');
|
|
});
|
|
|
|
it('considers recency', () => {
|
|
const recent = new Date();
|
|
const old = new Date(Date.now() - 365 * 24 * 60 * 60 * 1000);
|
|
const servers = [
|
|
makeServer({ name: 'old-server', lastUpdated: old }),
|
|
makeServer({ name: 'new-server', lastUpdated: recent }),
|
|
];
|
|
const ranked = rankResults(servers, 'test');
|
|
expect(ranked[0]?.name).toBe('new-server');
|
|
});
|
|
|
|
it('handles missing lastUpdated gracefully', () => {
|
|
const servers = [
|
|
makeServer({ name: 'no-date' }),
|
|
makeServer({ name: 'has-date', lastUpdated: new Date() }),
|
|
];
|
|
// Should not throw
|
|
const ranked = rankResults(servers, 'test');
|
|
expect(ranked).toHaveLength(2);
|
|
});
|
|
|
|
it('produces stable ordering for identical scores', () => {
|
|
const servers = Array.from({ length: 10 }, (_, i) =>
|
|
makeServer({ name: `server-${String(i)}` }),
|
|
);
|
|
const ranked1 = rankResults(servers, 'test');
|
|
const ranked2 = rankResults(servers, 'test');
|
|
expect(ranked1.map((s) => s.name)).toEqual(ranked2.map((s) => s.name));
|
|
});
|
|
|
|
it('returns empty array for empty input', () => {
|
|
expect(rankResults([], 'test')).toEqual([]);
|
|
});
|
|
|
|
it('does not mutate original array', () => {
|
|
const servers = [
|
|
makeServer({ name: 'b' }),
|
|
makeServer({ name: 'a' }),
|
|
];
|
|
const original = [...servers];
|
|
rankResults(servers, 'test');
|
|
expect(servers.map((s) => s.name)).toEqual(original.map((s) => s.name));
|
|
});
|
|
});
|