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('.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('#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('#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('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(); }); });