|
2 | 2 | * @vitest-environment node |
3 | 3 | */ |
4 | 4 |
|
5 | | -import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 5 | +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' |
6 | 6 | import { TOOL_RESULT_MAX_INLINE_CHARS } from '@/lib/copilot/constants' |
7 | 7 |
|
8 | 8 | const { getOrMaterializeVFS } = vi.hoisted(() => ({ |
@@ -405,3 +405,109 @@ describe('vfs uploads are opt-in (like recently-deleted/)', () => { |
405 | 405 | expect(grepChatUpload).toHaveBeenCalledWith('report.json', 'chat-1', 'x', expect.any(Object)) |
406 | 406 | }) |
407 | 407 | }) |
| 408 | + |
| 409 | +describe('vfs handlers docs corpus routing', () => { |
| 410 | + const fetchMock = vi.fn() |
| 411 | + const DOCS_PAGE = 'docs/workflows/blocks/agent.mdx' |
| 412 | + |
| 413 | + beforeEach(() => { |
| 414 | + vi.clearAllMocks() |
| 415 | + fetchMock.mockReset() |
| 416 | + vi.stubGlobal('fetch', fetchMock) |
| 417 | + }) |
| 418 | + |
| 419 | + afterEach(() => { |
| 420 | + vi.unstubAllGlobals() |
| 421 | + }) |
| 422 | + |
| 423 | + it('globs the docs corpus without materializing the workspace VFS', async () => { |
| 424 | + const result = await executeVfsGlob({ pattern: 'docs/**' }, GREP_CTX) |
| 425 | + |
| 426 | + expect(result.success).toBe(true) |
| 427 | + expect((result.output as { files: string[] }).files).toContain(DOCS_PAGE) |
| 428 | + expect(getOrMaterializeVFS).not.toHaveBeenCalled() |
| 429 | + }) |
| 430 | + |
| 431 | + it('reads a docs page via the live-site fetch, not the workspace VFS', async () => { |
| 432 | + fetchMock.mockResolvedValue({ ok: true, status: 200, text: async () => 'line one\nline two' }) |
| 433 | + |
| 434 | + const result = await executeVfsRead({ path: DOCS_PAGE }, GREP_CTX) |
| 435 | + |
| 436 | + expect(result.success).toBe(true) |
| 437 | + expect(result.output).toEqual({ content: 'line one\nline two', totalLines: 2 }) |
| 438 | + expect(getOrMaterializeVFS).not.toHaveBeenCalled() |
| 439 | + }) |
| 440 | + |
| 441 | + it('surfaces DocsCorpusError messages verbatim from read, without fetching', async () => { |
| 442 | + const unknown = await executeVfsRead({ path: 'docs/not-a-real-page.mdx' }, GREP_CTX) |
| 443 | + expect(unknown.success).toBe(false) |
| 444 | + expect(unknown.error).toContain('Docs page not found') |
| 445 | + |
| 446 | + const dir = await executeVfsRead({ path: 'docs/workflows/blocks' }, GREP_CTX) |
| 447 | + expect(dir.success).toBe(false) |
| 448 | + expect(dir.error).toContain('is a directory') |
| 449 | + expect(fetchMock).not.toHaveBeenCalled() |
| 450 | + }) |
| 451 | + |
| 452 | + it('greps exactly one docs page and rejects multi-page scopes verbatim', async () => { |
| 453 | + fetchMock.mockResolvedValue({ |
| 454 | + ok: true, |
| 455 | + status: 200, |
| 456 | + text: async () => 'alpha\ncron beta\ngamma', |
| 457 | + }) |
| 458 | + |
| 459 | + const single = await executeVfsGrep({ pattern: 'cron', path: DOCS_PAGE }, GREP_CTX) |
| 460 | + expect(single.success).toBe(true) |
| 461 | + |
| 462 | + const multi = await executeVfsGrep({ pattern: 'cron', path: 'docs/workflows' }, GREP_CTX) |
| 463 | + expect(multi.success).toBe(false) |
| 464 | + expect(multi.error).toContain('single page') |
| 465 | + expect(getOrMaterializeVFS).not.toHaveBeenCalled() |
| 466 | + }) |
| 467 | + |
| 468 | + it('truncates an oversized multi-line docs page to fit the inline cap', async () => { |
| 469 | + const line = 'y'.repeat(200) |
| 470 | + const totalLines = Math.ceil((TOOL_RESULT_MAX_INLINE_CHARS * 2) / (line.length + 1)) |
| 471 | + fetchMock.mockResolvedValue({ |
| 472 | + ok: true, |
| 473 | + status: 200, |
| 474 | + text: async () => Array.from({ length: totalLines }, () => line).join('\n'), |
| 475 | + }) |
| 476 | + |
| 477 | + const result = await executeVfsRead({ path: DOCS_PAGE }, GREP_CTX) |
| 478 | + |
| 479 | + expect(result.success).toBe(true) |
| 480 | + const output = result.output as { content: string; totalLines: number } |
| 481 | + expect(output.totalLines).toBe(totalLines) |
| 482 | + expect(output.content).toContain('[Page truncated: returned lines 1-') |
| 483 | + expect(JSON.stringify(output).length).toBeLessThanOrEqual(TOOL_RESULT_MAX_INLINE_CHARS) |
| 484 | + }) |
| 485 | + |
| 486 | + it('fails a docs page whose single line cannot fit inline instead of returning it oversized', async () => { |
| 487 | + fetchMock.mockResolvedValue({ |
| 488 | + ok: true, |
| 489 | + status: 200, |
| 490 | + text: async () => 'z'.repeat(TOOL_RESULT_MAX_INLINE_CHARS + 1000), |
| 491 | + }) |
| 492 | + |
| 493 | + const result = await executeVfsRead({ path: DOCS_PAGE }, GREP_CTX) |
| 494 | + |
| 495 | + expect(result.success).toBe(false) |
| 496 | + expect(result.error).toContain('Grep this page') |
| 497 | + }) |
| 498 | + |
| 499 | + it('rejects an explicit window that still overflows instead of truncating it', async () => { |
| 500 | + const line = 'y'.repeat(200) |
| 501 | + const totalLines = Math.ceil((TOOL_RESULT_MAX_INLINE_CHARS * 2) / (line.length + 1)) |
| 502 | + fetchMock.mockResolvedValue({ |
| 503 | + ok: true, |
| 504 | + status: 200, |
| 505 | + text: async () => Array.from({ length: totalLines }, () => line).join('\n'), |
| 506 | + }) |
| 507 | + |
| 508 | + const result = await executeVfsRead({ path: DOCS_PAGE, offset: 0, limit: totalLines }, GREP_CTX) |
| 509 | + |
| 510 | + expect(result.success).toBe(false) |
| 511 | + expect(result.error).toContain('still too large over the requested window') |
| 512 | + }) |
| 513 | +}) |
0 commit comments