216 lines
12 KiB
TypeScript
216 lines
12 KiB
TypeScript
import { TestBed } from '@angular/core/testing';
|
|
import { provideHttpClient } from '@angular/common/http';
|
|
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
|
import { ModelSettingsComponent } from './model-settings.component';
|
|
import { CapabilitySettingsComponent } from './capability-settings.component';
|
|
|
|
describe('持久化模型与功能设置', () => {
|
|
let http: HttpTestingController;
|
|
const config = { base_url: 'https://example.test/v1', model: 'mimo-v2.6-flash',
|
|
api_key_configured: true, timeout_seconds: 300, max_retries: 5, retry_interval_seconds: 10, reasoning_effort: 'high',
|
|
rate_limit_retry_seconds: 60, tool_fallback_model: 'gpt-6-luna', tool_fallback_reasoning_effort: 'high',
|
|
tool_fallback_max_retries: 3, tool_fallback_no_progress_attempts: 3,
|
|
model_presets: ['gpt-5.6-luna', 'gpt-6-luna', 'mimo-v2.6-flash', 'mimo-v2.6-pro'], env_path: '/app/.env' };
|
|
beforeEach(() => {
|
|
TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting()] });
|
|
http = TestBed.inject(HttpTestingController);
|
|
});
|
|
afterEach(() => http.verify());
|
|
|
|
it('加载配置后保留密钥,保存新模型且不发送空密钥', () => {
|
|
const fixture = TestBed.createComponent(ModelSettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/model-settings').flush(config);
|
|
const component = fixture.componentInstance;
|
|
expect(component.apiKey).toBe('');
|
|
expect(component.keyConfigured()).toBeTrue();
|
|
component.model = 'another-model';
|
|
component.save();
|
|
const request = http.expectOne('/api/system/model-settings');
|
|
expect(request.request.method).toBe('POST');
|
|
expect(request.request.body.model).toBe('another-model');
|
|
expect(request.request.body.reasoning_effort).toBe('high');
|
|
expect(request.request.body.timeout_seconds).toBe(300);
|
|
expect(request.request.body.max_retries).toBe(5);
|
|
expect(request.request.body.retry_interval_seconds).toBe(10);
|
|
expect(request.request.body.rate_limit_retry_seconds).toBe(60);
|
|
expect(request.request.body.tool_fallback_model).toBe('gpt-6-luna');
|
|
expect(request.request.body.tool_fallback_no_progress_attempts).toBe(3);
|
|
expect(request.request.body.api_key).toBeUndefined();
|
|
request.flush({ ...config, model: 'another-model' });
|
|
expect(component.busy()).toBeNull();
|
|
expect(component.message()).toContain('保存');
|
|
});
|
|
|
|
it('预设按钮填入名称,自定义模型与 max 可以保存并重新加载', async () => {
|
|
const fixture = TestBed.createComponent(ModelSettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/model-settings').flush(config);
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
const root: HTMLElement = fixture.nativeElement;
|
|
const presets = root.querySelectorAll<HTMLButtonElement>('.model-presets button');
|
|
expect(Array.from(presets, button => button.textContent?.trim())).toEqual([
|
|
'gpt-5.6-luna', 'gpt-6-luna', 'mimo-v2.6-flash', 'mimo-v2.6-pro'
|
|
]);
|
|
presets[0].click();
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
const input = root.querySelector<HTMLInputElement>('#model-name')!;
|
|
expect(input.value).toBe('gpt-5.6-luna');
|
|
expect(presets[0].getAttribute('aria-pressed')).toBe('true');
|
|
presets[1].click();
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
expect(input.value).toBe('gpt-6-luna');
|
|
presets[3].click();
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
expect(input.value).toBe('mimo-v2.6-pro');
|
|
expect(input.getAttribute('list')).toBeNull();
|
|
expect(input.autocomplete).toBe('off');
|
|
input.value = 'custom/model-v2';
|
|
input.dispatchEvent(new Event('input'));
|
|
const reasoning = root.querySelector<HTMLInputElement>('input[aria-label="思考强度"]')!;
|
|
reasoning.value = '7';
|
|
reasoning.dispatchEvent(new Event('input'));
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
expect(presets[0].getAttribute('aria-pressed')).toBe('false');
|
|
root.querySelector<HTMLFormElement>('form')!.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
const request = http.expectOne('/api/system/model-settings');
|
|
expect(request.request.body.model).toBe('custom/model-v2');
|
|
expect(request.request.body.reasoning_effort).toBe('max');
|
|
const saved = { ...config, model: 'custom/model-v2', reasoning_effort: 'max' };
|
|
request.flush(saved);
|
|
fixture.destroy();
|
|
const reopened = TestBed.createComponent(ModelSettingsComponent);
|
|
reopened.detectChanges();
|
|
http.expectOne('/api/system/model-settings').flush(saved);
|
|
reopened.detectChanges();
|
|
await reopened.whenStable();
|
|
expect(reopened.nativeElement.querySelector('#model-name').value).toBe('custom/model-v2');
|
|
expect(reopened.nativeElement.querySelector('input[aria-label="思考强度"]').getAttribute('aria-valuetext')).toBe('最高 · max');
|
|
});
|
|
|
|
it('高级稳定性设置可编辑并保存', async () => {
|
|
const fixture = TestBed.createComponent(ModelSettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/model-settings').flush(config);
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
const root: HTMLElement = fixture.nativeElement;
|
|
root.querySelector<HTMLDetailsElement>('.recovery-settings')!.open = true;
|
|
const fallback = root.querySelector<HTMLInputElement>('input[name="toolFallbackModel"]')!;
|
|
fallback.value = 'custom/fallback';
|
|
fallback.dispatchEvent(new Event('input'));
|
|
const wait = root.querySelector<HTMLInputElement>('input[name="rateLimitRetry"]')!;
|
|
wait.value = '90';
|
|
wait.dispatchEvent(new Event('input'));
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
root.querySelector<HTMLFormElement>('form')!.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
const request = http.expectOne('/api/system/model-settings');
|
|
expect(request.request.body.tool_fallback_model).toBe('custom/fallback');
|
|
expect(request.request.body.rate_limit_retry_seconds).toBe(90);
|
|
request.flush({ ...config, tool_fallback_model: 'custom/fallback', rate_limit_retry_seconds: 90 });
|
|
});
|
|
|
|
it('连接测试使用当前输入而不写入保存接口', () => {
|
|
const fixture = TestBed.createComponent(ModelSettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/model-settings').flush(config);
|
|
fixture.componentInstance.test();
|
|
const request = http.expectOne('/api/system/model-settings/test');
|
|
expect(request.request.body.model).toBe('mimo-v2.6-flash');
|
|
request.flush({ message: 'Responses 连接测试成功' });
|
|
http.expectNone('/api/system/model-settings');
|
|
expect(fixture.componentInstance.message()).toContain('成功');
|
|
});
|
|
|
|
it('加载失败时保持功能开关不可保存,避免覆盖现有设置', () => {
|
|
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/capability-settings').flush({}, { status: 500, statusText: 'Error' });
|
|
expect(fixture.componentInstance.error()).toContain('加载失败');
|
|
fixture.componentInstance.save();
|
|
http.expectNone('/api/system/capability-settings');
|
|
});
|
|
|
|
it('录屏与视频分析独立保存,重新打开后保留各自状态', () => {
|
|
const capabilities = { adb_auto_connect: true, adb_discovery_interval_seconds: 10, adb_discovery_timeout_seconds: 5, video_enabled: true, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: false, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
|
|
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/capability-settings').flush(capabilities);
|
|
fixture.componentInstance.config.video_enabled = false;
|
|
fixture.componentInstance.config.recording_enabled = true;
|
|
fixture.componentInstance.config.screenshot_limit_enabled = false;
|
|
fixture.componentInstance.save();
|
|
const request = http.expectOne('/api/system/capability-settings');
|
|
expect(request.request.body.video_enabled).toBeFalse();
|
|
expect(request.request.body.recording_enabled).toBeTrue();
|
|
expect(request.request.body.screenshot_limit_enabled).toBeFalse();
|
|
const saved = { ...capabilities, video_enabled: false, recording_enabled: true, screenshot_limit_enabled: false };
|
|
request.flush(saved);
|
|
fixture.destroy();
|
|
const reopened = TestBed.createComponent(CapabilitySettingsComponent);
|
|
reopened.detectChanges();
|
|
http.expectOne('/api/system/capability-settings').flush(saved);
|
|
expect(reopened.componentInstance.config.video_enabled).toBeFalse();
|
|
expect(reopened.componentInstance.config.recording_enabled).toBeTrue();
|
|
expect(reopened.componentInstance.config.screenshot_limit_enabled).toBeFalse();
|
|
});
|
|
|
|
it('执行保护默认折叠,修改时限可保存且拒绝无效数值', async () => {
|
|
const capabilities = { adb_auto_connect: true, adb_discovery_interval_seconds: 10, adb_discovery_timeout_seconds: 5, video_enabled: false, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: true, screenshot_limit_enabled: true, screenshot_max_long_edge: 2048, task_timeout_seconds: 1800, observation_timeout_seconds: 60, observation_max_attempts: 3, device_command_timeout_seconds: 10, action_failure_attempts: 3, worker_cleanup_timeout_seconds: 30 };
|
|
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/capability-settings').flush(capabilities);
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
const root: HTMLElement = fixture.nativeElement;
|
|
expect(root.querySelector<HTMLDetailsElement>('.execution-settings')!.open).toBeFalse();
|
|
root.querySelector<HTMLElement>('summary')!.click();
|
|
const timeout = root.querySelector<HTMLInputElement>('input[type="number"]')!;
|
|
timeout.value = '3600';
|
|
timeout.dispatchEvent(new Event('input'));
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
root.querySelector<HTMLButtonElement>('button')!.click();
|
|
const request = http.expectOne('/api/system/capability-settings');
|
|
expect(request.request.body.task_timeout_seconds).toBe(3600);
|
|
expect(request.request.body.observation_timeout_seconds).toBe(60);
|
|
request.flush({ ...capabilities, task_timeout_seconds: 3600 });
|
|
expect(fixture.componentInstance.message()).toContain('已保存');
|
|
timeout.value = '0';
|
|
timeout.dispatchEvent(new Event('input'));
|
|
fixture.detectChanges();
|
|
await fixture.whenStable();
|
|
expect(root.querySelector<HTMLButtonElement>('button')!.disabled).toBeTrue();
|
|
fixture.componentInstance.save();
|
|
http.expectNone('/api/system/capability-settings');
|
|
});
|
|
|
|
it('自动连接默认开启,可独立关闭并保存发现预算', () => {
|
|
const fixture = TestBed.createComponent(CapabilitySettingsComponent);
|
|
const capabilities = { ...fixture.componentInstance.config };
|
|
fixture.detectChanges();
|
|
http.expectOne('/api/system/capability-settings').flush(capabilities);
|
|
fixture.detectChanges();
|
|
const root: HTMLElement = fixture.nativeElement;
|
|
expect(root.textContent).toContain('自动连接设备');
|
|
expect(fixture.componentInstance.config.adb_auto_connect).toBeTrue();
|
|
fixture.componentInstance.config.adb_auto_connect = false;
|
|
fixture.componentInstance.config.adb_discovery_timeout_seconds = 8;
|
|
fixture.componentInstance.save();
|
|
const request = http.expectOne('/api/system/capability-settings');
|
|
expect(request.request.body.adb_auto_connect).toBeFalse();
|
|
expect(request.request.body.adb_discovery_timeout_seconds).toBe(8);
|
|
expect(request.request.body.recording_enabled).toBeFalse();
|
|
request.flush(request.request.body);
|
|
fixture.componentInstance.config.adb_discovery_timeout_seconds = 0;
|
|
fixture.componentInstance.save();
|
|
http.expectNone('/api/system/capability-settings');
|
|
});
|
|
});
|