Files
backend/test/eslint-config.cjs
T
ksv741 33592c1d5f refactor: enforce enum machine codes
Use owner enums for error and T-Bank protocol values with ESLint regression checks.

Preserve open wire fallbacks and keep protocol imports independent of DI and Prisma.

Verified npm ci, verify, 226 unit tests, 262 isolated e2e tests and unchanged OpenAPI.
2026-09-28 16:37:01 +03:00

88 lines
5.0 KiB
JavaScript

const { ESLint } = require('eslint');
const assert = require('node:assert/strict');
const { test } = require('node:test');
const eslint = new ESLint();
// Protect the quality gate's file scopes when configs or plugin versions change.
test('typed application code enforces async and unsafe-type checks', async () => {
const [result] = await eslint.lintText(
'export const unsafe: any = 1; Promise.resolve(unsafe);',
{ filePath: 'src/main.ts' },
);
const rules = new Set(result.messages.map(({ ruleId }) => ruleId));
assert.ok(rules.has('@typescript-eslint/no-explicit-any'));
assert.ok(rules.has('@typescript-eslint/no-floating-promises'));
});
test('framework and runtime rules reach their intended files', async () => {
const app = await eslint.calculateConfigForFile('src/main.ts');
const spec = await eslint.calculateConfigForFile('test/app.e2e-spec.ts');
const script = await eslint.calculateConfigForFile('scripts/generate-openapi.cjs');
const config = await eslint.calculateConfigForFile('eslint.config.mjs');
assert.equal(app.rules['@darraghor/nestjs-typed/validation-pipe-should-use-forbid-unknown'][0], 2);
assert.equal(spec.rules['jest/no-focused-tests'][0], 2);
assert.equal(app.rules['jest/no-focused-tests'], undefined);
assert.equal(app.languageOptions.globals.expect, undefined);
assert.equal(config.languageOptions.globals.require, 'off');
for (const profile of [app, spec, script, config]) {
assert.equal(profile.rules['n/no-deprecated-api'][0], 2);
assert.equal(profile.rules['security/detect-eval-with-expression'][0], 1);
assert.equal(profile.rules['regexp/no-super-linear-backtracking'][0], 2);
assert.equal(profile.rules['sonarjs/cognitive-complexity'][0], 2);
assert.equal(profile.rules['import-x/no-unresolved'][0], 2);
assert.equal(profile.rules['perfectionist/sort-imports'][0], 2);
assert.equal(profile.rules['@stylistic/semi'][0], 2);
}
});
test('focused tests are rejected and generated code is excluded', async () => {
const [result] = await eslint.lintText(
'it.only(\'example\', () => { expect(1).toBe(1); });',
{ filePath: 'test/app.e2e-spec.ts' },
);
assert.ok(result.messages.some(({ ruleId }) => ruleId === 'jest/no-focused-tests'));
assert.ok(await eslint.isPathIgnored('generated/prisma/client.ts'));
assert.ok(await eslint.isPathIgnored('dist/src/main.js'));
});
test('numeric literals require names, including previously ignored numbers and bigints', async () => {
const literals = [-60, -24, -10, -2, -1, 0, 1, 2, 10, 24, 60].map(String);
for (const literal of [...literals, ...literals.map((value) => `${value}n`)]) {
const [inline] = await eslint.lintText(
`export function value() { return ${literal}; }`,
{ filePath: 'src/main.ts' },
);
assert.ok(inline.messages.some(({ ruleId }) => ruleId === '@typescript-eslint/no-magic-numbers'), literal);
const [named] = await eslint.lintText(
`const boundary = ${literal}; export function value() { return boundary; }`,
{ filePath: 'src/main.ts' },
);
assert.ok(!named.messages.some(({ ruleId }) => ruleId === '@typescript-eslint/no-magic-numbers'), literal);
}
});
// Single-use codes must be caught too; fixture literals remain independent wire assertions.
test('machine codes require enum members without banning ordinary text or Prisma selects', async () => {
const eslint = new ESLint();
const filePath = 'src/main.ts';
for (const code of ['export function check(code: string) { return code === "one-off-code"; }', 'export const result = `MOEX_UNAVAILABLE`;', 'export const result = { code: "one-off-code" };', 'export const result = { "errorCode": "one-off-code" };', 'export const result = { code: `dynamic-${Date.now()}` };', 'export const result = "MOEX_UNAVAILABLE";', 'export const result = ["INSTRUMENT_ID_TYPE_FIGI"];', 'export const result = { OPERATION_TYPE_COUPON: "income" };']) {
const [result] = await eslint.lintText(code, { filePath });
assert.equal(result.fatalErrorCount, 0, code);
assert.ok(result.messages.some(({ ruleId }) => ruleId === 'no-restricted-syntax'), code);
}
for (const code of [
'enum Code { Unavailable = "MOEX_UNAVAILABLE" } export const result = { code: Code.Unavailable };',
'export const select = { code: true, errorCode: true };',
'export const label = "Please try again"; export const key = "JWT_SECRET";',
]) {
const [result] = await eslint.lintText(code, { filePath });
assert.equal(result.fatalErrorCount, 0, code);
assert.ok(!result.messages.some(({ ruleId }) => ruleId === 'no-restricted-syntax'), code);
}
const [fixture] = await eslint.lintText('export const result = { code: "MOEX_UNAVAILABLE" };', { filePath: 'src/integrations/moex/moex.client.spec.ts' });
assert.ok(!fixture.messages.some(({ ruleId }) => ruleId === 'no-restricted-syntax'));
const [comparison] = await eslint.lintText('enum Code { Ready = "ready" } export function check(code: Code) { return code === "ready"; }', { filePath });
assert.ok(comparison.messages.some(({ ruleId }) => ruleId === '@typescript-eslint/no-unsafe-enum-comparison'));
});