import { RuleTester } from 'eslint' import { describe, it } from 'vitest' import { noConnOutsideRoot } from '../no-conn-outside-root.js' const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2023, sourceType: 'module' }, }) const ROOT = '/repo/apps/server/src/main.js' const ROUTE = '/repo/apps/server/src/routes/drama.js' const PKG = '/repo/packages/tts/src/doubao.js' const SCRIPT = '/repo/tools/scripts/visual-game-dev-user.js' // 目录前缀豁免('tools/scripts/')+ 单文件豁免(main.js)共存。 const opts = [{ connExemptFiles: [ROOT, 'tools/scripts/'] }] describe('no-conn-outside-root (规则 C)', () => { it('valid/invalid + 误伤防回归', () => { ruleTester.run('no-conn-outside-root', noConnOutsideRoot, { valid: [ // 白名单 main.js:顶层连接放行。 { code: "const url = process.env.DB_URL\nconst db = new Knex({ url })", filename: ROOT, options: opts, }, // 业务模块可以在使用点直接读取运行参数。 { code: 'const k = process.env.SECRET', filename: ROUTE, options: opts, }, // 误伤防回归:非连接的普通类顶层 new → 放行。 { code: 'const m = new Map()\nclass Foo {}\nconst f = new Foo()', filename: ROUTE, options: opts, }, // 误伤防回归:连接类在函数体内 new(工厂按注入参数建连接)→ 放行。 { code: 'export function createDb({ url }) { return new Knex({ url }) }', filename: PKG, options: opts, }, // 误伤防回归:new ApiClient() 是业务类,精确名单不含 → 放行(旧后缀匹配会误伤)。 { code: 'class ApiClient {}\nconst c = new ApiClient()', filename: PKG, options: opts, }, // 误伤防回归:函数体内 knex('表名') 是查询构建(全仓几百处)→ 放行。 { code: "export function load(knex) { return knex('orders').where({ id: 1 }) }", filename: PKG, options: opts, }, // 白名单 main.js:顶层 knex(cfg) 建连接放行。 { code: 'const db = knex(cfg)', filename: ROOT, options: opts, }, // 目录前缀豁免:tools/scripts/ 下的可执行脚本读 env、顶层建连接均放行。 { code: "const url = process.env.DB_URL\nconst db = knex({ url })", filename: SCRIPT, options: opts, }, ], invalid: [ // 顶层 knex(cfg) 建连接(函数调用分支)→ 报错。 { code: 'const db = knex(cfg)', filename: PKG, options: opts, errors: [{ messageId: 'connCall' }], }, // 顶层成员调用 mysql.createConnection(...) → 报错。 { code: 'import mysql from "mysql2/promise"\nconst conn = mysql.createConnection(url)', filename: ROUTE, options: opts, errors: [{ messageId: 'connCall' }], }, // 非 main.js 顶层建连接 → 报错。 { code: 'const db = new Knex({})', filename: PKG, options: opts, errors: [{ messageId: 'conn' }], }, // 后缀匹配:new S3Client() 命中 Client 模式。 { code: 'const s3 = new S3Client({})', filename: PKG, options: opts, errors: [{ messageId: 'conn' }], }, // 成员形式 new pg.Pool() 顶层 → 报错。 { code: 'import pg from "pg"\nconst pool = new pg.Pool({})', filename: PKG, options: opts, errors: [{ messageId: 'conn' }], }, ], }) }) })