106 lines
3.7 KiB
TypeScript
106 lines
3.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
getAttachmentInputAcceptForMember,
|
|
getMemberAttachmentUnavailableReason,
|
|
validateAttachmentFilesForMember,
|
|
validateAttachmentPayloadsForMember,
|
|
} from '../../../src/renderer/utils/attachmentRecipientCapabilities';
|
|
|
|
import type { AttachmentPayload, ResolvedTeamMember } from '../../../src/shared/types';
|
|
|
|
function member(overrides: Partial<ResolvedTeamMember>): ResolvedTeamMember {
|
|
return {
|
|
name: 'bob',
|
|
status: 'idle',
|
|
currentTaskId: null,
|
|
taskCount: 0,
|
|
lastActiveAt: null,
|
|
messageCount: 0,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function file(name: string, type: string, bytes = 12): File {
|
|
return new File([new Uint8Array(bytes)], name, { type });
|
|
}
|
|
|
|
function payload(overrides: Partial<AttachmentPayload>): AttachmentPayload {
|
|
return {
|
|
id: 'att-1',
|
|
filename: 'diagram.png',
|
|
mimeType: 'image/png',
|
|
size: 12,
|
|
data: 'aW1n',
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('attachmentRecipientCapabilities', () => {
|
|
it('blocks OpenCode non-vision models before file selection or send', () => {
|
|
const bob = member({
|
|
providerId: 'opencode',
|
|
model: 'openrouter/z-ai/glm-5.1',
|
|
});
|
|
|
|
expect(getMemberAttachmentUnavailableReason(bob)).toBe(
|
|
'This OpenCode model is not verified for image attachments. Choose a vision-capable model or remove the image.'
|
|
);
|
|
expect(validateAttachmentFilesForMember({ member: bob, files: [file('diagram.png', 'image/png')] })).toBe(
|
|
'This OpenCode model is not verified for image attachments. Choose a vision-capable model or remove the image.'
|
|
);
|
|
expect(validateAttachmentPayloadsForMember({ member: bob, attachments: [payload({})] })).toBe(
|
|
'This OpenCode model is not verified for image attachments. Choose a vision-capable model or remove the image.'
|
|
);
|
|
});
|
|
|
|
it('allows image picker input for verified OpenCode vision models', () => {
|
|
const bob = member({
|
|
providerId: 'opencode',
|
|
model: 'openrouter/moonshotai/kimi-k2.6',
|
|
});
|
|
|
|
expect(getMemberAttachmentUnavailableReason(bob)).toBeNull();
|
|
expect(getAttachmentInputAcceptForMember(bob)).toBe('image/png,image/jpeg,image/webp');
|
|
expect(validateAttachmentFilesForMember({ member: bob, files: [file('diagram.png', 'image/png')] })).toBeNull();
|
|
expect(validateAttachmentPayloadsForMember({ member: bob, attachments: [payload({})] })).toBeNull();
|
|
});
|
|
|
|
it('blocks non-image files for image-only providers', () => {
|
|
const codexLead = member({
|
|
name: 'lead',
|
|
agentType: 'team-lead',
|
|
providerId: 'codex',
|
|
model: 'gpt-5.5',
|
|
});
|
|
|
|
expect(validateAttachmentFilesForMember({ member: codexLead, files: [file('notes.md', 'text/markdown')] })).toBe(
|
|
'This provider path currently supports image attachments only. Non-image files are blocked before provider delivery.'
|
|
);
|
|
expect(
|
|
validateAttachmentPayloadsForMember({
|
|
member: codexLead,
|
|
attachments: [payload({ filename: 'notes.md', mimeType: 'text/plain' })],
|
|
})
|
|
).toBe(
|
|
'This provider path currently supports image attachments only. Non-image files are blocked before provider delivery.'
|
|
);
|
|
});
|
|
|
|
it('allows text/PDF files for Anthropic lead recipients', () => {
|
|
const anthropicLead = member({
|
|
name: 'lead',
|
|
agentType: 'team-lead',
|
|
providerId: 'anthropic',
|
|
model: 'claude-opus-4-6',
|
|
});
|
|
|
|
expect(validateAttachmentFilesForMember({ member: anthropicLead, files: [file('brief.pdf', 'application/pdf')] })).toBeNull();
|
|
expect(
|
|
validateAttachmentPayloadsForMember({
|
|
member: anthropicLead,
|
|
attachments: [payload({ filename: 'brief.pdf', mimeType: 'application/pdf' })],
|
|
})
|
|
).toBeNull();
|
|
});
|
|
});
|