import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { webFetch, webSearch } from '../../src/tools/web.js' describe('webFetch', () => { let originalFetch: typeof fetch beforeEach(() => { originalFetch = global.fetch }) afterEach(() => { global.fetch = originalFetch }) it('errors when url missing', async () => { expect(await webFetch({})).toContain('Error: url is required') }) it('returns error on fetch throw', async () => { global.fetch = vi.fn(async () => { throw new Error('network down') }) as any const out = await webFetch({ url: 'https://example.com' }) expect(out).toContain('Error fetching') expect(out).toContain('network down') }) it('returns error on non-OK status', async () => { global.fetch = vi.fn(async () => ({ ok: false, status: 404, statusText: 'Not Found', url: 'https://example.com', headers: new Headers(), body: null, })) as any const out = await webFetch({ url: 'https://example.com' }) expect(out).toContain('404') expect(out).toContain('Not Found') }) it('reports redirect when host changes', async () => { global.fetch = vi.fn(async () => ({ ok: true, status: 200, url: 'https://other.com/x', headers: new Headers({ 'content-type': 'text/html' }), body: null, })) as any const out = await webFetch({ url: 'https://example.com' }) expect(out).toContain('[REDIRECT]') }) it('converts simple HTML to markdown', async () => { const html = '

Title

hello world

' const stream = new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode(html)); c.close() } }) global.fetch = vi.fn(async () => ({ ok: true, status: 200, url: 'https://example.com', headers: new Headers({ 'content-type': 'text/html' }), body: stream, })) as any const out = await webFetch({ url: 'https://example.com' }) expect(out).toContain('# Title') expect(out).toContain('hello world') }) it('appends prompt block when given', async () => { const stream = new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode('plain content')); c.close() } }) global.fetch = vi.fn(async () => ({ ok: true, status: 200, url: 'https://example.com', headers: new Headers({ 'content-type': 'text/plain' }), body: stream, })) as any const out = await webFetch({ url: 'https://example.com', prompt: 'summarize' }) expect(out).toContain('User prompt: summarize') }) it('handles empty body', async () => { const stream = new ReadableStream({ start(c) { c.close() } }) global.fetch = vi.fn(async () => ({ ok: true, status: 200, url: 'https://example.com', headers: new Headers({ 'content-type': 'text/html' }), body: stream, })) as any const out = await webFetch({ url: 'https://example.com' }) expect(out).toContain('empty body') }) }) describe('webSearch', () => { let originalFetch: typeof fetch beforeEach(() => { originalFetch = global.fetch }) afterEach(() => { global.fetch = originalFetch }) it('errors when query missing', async () => { expect(await webSearch({})).toContain('Error: query is required') }) it('error path on fetch throw', async () => { global.fetch = vi.fn(async () => { throw new Error('boom') }) as any const out = await webSearch({ query: 'rust async' }) expect(out).toContain('Error de búsqueda') }) it('error path on non-OK', async () => { global.fetch = vi.fn(async () => ({ ok: false, status: 500, statusText: 'Server Error', headers: new Headers(), body: null, })) as any const out = await webSearch({ query: 'x' }) expect(out).toContain('500') }) it('returns "no results" when DDG HTML has no result blocks', async () => { const stream = new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode('nothing here')) c.close() } }) global.fetch = vi.fn(async () => ({ ok: true, status: 200, headers: new Headers(), body: stream, })) as any const out = await webSearch({ query: 'unique-query' }) expect(out).toContain('Sin resultados') }) it('parses results when present', async () => { const html = `
Title 1 Snippet 1
Title 2 Snippet 2
` const stream = new ReadableStream({ start(c) { c.enqueue(new TextEncoder().encode(html)); c.close() } }) global.fetch = vi.fn(async () => ({ ok: true, status: 200, headers: new Headers(), body: stream, })) as any const out = await webSearch({ query: 'q' }) // Parsing of the regex-based DDG output is brittle; at least we don't crash // and we either return Sin resultados or include the markdown header. expect(out).toBeTruthy() }) })