File size: 4,670 Bytes
7c5ed63 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import { describe, it, expect } from 'vitest';
import { resolveBlobRefUrl, isSafeBlobHash, rehydrateWireEntries } from '../../src/lib/blob-resolver';
describe('blob-resolver', () => {
describe('resolveBlobRefUrl', () => {
it('converts a well-formed blobref into a relative route by default', () => {
const url = resolveBlobRefUrl(
'blobref:image/png;abc123def456',
'sess-1',
'main',
);
expect(url).toBe(
'/api/sessions/sess-1/blobs/abc123def456?agent=main&mime=image%2Fpng',
);
});
it('returns an absolute URL when baseUrl is provided', () => {
const url = resolveBlobRefUrl(
'blobref:image/png;abc123def456',
'sess-1',
'main',
'http://localhost:3001',
);
expect(url).toBe(
'http://localhost:3001/api/sessions/sess-1/blobs/abc123def456?agent=main&mime=image%2Fpng',
);
});
it('returns non-blobref URLs unchanged', () => {
expect(resolveBlobRefUrl('https://example.com/x.png', 's', 'a')).toBe(
'https://example.com/x.png',
);
expect(resolveBlobRefUrl('data:image/png;base64,abc', 's', 'a')).toBe(
'data:image/png;base64,abc',
);
});
it('returns malformed blobrefs unchanged', () => {
expect(resolveBlobRefUrl('blobref:nosemicolon', 's', 'a')).toBe(
'blobref:nosemicolon',
);
expect(resolveBlobRefUrl('blobref:image/png;', 's', 'a')).toBe(
'blobref:image/png;',
);
});
});
describe('isSafeBlobHash', () => {
it('accepts a 64-char hex string', () => {
expect(isSafeBlobHash('a'.repeat(64))).toBe(true);
expect(isSafeBlobHash('0'.repeat(64))).toBe(true);
expect(isSafeBlobHash('f'.repeat(64))).toBe(true);
});
it('rejects non-hex, wrong length, and path-traversal strings', () => {
expect(isSafeBlobHash('')).toBe(false);
expect(isSafeBlobHash('a'.repeat(63))).toBe(false);
expect(isSafeBlobHash('a'.repeat(65))).toBe(false);
expect(isSafeBlobHash('x'.repeat(64))).toBe(false);
expect(isSafeBlobHash('../etc/passwd')).toBe(false);
expect(isSafeBlobHash('a'.repeat(64) + '\n')).toBe(false);
});
});
describe('rehydrateWireEntries', () => {
it('mutates entry.data but leaves entry.raw untouched', () => {
const data: Record<string, unknown> = {
type: 'turn.prompt',
input: [
{
type: 'image_url',
imageUrl: { url: 'blobref:image/png;hashA' },
},
],
};
const raw = JSON.parse(JSON.stringify(data));
const entries = [{ lineNo: 1, data: data as any, raw }];
rehydrateWireEntries(entries, 'sess-1', 'main');
expect((entries[0]!.data as any).input[0].imageUrl.url).toBe(
'/api/sessions/sess-1/blobs/hashA?agent=main&mime=image%2Fpng',
);
expect((entries[0]!.raw as any).input[0].imageUrl.url).toBe(
'blobref:image/png;hashA',
);
});
it('handles audio_url, video_url, and nested tool result parts', () => {
const data: Record<string, unknown> = {
type: 'context.append_loop_event',
event: {
type: 'tool.result',
result: {
output: [
{
type: 'audio_url',
audioUrl: { url: 'blobref:audio/wav;hashB' },
},
],
},
},
};
const entries = [{ lineNo: 1, data: data as any, raw: {} }];
rehydrateWireEntries(entries, 'sess-2', 'sub-1');
const parts = (entries[0]!.data as any).event.result.output;
expect(parts[0].audioUrl.url).toBe(
'/api/sessions/sess-2/blobs/hashB?agent=sub-1&mime=audio%2Fwav',
);
});
it('resolves blobrefs to absolute URLs when baseUrl is provided', () => {
const data: Record<string, unknown> = {
type: 'turn.prompt',
input: [
{
type: 'image_url',
imageUrl: { url: 'blobref:image/png;hashC' },
},
],
};
const entries = [{ lineNo: 1, data: data as any, raw: {} }];
rehydrateWireEntries(entries, 'sess-3', 'main', 'http://localhost:3001');
expect((entries[0]!.data as any).input[0].imageUrl.url).toBe(
'http://localhost:3001/api/sessions/sess-3/blobs/hashC?agent=main&mime=image%2Fpng',
);
});
it('ignores records without media URLs', () => {
const data = { type: 'config.update', cwd: '/tmp' };
const entries = [{ lineNo: 1, data: data as any, raw: {} }];
rehydrateWireEntries(entries, 's', 'a');
expect(entries[0]!.data).toEqual(data);
});
});
});
|