sync: update from private repo (5b6df2f)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-10 08:40:41 +00:00
parent dfc5950117
commit 5502478636
43 changed files with 6452 additions and 18 deletions
+244
View File
@@ -0,0 +1,244 @@
import { mkdtempSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
import type { ToolContext, ToolResult } from './core.js';
// The cache DB path is resolved from process.cwd() at module load, so stub
// cwd BEFORE importing the module to keep the sqlite cache in a temp dir.
const tmpRoot = mkdtempSync(join(tmpdir(), 'ms-learn-test-'));
let executeTool: (n: string, i: Record<string, unknown>, c: ToolContext) => Promise<ToolResult | null>;
let TOOL_DEFS: Record<string, unknown>;
beforeAll(async () => {
vi.spyOn(process, 'cwd').mockReturnValue(tmpRoot);
const mod = await import('./ms-learn.js');
executeTool = mod.executeTool;
TOOL_DEFS = mod.TOOL_DEFS;
});
afterAll(() => {
rmSync(tmpRoot, { recursive: true, force: true });
});
afterEach(() => {
vi.unstubAllGlobals();
});
const ctx = {} as ToolContext;
function htmlResponse(body: string, status = 200): Response {
return new Response(body, { status, headers: { 'content-type': 'text/html' } });
}
function jsonResponse(body: unknown, status = 200): Response {
return new Response(JSON.stringify(body), {
status,
headers: { 'content-type': 'application/json' },
});
}
const LEARN_HTML = `<!doctype html>
<html><head>
<title>Durable Functions overview | Microsoft Learn</title>
</head><body>
<nav>site nav chrome</nav>
<main>
<article>
<h1>Durable Functions overview</h1>
<p>Durable Functions is an &amp; extension of Azure Functions.</p>
<h2>Patterns</h2>
<ul><li>Function chaining</li><li>Fan-out/fan-in</li></ul>
<pre><code class="lang-csharp">var x = 1;</code></pre>
<p>See <a href="https://learn.microsoft.com/en-us/azure/other">related docs</a>.</p>
</article>
</main>
<footer>footer chrome</footer>
</body></html>`;
describe('TOOL_DEFS', () => {
it('registers the four Learn tools', () => {
expect(Object.keys(TOOL_DEFS).sort()).toEqual([
'FetchMicrosoftLearn',
'RefreshMicrosoftLearnCache',
'SearchMicrosoftLearn',
'SearchMicrosoftLearnCache',
]);
});
});
describe('executeTool', () => {
it('returns null for unknown tool names', async () => {
expect(await executeTool('NotALearnTool', {}, ctx)).toBeNull();
});
});
describe('FetchMicrosoftLearn', () => {
it('requires a url', async () => {
const res = await executeTool('FetchMicrosoftLearn', {}, ctx);
expect(res?.isError).toBe(true);
expect(res?.output).toContain('url is required');
});
it('rejects URLs outside learn.microsoft.com', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
const res = await executeTool('FetchMicrosoftLearn', { url: 'https://evil.example/docs' }, ctx);
expect(res?.isError).toBe(true);
expect(res?.output).toContain('not on learn.microsoft.com');
expect(fetchMock).not.toHaveBeenCalled();
});
it('fetches a page, converts to markdown, and caches it', async () => {
vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => htmlResponse(LEARN_HTML)));
const res = await executeTool(
'FetchMicrosoftLearn',
{ url: 'https://learn.microsoft.com/en-us/azure/durable?view=latest#anchor' },
ctx,
);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('Fetched and cached');
// Title cleaned of the "| Microsoft Learn" suffix.
expect(res?.output).toContain('# Durable Functions overview');
// Entity decoding, headings, lists, fenced code, links survive.
expect(res?.output).toContain('an & extension');
expect(res?.output).toContain('## Patterns');
expect(res?.output).toContain('- Function chaining');
expect(res?.output).toContain('```csharp\nvar x = 1;\n```');
expect(res?.output).toContain('[related docs](https://learn.microsoft.com/en-us/azure/other)');
// Chrome outside <article> is dropped.
expect(res?.output).not.toContain('site nav chrome');
expect(res?.output).not.toContain('footer chrome');
});
it('serves the cached copy (query/hash canonicalized away) without refetching', async () => {
const fetchMock = vi.fn();
vi.stubGlobal('fetch', fetchMock);
const res = await executeTool(
'FetchMicrosoftLearn',
{ url: 'https://learn.microsoft.com/en-us/azure/durable?other=param' },
ctx,
);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('Cached (age=');
expect(res?.output).toContain('Durable Functions overview');
expect(fetchMock).not.toHaveBeenCalled();
});
it('reports HTTP errors as tool errors', async () => {
vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => htmlResponse('nope', 404)));
const res = await executeTool(
'FetchMicrosoftLearn',
{ url: 'https://learn.microsoft.com/en-us/azure/missing' },
ctx,
);
expect(res?.isError).toBe(true);
expect(res?.output).toContain('HTTP 404');
});
});
describe('SearchMicrosoftLearnCache', () => {
it('requires a query', async () => {
const res = await executeTool('SearchMicrosoftLearnCache', {}, ctx);
expect(res?.isError).toBe(true);
});
it('finds previously cached pages via FTS', async () => {
const res = await executeTool('SearchMicrosoftLearnCache', { query: 'durable functions' }, ctx);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('Cache hits');
expect(res?.output).toContain('https://learn.microsoft.com/en-us/azure/durable');
});
it('reports zero hits without error', async () => {
const res = await executeTool('SearchMicrosoftLearnCache', { query: 'zzz-no-such-term' }, ctx);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('No cache hits');
});
});
describe('RefreshMicrosoftLearnCache', () => {
it('force-refetches and overwrites the cache', async () => {
const updated = LEARN_HTML.replace('an &amp; extension', 'a refreshed extension');
vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => htmlResponse(updated)));
const res = await executeTool(
'RefreshMicrosoftLearnCache',
{ url: 'https://learn.microsoft.com/en-us/azure/durable' },
ctx,
);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('Refreshed');
const cached = await executeTool(
'FetchMicrosoftLearn',
{ url: 'https://learn.microsoft.com/en-us/azure/durable' },
ctx,
);
expect(cached?.output).toContain('a refreshed extension');
});
it('surfaces refresh failures', async () => {
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('socket hang up')));
const res = await executeTool(
'RefreshMicrosoftLearnCache',
{ url: 'https://learn.microsoft.com/en-us/azure/durable' },
ctx,
);
expect(res?.isError).toBe(true);
expect(res?.output).toContain('socket hang up');
});
});
describe('SearchMicrosoftLearn', () => {
it('requires a query', async () => {
const res = await executeTool('SearchMicrosoftLearn', {}, ctx);
expect(res?.isError).toBe(true);
});
it('merges online results with cache hits and clamps top to 25', async () => {
const fetchMock = vi.fn().mockImplementation(async () =>
jsonResponse({
results: [
{ title: 'Durable Functions overview', url: 'https://learn.microsoft.com/en-us/azure/durable', description: 'desc' },
{ title: 'broken', description: 'no url, filtered out' },
],
}),
);
vi.stubGlobal('fetch', fetchMock);
const res = await executeTool(
'SearchMicrosoftLearn',
{ query: 'durable functions', top: 100, products: ['azure'] },
ctx,
);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('## Online results (1)');
// The cached page is flagged.
expect(res?.output).toContain('[cached]');
expect(res?.output).toContain('## Cache hits');
const calledUrl = String(fetchMock.mock.calls[0]?.[0]);
expect(calledUrl).toContain('%24top=25');
expect(calledUrl).toContain('products=azure');
});
it('falls back to cache-only results when the online API fails', async () => {
vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => jsonResponse({}, 503)));
const res = await executeTool('SearchMicrosoftLearn', { query: 'durable functions' }, ctx);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('## Cache hits');
expect(res?.output).toContain('online search failed');
});
it('errors when online fails and the cache has nothing', async () => {
vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => jsonResponse({}, 503)));
const res = await executeTool('SearchMicrosoftLearn', { query: 'zzz-no-such-term' }, ctx);
expect(res?.isError).toBe(true);
expect(res?.output).toContain('Search failed');
});
it('reports no results without error when both sources are empty', async () => {
vi.stubGlobal('fetch', vi.fn().mockImplementation(async () => jsonResponse({ results: [] })));
const res = await executeTool('SearchMicrosoftLearn', { query: 'zzz-no-such-term' }, ctx);
expect(res?.isError).toBe(false);
expect(res?.output).toContain('No results');
});
});