@unito/integration-sdk
Version:
Integration SDK
173 lines (168 loc) • 5.56 kB
text/typescript
import { RuleTester } from 'eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/no-error-message-rethrow-to-caller.js';
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
});
const HANDLER_FILE = '/repo/integrations/foo/src/handlers/record.ts';
const ROUTE_FILE = '/repo/integrations/foo/src/routes/comment.ts';
const RATE_LIMITER_FILE = '/repo/integrations/foo/src/handlers/rateLimiter.ts';
const NON_HANDLER_FILE = '/repo/integrations/foo/src/helpers/utils.ts';
const wrap = (body: string): string => `async function f() { ${body} }`;
test('no-error-message-rethrow-to-caller', () => {
ruleTester.run('no-error-message-rethrow-to-caller', rule, {
valid: [
{
name: 'static message in HttpErrors throw',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.BadRequestError('Upload failed');
}
`),
},
{
name: 'template literal with non-error context',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.NotFoundError(\`item \${id} not found\`);
}
`),
},
{
name: 'commit.message is not error context (different identifier)',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.UnprocessableEntityError(commit.message);
}
`),
},
{
name: 'error.status (not message) is fine',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.X(\`status: \${error.status}\`);
}
`),
},
{
name: 'logger.warn with error.message is fine — logger has its own redaction',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
context.logger.warn('failed', { error: error.message });
throw new HttpErrors.UnprocessableEntityError('Upload failed');
}
`),
},
{
name: 'helpers/ file is out of scope (not handler/route)',
filename: NON_HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.BadRequestError(error.message);
}
`),
},
{
name: 'rateLimiter.ts under handlers/ is allowlisted',
filename: RATE_LIMITER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.RateLimitExceededError(error.message);
}
`),
},
{
name: 'throw outside catch is out of scope',
filename: HANDLER_FILE,
code: wrap(`
if (cond) {
throw new HttpErrors.BadRequestError(error.message);
}
`),
},
{
name: 'throw inside catch but not HttpErrors',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new Error(error.message);
}
`),
},
{
name: 'nested function inside catch — function boundary stops the scope walk',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
const handler = () => {
throw new HttpErrors.BadRequestError(error.message);
};
handler();
}
`),
},
],
invalid: [
{
name: 'direct error.message rethrow',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.UnprocessableEntityError(error.message);
}
`),
errors: [{ messageId: 'errorMessageRethrow' }],
},
{
name: 'template literal interpolating err.message',
filename: HANDLER_FILE,
code: 'async function f() { try { foo(); } catch (err) { throw new HttpErrors.BadRequestError(`upload failed: ${err.message}`); } }',
errors: [{ messageId: 'errorMessageRethrow' }],
},
{
name: 'conditional ternary with error.message branch',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.UnprocessableEntityError(error instanceof Error ? error.message : String(error));
}
`),
errors: [{ messageId: 'errorMessageRethrow' }],
},
{
name: 'mixed template w/ both status and message — message wins',
filename: HANDLER_FILE,
code: 'async function f() { try { foo(); } catch (error) { throw new HttpErrors.X(`status: ${error.status}, msg: ${error.message}`); } }',
errors: [{ messageId: 'errorMessageRethrow' }],
},
{
name: 'route file flagged the same as handler',
filename: ROUTE_FILE,
code: wrap(`
try { foo(); } catch (e) {
throw new HttpErrors.BadRequestError(\`failed: \${e.message}\`);
}
`),
errors: [{ messageId: 'errorMessageRethrow' }],
},
{
name: 'bracket-access error["message"] is also flagged',
filename: HANDLER_FILE,
code: wrap(`
try { foo(); } catch (error) {
throw new HttpErrors.BadRequestError(error['message']);
}
`),
errors: [{ messageId: 'errorMessageRethrow' }],
},
],
});
});