feat(GOV-010): 建立前端 API 分域边界
This commit is contained in:
@@ -140,3 +140,90 @@ test('接受只消费公开 OpenAPI 类型的 Web 工程', async () => {
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.stderr, '');
|
||||
});
|
||||
|
||||
for (const [name, relativePath, source, expectedViolation] of [
|
||||
[
|
||||
'页面直接导入 Axios',
|
||||
'web/src/views/MarketView.vue',
|
||||
`<script setup lang="ts">
|
||||
import axios from 'axios';
|
||||
void axios.get('/api/v1/market');
|
||||
</script>
|
||||
<template><main /></template>
|
||||
`,
|
||||
'Web 运行时代码禁止直接使用 Axios',
|
||||
],
|
||||
[
|
||||
'Store 直接调用 fetch',
|
||||
'web/src/stores/market.ts',
|
||||
"export const loadMarket = () => fetch('/api/v1/market');\n",
|
||||
'Web 运行时代码禁止直接调用 fetch',
|
||||
],
|
||||
[
|
||||
'页面通过 globalThis 调用 fetch',
|
||||
'web/src/views/GlobalFetchView.ts',
|
||||
"export const loadMarket = () => globalThis.fetch('/api/v1/market');\n",
|
||||
'Web 运行时代码禁止直接调用 fetch',
|
||||
],
|
||||
[
|
||||
'Composable 自行创建 Axios 实例',
|
||||
'web/src/composables/use-market.ts',
|
||||
"import axios from 'axios';\nexport const client = axios.create();\n",
|
||||
'Web 运行时代码禁止直接使用 Axios',
|
||||
],
|
||||
[
|
||||
'页面把业务 API 路径交给其他函数',
|
||||
'web/src/views/MarketView.ts',
|
||||
"declare function load(path: string): void;\nload('/api/v1/market');\n",
|
||||
'业务 API 路径只能声明在 web/src/api',
|
||||
],
|
||||
[
|
||||
'业务 API 绕过统一 request',
|
||||
'web/src/api/market/index.ts',
|
||||
"declare function send(path: string): void;\nexport const load = () => send('/api/v1/market');\n",
|
||||
'业务 API 文件必须使用 @/utils/request',
|
||||
],
|
||||
[
|
||||
'业务 API 通过配置对象绕过统一 request',
|
||||
'web/src/api/market/config.ts',
|
||||
"declare function send(config: { url: string }): void;\nexport const load = () => send({ url: '/api/v1/market' });\n",
|
||||
'业务 API 文件必须使用 @/utils/request',
|
||||
],
|
||||
]) {
|
||||
test(`拒绝${name}`, async () => {
|
||||
const repositoryRoot = await createFixture();
|
||||
const sourcePath = path.join(repositoryRoot, relativePath);
|
||||
await mkdir(path.dirname(sourcePath), { recursive: true });
|
||||
await writeFile(sourcePath, source);
|
||||
|
||||
const result = await runChecker(repositoryRoot);
|
||||
|
||||
assert.equal(result.exitCode, 1);
|
||||
assert.match(result.stderr, new RegExp(expectedViolation));
|
||||
assert.match(result.stderr, new RegExp(path.basename(relativePath).replace('.', '\\.')));
|
||||
});
|
||||
}
|
||||
|
||||
test('接受 Axios 封装、业务 API 和页面消费的三层边界', async () => {
|
||||
const repositoryRoot = await createFixture();
|
||||
await mkdir(path.join(repositoryRoot, 'web/src/utils'), { recursive: true });
|
||||
await mkdir(path.join(repositoryRoot, 'web/src/api/health'), { recursive: true });
|
||||
await mkdir(path.join(repositoryRoot, 'web/src/views'), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(repositoryRoot, 'web/src/utils/request.ts'),
|
||||
"import axios from 'axios';\nexport default axios.create();\n",
|
||||
);
|
||||
await writeFile(
|
||||
path.join(repositoryRoot, 'web/src/api/health/index.ts'),
|
||||
"import request from '@/utils/request';\nexport const getHealth = () => request.get('/api/v1/health');\n",
|
||||
);
|
||||
await writeFile(
|
||||
path.join(repositoryRoot, 'web/src/views/HealthView.ts'),
|
||||
"import { getHealth } from '@/api/health';\nexport const load = () => getHealth();\n",
|
||||
);
|
||||
|
||||
const result = await runChecker(repositoryRoot);
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(result.stderr, '');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user