2026-09-23 19:31:13 +08:00
/ * *
* 依赖方向检查的负例用例 ( 任务 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 ) , [ ] ) ;
} ,
) ;
} ) ;
2026-09-24 15:48:45 +08:00
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 ) , [ ] ) ;
} ) ;
} ) ;
2026-09-23 19:31:13 +08:00
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 ) ) ;
} ,
) ;
} ) ;