77 lines
6.3 KiB
TypeScript
77 lines
6.3 KiB
TypeScript
import {test, expect} from '@playwright/test';
|
|
import {chooseStoryVideoSettings, filterCanvasModels, getCanvasVideoModes, getCanvasVideoParameters, validateCanvasVideoInputs} from '@/lib/video-canvas/model-matching';
|
|
|
|
const language = {id: 'text', modelId: 'agnes-1.5-flash', providerType:'custom', supportsVision:true, capabilities:{kind:'text', provider:'agnes-ai'}};
|
|
const image = {id:'image', modelId:'gpt-image-2', providerType:'vip98k', supportsImageGen:true, supportsVision:true, capabilities:{kind:'image'}};
|
|
const threeD = {id:'3d', modelId:'tencent/hunyuan3d-pro/image-to-3d', providerType:'atlascloud', supportsVision:true, capabilities:{kind:'3d'}};
|
|
const video = {id:'video', modelId:'bytedance/seedance-2.0-mini/image-to-video', providerType:'atlascloud', capabilities:{kind:'video', supportedModes:['first-frame','first-last-frame'], supportedResolutions:['720p','1080p'], supportedRatios:['16:9','9:16'], durationOptions:[5,10]}};
|
|
|
|
test('never offers text, image or 3D models when video is unavailable', () => {
|
|
expect(filterCanvasModels([language,image,threeD], 'video')).toEqual([]);
|
|
});
|
|
test('vision analysis excludes image generation and 3D models even when supportsVision is true', () => {
|
|
expect(filterCanvasModels([language,image,threeD,video], 'multimodal')).toEqual([language]);
|
|
});
|
|
test('video choices honor enabled status, executable adapter and exact input mode', () => {
|
|
expect(filterCanvasModels([video], 'video','text-to-video')).toEqual([]);
|
|
expect(filterCanvasModels([video], 'video','first-last-frame')).toEqual([video]);
|
|
expect(filterCanvasModels([{...video,isEnabled:false}], 'video')).toEqual([]);
|
|
expect(filterCanvasModels([{...video,providerType:'unimplemented'}], 'video')).toEqual([]);
|
|
});
|
|
test('normalizes provider mode aliases without inventing unknown capabilities', () => {
|
|
expect(getCanvasVideoModes({...video, capabilities:{kind:'video',supportedModes:['image-to-video','reference-to-video','made-up']}})).toEqual(['first-frame','reference-images']);
|
|
expect(getCanvasVideoModes({...video,capabilities:{kind:'video'}})).toEqual([]);
|
|
});
|
|
test('image editing rejects a model that does not accept references', () => {
|
|
expect(filterCanvasModels([{...image,supportsVision:false}], 'image-edit')).toEqual([]);
|
|
expect(filterCanvasModels([image], 'image-edit')).toEqual([image]);
|
|
});
|
|
test('mode parameter constraints override generic choices and never add 4K', () => {
|
|
const options = getCanvasVideoParameters({...video,capabilities:{...video.capabilities,modeConstraints:{'first-last-frame':{durationOptions:[5],resolutionOptions:['720p'],ratioOptions:['16:9']}}}},'first-last-frame');
|
|
expect(options).toMatchObject({durations:[5],resolutions:['720p'],ratios:['16:9']});
|
|
expect(getCanvasVideoParameters(undefined).resolutions).toEqual([]);
|
|
});
|
|
test('preserves configured discrete duration choices', () => {
|
|
expect(getCanvasVideoParameters(video).durations).toEqual([5,10]);
|
|
});
|
|
test('story generation never submits a 4K-only model through the 1080P pipeline', () => {
|
|
const textVideo = {...video, capabilities:{...video.capabilities, supportedModes:['text-to-video'], supportedResolutions:['4k']}};
|
|
expect(() => chooseStoryVideoSettings([textVideo], false, 5)).toThrow('可执行');
|
|
expect(chooseStoryVideoSettings([{...textVideo, capabilities:{...textVideo.capabilities, supportedResolutions:['4k','1080p']}}], false, 5).resolution).toBe('1080p');
|
|
});
|
|
test('rejects missing tail frame and references silently attached to text-to-video', () => {
|
|
expect(()=>validateCanvasVideoInputs('first-last-frame',['head'])).toThrow('两张');
|
|
expect(()=>validateCanvasVideoInputs('text-to-video',['head'])).toThrow('切换');
|
|
expect(()=>validateCanvasVideoInputs('reference-images',[])).toThrow('至少');
|
|
expect(()=>validateCanvasVideoInputs('first-frame',['head','tail'])).toThrow('一张');
|
|
expect(()=>validateCanvasVideoInputs('first-last-frame',['head','tail'])).not.toThrow();
|
|
});
|
|
|
|
import {buildVip98kModelPresets} from '@/lib/ai/vip98k-models';
|
|
import {getVip98kImageBindingKeys, selectVip98kTaskInvocation} from '@/lib/ai/vip98k-task-invocation';
|
|
test('reads task schema rather than a text-only example for Veo frame and ingredient inputs', () => {
|
|
const model:any = {id:'veo-3.1-fast',type:'video',status:'published',available:true,capabilities:['media.tasks'],input_modes:['prompt','url'],output_modes:['video'],supported_resolutions:['720p','1080p'],duration:{allowed_seconds:[8]},invocation:{kind:'task',path:'/extensions/v1/tasks',method:'POST',request_example:{model:'veo-3.1-fast',prompt:'test'},input_schema:{properties:{prompt:{type:'string'},mode_image:{enum:['frame','ingredient']},file_urls:{type:'array',maxItems:3},aspect_ratio:{enum:['16:9']}}}}};
|
|
const preset = buildVip98kModelPresets([model])[0];
|
|
expect(preset.capabilities).toMatchObject({supportedModes:['text-to-video','first-frame','first-last-frame','reference-images'],durationOptions:[8],supportedRatios:['16:9'],maxReferenceImages:3});
|
|
const invocation:any = (preset.capabilities.vip98k as any).invocation;
|
|
expect(getVip98kImageBindingKeys(invocation)).toContain('file_urls');
|
|
expect(selectVip98kTaskInvocation({modelId:model.id,configuredInvocation:invocation,imageCount:2})?.path).toBe('/extensions/v1/tasks');
|
|
});
|
|
|
|
import {toVideoPixelRect} from '@/lib/video-canvas/media-processing';
|
|
import {isPublicMediaIpv4} from '@/lib/video-canvas/download-media';
|
|
test('video crop preserves selected geometry with even output dimensions', () => {
|
|
expect(toVideoPixelRect({x:.1,y:.1,width:.8,height:.8},1920,1080)).toEqual({x:192,y:108,width:1536,height:864});
|
|
expect(()=>toVideoPixelRect({x:.9,y:0,width:.8,height:1},1920,1080)).toThrow();
|
|
expect(()=>toVideoPixelRect({x:NaN,y:0,width:1,height:1},1920,1080)).toThrow();
|
|
});
|
|
test('video downloads reject private, metadata and loopback networks', () => {
|
|
for(const ip of ['127.0.0.1','10.0.0.1','169.254.169.254','172.16.0.1','192.168.1.1','100.64.0.1','0.0.0.0','224.0.0.1']) expect(isPublicMediaIpv4(ip)).toBe(false);
|
|
expect(isPublicMediaIpv4('8.8.8.8')).toBe(true);
|
|
});
|
|
test('audio-driven video requires a real audio reference contract', () => {
|
|
expect(filterCanvasModels([video], 'audio-video')).toEqual([]);
|
|
const audioVideo={...video,imageGenConfig:{atlasCloud:{propertyNames:['reference_audios']}}};
|
|
expect(filterCanvasModels([audioVideo], 'audio-video')).toEqual([audioVideo]);
|
|
});
|