255 lines
6.8 KiB
TypeScript
255 lines
6.8 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { createCallResult, describeConnectionIssue } from '../src/result-utils.js';
|
|
|
|
describe('result-utils connection helpers', () => {
|
|
it('describes connection issues for offline errors', () => {
|
|
const issue = describeConnectionIssue(new Error('fetch failed: connect ECONNREFUSED 127.0.0.1:9999'));
|
|
expect(issue.kind).toBe('offline');
|
|
});
|
|
});
|
|
|
|
describe('createCallResult text extraction', () => {
|
|
it('extracts text from content array at top level', () => {
|
|
const response = {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'Hello World',
|
|
},
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.text()).toBe('Hello World');
|
|
});
|
|
|
|
it('extracts text from content array nested inside raw wrapper', () => {
|
|
const response = {
|
|
raw: {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'Available pages for stanfordnlp/dspy:\n\n- 1 Overview',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.text()).toBe('Available pages for stanfordnlp/dspy:\n\n- 1 Overview');
|
|
});
|
|
|
|
it('extracts multiple text entries and joins them', () => {
|
|
const response = {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'First part',
|
|
},
|
|
{
|
|
type: 'text',
|
|
text: 'Second part',
|
|
},
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.text()).toBe('First part\nSecond part');
|
|
});
|
|
|
|
it('returns null when no text content is available', () => {
|
|
const response = {
|
|
content: [
|
|
{
|
|
type: 'image',
|
|
data: 'base64...',
|
|
},
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.text()).toBe(null);
|
|
});
|
|
|
|
it('returns string when raw is already a string', () => {
|
|
const response = 'Simple string response';
|
|
const result = createCallResult(response);
|
|
expect(result.text()).toBe('Simple string response');
|
|
});
|
|
});
|
|
|
|
describe('createCallResult image extraction', () => {
|
|
it('extracts image blocks from content', () => {
|
|
const response = {
|
|
content: [
|
|
{ type: 'image', mimeType: 'image/png', data: 'aGVsbG8=' },
|
|
{ type: 'image', mimeType: 'image/jpeg', data: 'd29ybGQ=' },
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.images()).toEqual([
|
|
{ mimeType: 'image/png', data: 'aGVsbG8=' },
|
|
{ mimeType: 'image/jpeg', data: 'd29ybGQ=' },
|
|
]);
|
|
});
|
|
|
|
it('extracts image blocks nested under raw.content', () => {
|
|
const response = {
|
|
raw: {
|
|
content: [{ type: 'image', data: 'aGVsbG8=' }],
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.images()).toEqual([{ mimeType: 'image/png', data: 'aGVsbG8=' }]);
|
|
});
|
|
|
|
it('returns null when no images exist', () => {
|
|
const response = {
|
|
content: [{ type: 'text', text: 'no image here' }],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.images()).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('createCallResult markdown extraction', () => {
|
|
it('extracts markdown from content array', () => {
|
|
const response = {
|
|
content: [
|
|
{
|
|
type: 'markdown',
|
|
text: '# Header\n\nContent',
|
|
},
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.markdown()).toBe('# Header\n\nContent');
|
|
});
|
|
|
|
it('extracts markdown from content array nested inside raw wrapper', () => {
|
|
const response = {
|
|
raw: {
|
|
content: [
|
|
{
|
|
type: 'markdown',
|
|
text: '## Subtitle',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.markdown()).toBe('## Subtitle');
|
|
});
|
|
|
|
it('extracts markdown from structuredContent nested inside raw wrapper', () => {
|
|
const response = {
|
|
raw: {
|
|
structuredContent: {
|
|
markdown: '_italic_',
|
|
},
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.markdown()).toBe('_italic_');
|
|
});
|
|
});
|
|
|
|
describe('createCallResult json extraction', () => {
|
|
it('returns null for text-only content', () => {
|
|
const response = {
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: 'Plain text',
|
|
},
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.json()).toBe(null);
|
|
});
|
|
|
|
it('extracts json from content array with json type', () => {
|
|
const response = {
|
|
content: [
|
|
{
|
|
type: 'json',
|
|
json: { foo: 'bar' },
|
|
},
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.json()).toEqual({ foo: 'bar' });
|
|
});
|
|
|
|
it('returns all items when content array has multiple json entries', () => {
|
|
const response = {
|
|
content: [
|
|
{ type: 'json', json: { id: 1, name: 'first' } },
|
|
{ type: 'json', json: { id: 2, name: 'second' } },
|
|
{ type: 'json', json: { id: 3, name: 'third' } },
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.json()).toEqual([
|
|
{ id: 1, name: 'first' },
|
|
{ id: 2, name: 'second' },
|
|
{ id: 3, name: 'third' },
|
|
]);
|
|
});
|
|
|
|
it('returns all items when content has mixed json and text-parseable-as-json entries', () => {
|
|
const response = {
|
|
content: [
|
|
{ type: 'json', json: { source: 'json-type' } },
|
|
{ type: 'text', text: '{"source":"text-type"}' },
|
|
],
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.json()).toEqual([{ source: 'json-type' }, { source: 'text-type' }]);
|
|
});
|
|
|
|
it('extracts json from structuredContent nested inside raw wrapper', () => {
|
|
const response = {
|
|
raw: {
|
|
structuredContent: {
|
|
json: { nested: true },
|
|
},
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.json()).toEqual({ nested: true });
|
|
});
|
|
});
|
|
|
|
describe('createCallResult structured accessors', () => {
|
|
it('content() returns nested raw content array', () => {
|
|
const nested = [{ type: 'text', text: 'Hello' }];
|
|
const response = {
|
|
raw: {
|
|
content: nested,
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.content()).toBe(nested);
|
|
});
|
|
|
|
it('structuredContent() returns nested raw structuredContent', () => {
|
|
const structured = { text: 'Inner text' };
|
|
const response = {
|
|
raw: {
|
|
structuredContent: structured,
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.structuredContent()).toBe(structured);
|
|
});
|
|
|
|
it('text() falls back to structuredContent.text when no content exists', () => {
|
|
const response = {
|
|
raw: {
|
|
structuredContent: {
|
|
text: 'Structured fallback',
|
|
},
|
|
},
|
|
};
|
|
const result = createCallResult(response);
|
|
expect(result.text()).toBe('Structured fallback');
|
|
});
|
|
});
|