no-conn-outside-root.test.js 3.77 KB
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' }],
        },
      ],
    })
  })
})