[功能] 重构代理路由页面的单元测试,优化fetch模拟和输入验证逻辑

This commit is contained in:
ryan
2026-03-20 22:20:13 +08:00
parent afd891f0f6
commit be9744abc6
2 changed files with 1259 additions and 720 deletions
File diff suppressed because it is too large Load Diff
@@ -40,18 +40,47 @@ describe('ProxyRoutesPage', () => {
); );
} }
it('shows wildcard subdomain input after selecting a wildcard website', async () => { function buildBaseFetchStub({
stubMatchMedia(); managedDomains,
origins = [],
vi.stubGlobal( certificates = [{ id: 1, name: 'example-cert', not_after: null }],
'fetch', matchResult,
vi.fn((input: RequestInfo | URL) => { }: {
managedDomains: Array<Record<string, unknown>>;
origins?: Array<Record<string, unknown>>;
certificates?: Array<Record<string, unknown>>;
matchResult?: Record<string, unknown>;
}) {
return vi.fn((input: RequestInfo | URL) => {
const url = String(input); const url = String(input);
if (url.includes('/proxy-routes/')) { if (url.includes('/proxy-routes/')) {
return Promise.resolve(
new Response(JSON.stringify({ success: true, message: '', data: [] })),
);
}
if (url.includes('/managed-domains/match?')) {
return Promise.resolve( return Promise.resolve(
new Response( new Response(
JSON.stringify({ success: true, message: '', data: [] }), JSON.stringify({
success: true,
message: '',
data:
matchResult ??
{
domain: 'a.example.com',
matched: true,
candidate: {
managed_domain_id: 2,
domain: 'a.example.com',
match_type: 'exact',
certificate_id: 1,
certificate_name: 'example-cert',
},
candidates: [],
},
}),
), ),
); );
} }
@@ -62,7 +91,47 @@ describe('ProxyRoutesPage', () => {
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: '', message: '',
data: [ data: managedDomains,
}),
),
);
}
if (url.includes('/origins/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: origins,
}),
),
);
}
if (url.includes('/tls-certificates/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: certificates,
}),
),
);
}
return Promise.reject(new Error(`Unhandled fetch: ${url}`));
});
}
it('shows wildcard subdomain input after selecting a wildcard website', async () => {
stubMatchMedia();
vi.stubGlobal(
'fetch',
buildBaseFetchStub({
managedDomains: [
{ {
id: 1, id: 1,
domain: '*.example.com', domain: '*.example.com',
@@ -83,36 +152,6 @@ describe('ProxyRoutesPage', () => {
}, },
], ],
}), }),
),
);
}
if (url.includes('/origins/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: [],
}),
),
);
}
if (url.includes('/tls-certificates/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: [{ id: 1, name: 'example-cert', not_after: null }],
}),
),
);
}
return Promise.reject(new Error(`Unhandled fetch: ${url}`));
}),
); );
renderProxyRoutesPage(); renderProxyRoutesPage();
@@ -121,23 +160,17 @@ describe('ProxyRoutesPage', () => {
await user.click(await screen.findByRole('button', { name: '新增规则' })); await user.click(await screen.findByRole('button', { name: '新增规则' }));
const dialog = await screen.findByRole('dialog', { name: '新增规则' }); const dialog = await screen.findByRole('dialog', { name: '新增规则' });
await user.selectOptions( await user.click(
within(dialog).getAllByRole('combobox')[0], within(dialog).getByRole('button', { name: /Select Target Domain/ }),
'*.example.com',
); );
await user.click(await screen.findByText('*.example.com'));
expect(await screen.findByPlaceholderText('ai')).toBeInTheDocument(); expect(await screen.findByPlaceholderText('e.g. ai')).toBeInTheDocument();
expect( expect(screen.getByText('*.example.com')).toBeInTheDocument();
screen.getByText(
'当前网站为通配符 *.example.com,这里只需填写前缀,例如 ai。',
),
).toBeInTheDocument();
await user.type(screen.getByPlaceholderText('ai'), 'ai'); await user.type(screen.getByPlaceholderText('e.g. ai'), 'ai');
expect( expect(screen.getByText('ai.example.com')).toBeInTheDocument();
screen.getByText('当前将生成规则域名 ai.example.com'),
).toBeInTheDocument();
}); });
it('uses exact website directly without showing subdomain input', async () => { it('uses exact website directly without showing subdomain input', async () => {
@@ -145,24 +178,8 @@ describe('ProxyRoutesPage', () => {
vi.stubGlobal( vi.stubGlobal(
'fetch', 'fetch',
vi.fn((input: RequestInfo | URL) => { buildBaseFetchStub({
const url = String(input); managedDomains: [
if (url.includes('/proxy-routes/')) {
return Promise.resolve(
new Response(
JSON.stringify({ success: true, message: '', data: [] }),
),
);
}
if (url.includes('/managed-domains/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: [
{ {
id: 2, id: 2,
domain: 'a.example.com', domain: 'a.example.com',
@@ -174,36 +191,6 @@ describe('ProxyRoutesPage', () => {
}, },
], ],
}), }),
),
);
}
if (url.includes('/origins/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: [],
}),
),
);
}
if (url.includes('/tls-certificates/')) {
return Promise.resolve(
new Response(
JSON.stringify({
success: true,
message: '',
data: [{ id: 1, name: 'example-cert', not_after: null }],
}),
),
);
}
return Promise.reject(new Error(`Unhandled fetch: ${url}`));
}),
); );
renderProxyRoutesPage(); renderProxyRoutesPage();
@@ -212,16 +199,71 @@ describe('ProxyRoutesPage', () => {
await user.click(await screen.findByRole('button', { name: '新增规则' })); await user.click(await screen.findByRole('button', { name: '新增规则' }));
const dialog = await screen.findByRole('dialog', { name: '新增规则' }); const dialog = await screen.findByRole('dialog', { name: '新增规则' });
await user.selectOptions( await user.click(
within(dialog).getAllByRole('combobox')[0], within(dialog).getByRole('button', { name: /Select Target Domain/ }),
'a.example.com', );
await user.click(await screen.findByText('a.example.com'));
expect(screen.queryByPlaceholderText('e.g. ai')).not.toBeInTheDocument();
expect((await screen.findAllByText('a.example.com')).length).toBeGreaterThan(0);
expect(await screen.findByText('Select Certificate')).toBeInTheDocument();
});
it('shows origin autocomplete suggestions and empty state', async () => {
stubMatchMedia();
vi.stubGlobal(
'fetch',
buildBaseFetchStub({
managedDomains: [
{
id: 1,
domain: '*.example.com',
cert_id: 1,
enabled: true,
remark: '',
created_at: '2026-03-20T08:00:00Z',
updated_at: '2026-03-20T08:00:00Z',
},
],
origins: [
{
id: 1,
name: 'Main Backend',
address: '192.168.1.45',
remark: '',
route_count: 1,
created_at: '2026-03-20T08:00:00Z',
updated_at: '2026-03-20T08:00:00Z',
},
],
}),
); );
expect(screen.queryByPlaceholderText('ai')).not.toBeInTheDocument(); renderProxyRoutesPage();
const user = userEvent.setup();
await user.click(await screen.findByRole('button', { name: '新增规则' }));
const domainTrigger = await screen.findByRole('button', {
name: /Select Target Domain/,
});
await user.click(domainTrigger);
await user.click(await screen.findByText('*.example.com'));
const addressInput = screen.getByPlaceholderText('192.168.1.45');
await user.click(addressInput);
await user.type(addressInput, '192.');
expect(await screen.findByText('192.168.1.45')).toBeInTheDocument();
expect(await screen.findByText('(Main Backend)')).toBeInTheDocument();
expect(await screen.findByText('Local')).toBeInTheDocument();
await user.clear(addressInput);
await user.type(addressInput, '10.10.');
expect( expect(
await screen.findByText( await screen.findByText('未发现匹配资产,请手动输入'),
'当前将直接使用 a.example.com 作为规则域名,无需再填写网站名。',
),
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
}); });