@unito/integration-sdk
Version:
Integration SDK
134 lines (129 loc) • 4.61 kB
text/typescript
import { RuleTester } from 'eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/no-credentials-spread-in-cache-key.js';
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
});
const CACHE_FILENAME = '/repo/integrations/foo/src/cache.ts';
const NON_CACHE_FILENAME = '/repo/integrations/foo/src/credentials.ts';
const wrap = (body: string): string => `function buildKey(params, credentials, creds) { ${body} }`;
test('no-credentials-spread-in-cache-key', () => {
ruleTester.run('no-credentials-spread-in-cache-key', rule, {
valid: [
{
name: 'cache.ts using stable identifier instead of spread',
filename: CACHE_FILENAME,
code: wrap(`
const userIdentifier = credentials.unitoCredentialId ?? credentials.accessToken;
return crc32(JSON.stringify({ ...params, userIdentifier }));
`),
},
{
name: 'cache.ts referencing credentials field directly (no spread)',
filename: CACHE_FILENAME,
code: wrap(`
return crc32(JSON.stringify({ ...params, id: credentials.unitoCredentialId }));
`),
},
{
name: 'cache.ts spreading params is fine',
filename: CACHE_FILENAME,
code: wrap(`
return crc32(JSON.stringify({ ...params }));
`),
},
{
name: 'cache.ts spreading non-credential identifier',
filename: CACHE_FILENAME,
code: wrap(`
const opts = { foo: 1 };
return crc32(JSON.stringify({ ...params, ...opts }));
`),
},
{
name: 'credentials.ts spreading credentials (legitimate enrichment)',
filename: NON_CACHE_FILENAME,
code: wrap(`
return { ...credentials, extra: 'data' };
`),
},
{
name: 'helpers/credentials.ts file (rule does not target it)',
filename: '/repo/integrations/foo/src/helpers/credentials.ts',
code: wrap(`
return { ...credentials, ...params };
`),
},
{
name: 'cache.ts spreading credentials member (not the whole object)',
filename: CACHE_FILENAME,
code: wrap(`
return crc32(JSON.stringify({ ...params, ...credentials.publicMetadata }));
`),
},
],
invalid: [
{
name: 'cache.ts spreading credentials into JSON.stringify',
filename: CACHE_FILENAME,
code: wrap(`
return crc32(JSON.stringify({ ...params, ...credentials })).toString(16);
`),
errors: [{ messageId: 'credentialsSpread', data: { name: 'credentials' } }],
},
{
name: 'cache.ts spreading credentials directly inside hash call',
filename: CACHE_FILENAME,
code: wrap(`
return crc.crc32(JSON.stringify({ ...credentials })).toString(16);
`),
errors: [{ messageId: 'credentialsSpread', data: { name: 'credentials' } }],
},
{
name: 'cache.ts spreading creds (alias) in cache key',
filename: CACHE_FILENAME,
code: wrap(`
return crc32(JSON.stringify({ ...params, ...creds })).toString(16);
`),
errors: [{ messageId: 'credentialsSpread', data: { name: 'creds' } }],
},
{
name: 'cache.ts spreading credentials in helper-built object literal',
filename: CACHE_FILENAME,
code: wrap(`
const input = { prefix, ...credentials, ...params };
return hash(input);
`),
errors: [{ messageId: 'credentialsSpread', data: { name: 'credentials' } }],
},
{
name: 'cache.ts spreading credentials in non-hashing path is still flagged (rule is a hard rule)',
filename: CACHE_FILENAME,
code: wrap(`
const debug = { ...credentials };
return debug;
`),
errors: [{ messageId: 'credentialsSpread', data: { name: 'credentials' } }],
},
{
name: 'cache.ts spreading credentials as call-arg is also flagged',
filename: CACHE_FILENAME,
code: wrap(`
return hash(...credentials);
`),
errors: [{ messageId: 'credentialsSpread', data: { name: 'credentials' } }],
},
{
name: 'cache.ts spreading singular alias `cred`',
filename: CACHE_FILENAME,
code: `function buildKey(params, cred) {
return crc32(JSON.stringify({ ...params, ...cred })).toString(16);
}`,
errors: [{ messageId: 'credentialsSpread', data: { name: 'cred' } }],
},
],
});
});