Files
Artemis-Eapil/apps/showcase_ui/src/app/components/model-settings/model-settings.component.spec.ts
T

133 lines
6.6 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',
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.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<HTMLSelectElement>('#model-reasoning')!;
reasoning.value = 'max';
reasoning.dispatchEvent(new Event('change'));
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('#model-reasoning').value).toBe('max');
});
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 = { video_enabled: true, recording_enabled: false, outputter_enabled: true, step_summarizer_enabled: false };
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.save();
const request = http.expectOne('/api/system/capability-settings');
expect(request.request.body.video_enabled).toBeFalse();
expect(request.request.body.recording_enabled).toBeTrue();
const saved = { ...capabilities, video_enabled: false, recording_enabled: true };
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();
});
});