← ubon
import { mkdirSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import { SecurityScanner } from '../scanners/security-scanner';
import { IacScanner } from '../scanners/iac-scanner';
describe('Framework/infra rules', () => {
const tmp = join(process.cwd(), '.tmp-framework-tests');
beforeAll(() => { try { rmSync(tmp, { recursive: true, force: true }); } catch {} mkdirSync(tmp, { recursive: true }); });
afterAll(() => { try { rmSync(tmp, { recursive: true, force: true }); } catch {} });
it('flags Next.js API route without validation (NEXT003)', async () => {
const dir = join(tmp, 'pages/api');
mkdirSync(dir, { recursive: true });
const fp = join(dir, 'hello.ts');
writeFileSync(fp, `export default function handler(req,res){ const x = req.body.name; res.status(200).json({x}); }`);
const s = new SecurityScanner();
const res = await s.scan({ directory: tmp });
expect(res.some(r => r.ruleId === 'NEXT003')).toBe(true);
});
it('flags Next.js SSR secrets in getServerSideProps/getStaticProps (NEXT006)', async () => {
const pdir = join(tmp, 'pages');
mkdirSync(pdir, { recursive: true });
const fp = join(pdir, 'index.tsx');
writeFileSync(fp, `export async function getServerSideProps(){ const k=process.env.SECRET_KEY; return { props: { k } } } export default function P(){return null}`);
const s = new SecurityScanner();
const res = await s.scan({ directory: tmp });
expect(res.some(r => r.ruleId === 'NEXT006')).toBe(true);
});
it('flags GitHub Actions secrets echoed (GHA001)', async () => {
const ghaDir = join(tmp, '.github/workflows');
mkdirSync(ghaDir, { recursive: true });
const wf = join(ghaDir, 'ci.yml');
const content = 'name: ci\njobs:\n build:\n steps:\n - run: echo ${{ secrets.NPM_TOKEN }}';
writeFileSync(wf, content.replace(/\$\{\{/, '${{')); // avoid TS template parsing
const iac = new IacScanner();
const res = await iac.scan({ directory: tmp });
expect(res.some(r => r.ruleId === 'GHA001')).toBe(true);
});
});