@unito/integration-sdk
Version:
Integration SDK
123 lines (119 loc) • 4.77 kB
text/typescript
import { RuleTester } from 'eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/no-full-error-object-logging.js';
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
});
const wrap = (body: string): string => `async function f() { ${body} }`;
test('no-full-error-object-logging', () => {
ruleTester.run('no-full-error-object-logging', rule, {
valid: [
{
name: 'structured field extraction',
code: wrap('try { foo(); } catch (error) { logger.error({ message: error.message }); }'),
},
{
name: 'string description + structured metadata',
code: wrap("try { foo(); } catch (error) { logger.error('upload failed', { message: error.message }); }"),
},
{
name: 'just the message string',
code: wrap('try { foo(); } catch (error) { logger.error(error.message); }'),
},
{
name: 'static string — no error reference',
code: wrap("logger.error('static string');"),
},
{
name: 'non-error identifier — result does not match error pattern',
code: wrap('logger.error(result);'),
},
{
name: 'not a logger call — someFunction(error) is ignored',
code: wrap('try { foo(); } catch (error) { someFunction(error); }'),
},
{
name: 'error.code extraction is fine',
code: wrap('try { foo(); } catch (error) { logger.error({ code: error.code }); }'),
},
{
name: 'nested function inside catch — function boundary stops scope walk',
code: wrap(`
try { foo(); } catch (oops) {
const handler = () => { logger.error(oops); };
handler();
}
`),
},
{
name: 'catch with destructured param — not an Identifier, tier 1 skips',
code: wrap('try { foo(); } catch ({ message }) { logger.error(message); }'),
},
{
name: 'scripts/ files are excluded — CLI tooling needs full error output',
filename: '/repo/integrations/foo/src/scripts/createIssueInBatch.ts',
code: wrap('try { foo(); } catch (err) { logger.error(err); }'),
},
],
invalid: [
{
name: 'tier 1: catch parameter passed to logger.error',
code: wrap('try { foo(); } catch (error) { logger.error(error); }'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'error' } }],
},
{
name: 'tier 1: renamed catch parameter',
code: wrap('try { foo(); } catch (oops) { logger.error(oops); }'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'oops' } }],
},
{
name: 'tier 1: chained logger (context.logger.warn)',
code: wrap('try { foo(); } catch (e) { context.logger.warn(e); }'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'warn', identifier: 'e' } }],
},
{
name: 'tier 1: catch param as second argument',
code: wrap("try { foo(); } catch (error) { logger.error('failed', error); }"),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'error' } }],
},
{
name: 'tier 2: error-named identifier outside catch',
code: wrap('logger.error(err);'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'err' } }],
},
{
name: 'tier 2: logger.warn(error)',
code: wrap('logger.warn(error);'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'warn', identifier: 'error' } }],
},
{
name: 'tier 2: logger.error(exception)',
code: wrap('logger.error(exception);'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'exception' } }],
},
{
name: 'tier 2: logger.info(cause)',
code: wrap('logger.info(cause);'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'info', identifier: 'cause' } }],
},
{
name: 'tier 2: console.error(error)',
code: wrap('console.error(error);'),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'error' } }],
},
{
name: 'tier 2: error-named var inside nested function in catch — tier 2 still fires',
code: wrap(`
try { foo(); } catch (err) {
const handler = () => { logger.error(err); };
handler();
}
`),
errors: [{ messageId: 'fullErrorObjectLogged', data: { method: 'error', identifier: 'err' } }],
},
],
});
});