aps-agent/scripts/check-dependency-direction....

127 lines
5.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 依赖方向检查的负例用例(任务 A1.2 的验证方式)。
* 每个负例都要求检查器真的报错——只测正例等于没测。
*/
import assert from 'node:assert/strict';
import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { test } from 'node:test';
import { fileURLToPath } from 'node:url';
import { findViolations } from './check-dependency-direction.mjs';
const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url));
const WORKSPACE = ['contracts', 'server', 'server/provider-sdk', 'providers/*', 'client/*'];
/** 造一个最小工作区;extra 覆盖或新增文件(键为相对路径)。 */
async function makeWorkspace(extra = {}) {
const root = await mkdtemp(join(tmpdir(), 'depcheck-'));
const files = {
'pnpm-workspace.yaml': `packages:\n${WORKSPACE.map((p) => ` - ${p}\n`).join('')}`,
'contracts/package.json': JSON.stringify({ name: '@aps/contracts' }),
'server/package.json': JSON.stringify({ name: '@aps/server', dependencies: { '@aps/contracts': 'workspace:*', '@aps/provider-sdk': 'workspace:*' } }),
'server/provider-sdk/package.json': JSON.stringify({ name: '@aps/provider-sdk', dependencies: { '@aps/contracts': 'workspace:*' } }),
'providers/aps/package.json': JSON.stringify({ name: '@aps/provider-aps', dependencies: { '@aps/provider-sdk': 'workspace:*' } }),
'client/app/package.json': JSON.stringify({ name: '@aps/client-app', dependencies: { '@aps/contracts': 'workspace:*' } }),
...extra,
};
for (const [rel, content] of Object.entries(files)) {
const file = join(root, rel);
await mkdir(dirname(file), { recursive: true });
await writeFile(file, content, 'utf8');
}
return root;
}
async function withWorkspace(extra, run) {
const root = await makeWorkspace(extra);
try {
return await run(root);
} finally {
await rm(root, { recursive: true, force: true });
}
}
const reasons = (violations) => violations.map((v) => `${v.file} → ${v.specifier}:${v.reason}`).join('\n');
test('正例:当前仓库无违规', async () => {
assert.deepEqual(await findViolations(REPO_ROOT), []);
});
test('负例:core 引用具体 Provider 被拦下', async () => {
await withWorkspace(
{
'server/core/router.ts': "import type { CapabilityProvider } from '@aps/provider-aps';\nexport type X = CapabilityProvider;\n",
},
async (root) => {
const violations = await findViolations(root);
assert.ok(violations.some((v) => v.file.endsWith('server/core/router.ts') && v.specifier === '@aps/provider-aps'), reasons(violations));
},
);
});
test('负例:业务包直连 Provider 被拦下', async () => {
await withWorkspace(
{ 'client/app/direct.ts': "import '@aps/provider-aps';\n" },
async (root) => {
const violations = await findViolations(root);
assert.ok(violations.some((v) => v.package === '@aps/client-app' && v.specifier === '@aps/provider-aps'), reasons(violations));
},
);
});
test('负例:客户端引用服务端被拦下', async () => {
await withWorkspace(
{ 'client/app/server-call.ts': "import type { ExecutionReceipt } from '@aps/server';\nexport type R = ExecutionReceipt;\n" },
async (root) => {
const violations = await findViolations(root);
assert.ok(violations.some((v) => v.specifier === '@aps/server'), reasons(violations));
},
);
});
test('负例:非 agent 模块引用引擎包被拦下', async () => {
await withWorkspace(
{ 'server/entry/boot.ts': "import '@earendil-works/pi-agent-core';\n" },
async (root) => {
const violations = await findViolations(root);
assert.ok(violations.some((v) => v.file.endsWith('server/entry/boot.ts')), reasons(violations));
},
);
});
test('正例:server/agent 引用引擎包放行', async () => {
await withWorkspace(
{ 'server/agent/runtime.ts': "import '@earendil-works/pi-agent-core';\n" },
async (root) => {
assert.deepEqual(await findViolations(root), []);
},
);
});
test('正例:允许包的子路径导入不算违规', async () => {
await withWorkspace({ 'server/agent/importer.ts': "import { createFakeProvider } from '@aps/provider-sdk/testing';\nexport const p = createFakeProvider;\n" }, async (root) => {
assert.deepEqual(await findViolations(root), []);
});
});
test('负例:相对路径逃出包边界被拦下', async () => {
await withWorkspace(
{ 'client/app/escape.ts': "import '../../../providers/aps/index.js';\n" },
async (root) => {
const violations = await findViolations(root);
assert.ok(violations.some((v) => v.reason === '相对路径逃出包边界'), reasons(violations));
},
);
});
test('负例:未登记的包名被拦下', async () => {
await withWorkspace(
{ 'providers/erp/package.json': JSON.stringify({ name: '@aps/provider-erp' }) },
async (root) => {
const violations = await findViolations(root);
assert.ok(violations.some((v) => v.reason.includes('未登记的包名')), reasons(violations));
},
);
});