@unito/integration-sdk
Version:
Integration SDK
221 lines (215 loc) • 8.56 kB
text/typescript
import { RuleTester } from 'eslint';
import tseslint from 'typescript-eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/webhook-json-parse-must-have-try.js';
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
});
const tsRuleTester = new RuleTester({
languageOptions: {
parser: tseslint.parser,
ecmaVersion: 2022,
sourceType: 'module',
},
});
const WEBHOOK_FILE = '/repo/integrations/foo/src/webhook.ts';
const WEBHOOKS_FILE = '/repo/integrations/foo/src/webhooks.ts';
const PARSE_WEBHOOKS_FILE = '/repo/integrations/foo/src/parseWebhooks.ts';
const WEBHOOK_HELPER_FILE = '/repo/integrations/foo/src/webhookHelper.ts';
const WEBHOOKS_HELPER_FILE = '/repo/integrations/foo/src/helpers/webhooksHelper.ts';
const NON_WEBHOOK_FILE = '/repo/integrations/foo/src/handlers/item.ts';
const PARSE_EVENTS_FILE = '/repo/integrations/foo/src/handlers/parseEvents.ts';
const EVENT_DISPATCHER_FILE = '/repo/integrations/foo/src/handlers/eventDispatcher.ts';
test('webhook-json-parse-must-have-try', () => {
ruleTester.run('webhook-json-parse-must-have-try', rule, {
valid: [
{
name: 'try-wrapped JSON.parse in webhook.ts',
filename: WEBHOOK_FILE,
code: `function f(raw) { try { return JSON.parse(raw); } catch (e) { return null; } }`,
},
{
name: 'try-wrapped JSON.parse with logger in webhooks.ts',
filename: WEBHOOKS_FILE,
code: `function f(raw) { try { const body = JSON.parse(raw); return body; } catch (e) { logger.warn('bad', { error: e }); return null; } }`,
},
{
name: 'JSON.parse inside catch handler is fine (already error-handling)',
filename: WEBHOOK_FILE,
code: `function f(raw) { try { doThing(); } catch (e) { const fallback = JSON.parse('{}'); return fallback; } }`,
},
{
name: 'JSON.parse inside arrow inside try.block is covered',
filename: PARSE_WEBHOOKS_FILE,
code: `function f(raw) { try { return [raw].map((r) => JSON.parse(r)); } catch (e) { return []; } }`,
},
{
name: 'JSON.parse in non-webhook file is skipped',
filename: NON_WEBHOOK_FILE,
code: `function f(raw) { return JSON.parse(raw); }`,
},
{
name: 'webhookHelper.ts with try-wrapped JSON.parse',
filename: WEBHOOK_HELPER_FILE,
code: `function parsePayload(raw) { try { return JSON.parse(raw); } catch (e) { logger.warn('x'); return null; } }`,
},
{
name: 'webhooksHelper.ts with try-wrapped JSON.parse',
filename: WEBHOOKS_HELPER_FILE,
code: `export function parse(s) { try { return JSON.parse(s); } catch { return undefined; } }`,
},
{
name: 'no JSON.parse at all in webhook file',
filename: WEBHOOK_FILE,
code: `function f(body) { return body.id; }`,
},
{
name: 'JSON.stringify is not flagged',
filename: WEBHOOK_FILE,
code: `function f(body) { return JSON.stringify(body); }`,
},
{
name: 'unrelated parse method on non-JSON object is not flagged',
filename: WEBHOOK_FILE,
code: `function f(s) { return Date.parse(s); }`,
},
],
invalid: [
{
name: 'bare JSON.parse in webhook.ts',
filename: WEBHOOK_FILE,
code: `function f(raw) { return JSON.parse(raw); }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'bare JSON.parse in webhooks.ts',
filename: WEBHOOKS_FILE,
code: `function f(raw) { const body = JSON.parse(raw); return body; }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'bare JSON.parse in parseWebhooks.ts',
filename: PARSE_WEBHOOKS_FILE,
code: `export function parse(raw) { return JSON.parse(raw); }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'bare JSON.parse in webhookHelper.ts',
filename: WEBHOOK_HELPER_FILE,
code: `function parsePayload(raw) { const data = JSON.parse(raw); return data; }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'bare JSON.parse in webhooksHelper.ts',
filename: WEBHOOKS_HELPER_FILE,
code: `export function parse(s) { return JSON.parse(s); }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'JSON.parse inside arrow function nested in webhook function — function boundary crosses before try',
filename: WEBHOOK_FILE,
code: `function f(items) { return items.map((r) => JSON.parse(r)); }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'JSON.parse before an unrelated try — try does not cover the parse',
filename: WEBHOOK_FILE,
code: `function f(raw) { const x = JSON.parse(raw); try { doIt(); } catch (e) { log(e); } return x; }`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'JSON.parse at module top-level (no enclosing function or try)',
filename: WEBHOOK_FILE,
code: `const config = JSON.parse(process.env.CFG);`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'bracket-access JSON["parse"](raw) is also flagged',
filename: WEBHOOK_FILE,
code: `function f(raw) { return JSON['parse'](raw); }`,
errors: [{ messageId: 'missingTry' }],
},
],
});
});
test('webhook-json-parse-must-have-try — handler-type AST detection', () => {
tsRuleTester.run('webhook-json-parse-must-have-try', rule, {
valid: [
{
name: 'ParseWebhooksHandler in non-webhook file with try-wrapped JSON.parse',
filename: PARSE_EVENTS_FILE,
code: `
import { ParseWebhooksHandler } from '@unito/integration-sdk';
export const parseEvents: ParseWebhooksHandler = async (ctx) => {
try { return JSON.parse(ctx.body.payload); } catch (e) { return []; }
};
`,
},
{
name: 'AcknowledgeWebhooksHandler in non-webhook file with try-wrapped JSON.parse',
filename: EVENT_DISPATCHER_FILE,
code: `
import { AcknowledgeWebhooksHandler } from '@unito/integration-sdk';
export const ack: AcknowledgeWebhooksHandler = async (ctx) => {
try { const p = JSON.parse(ctx.body.payload); return { statusCode: 200 }; } catch { return { statusCode: 400 }; }
};
`,
},
{
name: 'non-handler-typed function in non-webhook file with bare JSON.parse is skipped',
filename: NON_WEBHOOK_FILE,
code: `
export const handler = async (ctx: { body: { payload: string } }) => {
return JSON.parse(ctx.body.payload);
};
`,
},
{
name: 'unrelated typed export in non-webhook file with bare JSON.parse is skipped',
filename: NON_WEBHOOK_FILE,
code: `
type SomeOtherHandler = (s: string) => unknown;
export const handler: SomeOtherHandler = (s) => JSON.parse(s);
`,
},
],
invalid: [
{
name: 'ParseWebhooksHandler in non-webhook file with bare JSON.parse — flagged via AST signal',
filename: PARSE_EVENTS_FILE,
code: `
import { ParseWebhooksHandler } from '@unito/integration-sdk';
export const parseEvents: ParseWebhooksHandler = async (ctx) => {
const body = JSON.parse(ctx.body.payload);
return body;
};
`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'AcknowledgeWebhooksHandler in non-webhook file with bare JSON.parse — flagged via AST signal',
filename: EVENT_DISPATCHER_FILE,
code: `
import { AcknowledgeWebhooksHandler } from '@unito/integration-sdk';
export const ack: AcknowledgeWebhooksHandler = async (ctx) => {
const p = JSON.parse(ctx.body.payload);
return { statusCode: 200, payload: JSON.stringify(p) };
};
`,
errors: [{ messageId: 'missingTry' }],
},
{
name: 'singular ParseWebhookHandler variant also flagged',
filename: PARSE_EVENTS_FILE,
code: `
import { ParseWebhookHandler } from '@unito/integration-sdk';
export const parseEvent: ParseWebhookHandler = async (ctx) => JSON.parse(ctx.body.payload);
`,
errors: [{ messageId: 'missingTry' }],
},
],
});
});