Files
open-design/apps/web/tests/components/SettingsDialog.media.test.tsx
zztdanandGitHub fe879036fb fix(web): restore media config from daemon on startup (#687)
* fix(web): restore media config from daemon on startup

* fix(media): preserve stored keys on settings save

* fix(web): harden daemon media restore flow

* fix(web): unify media provider empty-state rules

* fix(desktop): retry loading discovered web url

* fix(web): preserve local media providers on partial daemon reload

* fix(web): preserve media providers on daemon reload

* fix(web): skip media migration for masked-only local state

* fix(web): preserve daemon media state across reloads
2026-05-09 19:31:08 +08:00

232 lines
6.7 KiB
TypeScript

// @vitest-environment jsdom
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { within } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { SettingsDialog } from '../../src/components/SettingsDialog';
import { DEFAULT_CONFIG } from '../../src/state/config';
import type { AgentInfo, AppConfig } from '../../src/types';
describe('SettingsDialog media providers', () => {
afterEach(() => {
cleanup();
});
it('shows saved masked media provider keys like Composio does', () => {
renderDialog({
...DEFAULT_CONFIG,
mediaProviders: {
openai: {
apiKey: '',
apiKeyConfigured: true,
apiKeyTail: '1234',
baseUrl: '',
},
},
});
expect(screen.getByText('Saved · ••••1234')).toBeTruthy();
expect(screen.getByLabelText('OpenAI API key').getAttribute('placeholder')).toBe(
'Paste a new key to replace the saved one',
);
});
it('shows daemon fallback notice and reloads media providers from daemon', async () => {
const reloadMock = vi.fn(async () => ({
openai: {
apiKey: '',
apiKeyConfigured: true,
apiKeyTail: '9876',
baseUrl: 'https://daemon.example/v1',
},
}));
renderDialog(
{
...DEFAULT_CONFIG,
mediaProviders: {},
},
{
mediaProvidersNotice:
'Could not load media provider settings from the local daemon. Using browser-saved settings for now.',
onReloadMediaProviders: reloadMock,
},
);
expect(
screen.getByText(
'Could not load media provider settings from the local daemon. Using browser-saved settings for now.',
),
).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: 'Reload from daemon' }));
await waitFor(() => {
expect(reloadMock).toHaveBeenCalledTimes(1);
expect(screen.getByText('Saved · ••••9876')).toBeTruthy();
expect(screen.getByText('Reloaded media provider settings from the local daemon.')).toBeTruthy();
});
expect((screen.getByLabelText('OpenAI Base URL') as HTMLInputElement).value).toBe(
'https://daemon.example/v1',
);
});
it('preserves local-only providers when daemon reload returns a partial provider set', async () => {
const reloadMock = vi.fn(async () => ({
openai: {
apiKey: '',
apiKeyConfigured: true,
apiKeyTail: '9876',
baseUrl: 'https://daemon.example/v1',
},
}));
renderDialog(
{
...DEFAULT_CONFIG,
mediaProviders: {
openai: {
apiKey: 'sk-local-openai',
baseUrl: 'https://local-openai.example/v1',
},
fal: {
apiKey: 'sk-local-fal',
baseUrl: 'https://queue.fal.run',
model: 'fal-ai/imagen4/preview',
},
},
},
{
mediaProvidersNotice:
'Could not load media provider settings from the local daemon. Using browser-saved settings for now.',
onReloadMediaProviders: reloadMock,
},
);
fireEvent.click(screen.getByRole('button', { name: 'Reload from daemon' }));
await waitFor(() => {
expect(reloadMock).toHaveBeenCalledTimes(1);
expect(screen.getByText('Saved · ••••9876')).toBeTruthy();
expect(screen.getByText('Reloaded media provider settings from the local daemon.')).toBeTruthy();
});
expect((screen.getByLabelText('OpenAI Base URL') as HTMLInputElement).value).toBe(
'https://daemon.example/v1',
);
expect((screen.getByLabelText('Fal.ai Base URL') as HTMLInputElement).value).toBe(
'https://queue.fal.run',
);
expect((screen.getByLabelText('Fal.ai API key') as HTMLInputElement).value).toBe('sk-local-fal');
});
it('preserves saved media keys when clearing only a non-secret field', async () => {
const onPersist = vi.fn();
renderDialog(
{
...saveableConfig(),
mediaProviders: {
openai: {
apiKey: '',
apiKeyConfigured: true,
apiKeyTail: '1234',
baseUrl: 'https://custom.example/v1',
},
},
},
{ onPersist },
);
fireEvent.change(screen.getByLabelText('OpenAI Base URL'), { target: { value: '' } });
await waitFor(() => {
expect(onPersist).toHaveBeenCalledWith(
expect.objectContaining({
mediaProviders: {
openai: {
apiKey: '',
apiKeyConfigured: true,
apiKeyTail: '1234',
baseUrl: '',
},
},
}),
expect.objectContaining({ forceMediaProviderSync: true }),
);
});
});
it('clears saved media keys only through the explicit Clear action', async () => {
const onPersist = vi.fn();
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
renderDialog(
{
...saveableConfig(),
mediaProviders: {
openai: {
apiKey: '',
apiKeyConfigured: true,
apiKeyTail: '1234',
baseUrl: 'https://custom.example/v1',
},
},
},
{ onPersist },
);
const openaiRow = screen.getByText('OpenAI').closest('.media-provider-row') as HTMLElement | null;
if (!openaiRow) throw new Error('Expected OpenAI media provider row');
fireEvent.click(within(openaiRow).getByRole('button', { name: 'Clear' }));
await waitFor(() => {
expect(onPersist).toHaveBeenCalledWith(
expect.objectContaining({ mediaProviders: {} }),
expect.objectContaining({ forceMediaProviderSync: true }),
);
});
expect(confirmSpy).toHaveBeenCalledTimes(1);
confirmSpy.mockRestore();
});
});
function renderDialog(
initial: AppConfig,
options?: {
mediaProvidersNotice?: string | null;
onReloadMediaProviders?: () => Promise<AppConfig['mediaProviders'] | null>;
onPersist?: (cfg: AppConfig, options?: { forceMediaProviderSync?: boolean }) => void;
},
) {
return render(
<SettingsDialog
initial={initial}
agents={SAVEABLE_AGENTS}
daemonLive
appVersionInfo={null}
initialSection="media"
onPersist={options?.onPersist ?? vi.fn()}
onPersistComposioKey={vi.fn()}
onClose={vi.fn()}
onRefreshAgents={vi.fn()}
mediaProvidersNotice={options?.mediaProvidersNotice}
onReloadMediaProviders={options?.onReloadMediaProviders}
/>,
);
}
const SAVEABLE_AGENTS: AgentInfo[] = [
{
id: 'codex',
name: 'Codex',
bin: 'codex',
available: true,
},
];
function saveableConfig(): AppConfig {
return {
...DEFAULT_CONFIG,
agentId: 'codex',
};
}