Files
puaros/packages/guardian/tests/unit/infrastructure/SecretDetector.test.ts
imfozilbek 0b1cc5a79a feat: add secret detection with Secretlint (v0.8.0)
Add critical security feature to detect 350+ types of hardcoded secrets
using industry-standard Secretlint library.

Features:
- Detect AWS keys, GitHub tokens, NPM tokens, SSH keys, API keys, etc.
- All secrets marked as CRITICAL severity
- Context-aware remediation suggestions per secret type
- New SecretDetector using @secretlint/node
- New SecretViolation value object (100% test coverage)
- CLI output with "🔐 Secrets" section
- Async pipeline support for secret detection

Tests:
- Added 47 new tests (566 total, 100% pass rate)
- Coverage: 93.3% statements, 83.74% branches
- SecretViolation: 23 tests, 100% coverage
- SecretDetector: 24 tests

Dependencies:
- @secretlint/node: 11.2.5
- @secretlint/core: 11.2.5
- @secretlint/types: 11.2.5
- @secretlint/secretlint-rule-preset-recommend: 11.2.5
2025-11-25 18:27:27 +05:00

278 lines
8.6 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest"
import { SecretDetector } from "../../../src/infrastructure/analyzers/SecretDetector"
describe("SecretDetector", () => {
let detector: SecretDetector
beforeEach(() => {
detector = new SecretDetector()
})
describe("detectAll", () => {
it("should return empty array for code without secrets", async () => {
const code = `
const greeting = "Hello World"
const count = 42
function test() {
return true
}
`
const violations = await detector.detectAll(code, "test.ts")
expect(violations).toHaveLength(0)
})
it("should return empty array for normal environment variable usage", async () => {
const code = `
const apiKey = process.env.API_KEY
const dbUrl = process.env.DATABASE_URL
`
const violations = await detector.detectAll(code, "config.ts")
expect(violations).toHaveLength(0)
})
it("should handle empty code", async () => {
const violations = await detector.detectAll("", "empty.ts")
expect(violations).toHaveLength(0)
})
it("should handle code with only comments", async () => {
const code = `
// This is a comment
/* Multi-line
comment */
`
const violations = await detector.detectAll(code, "comments.ts")
expect(violations).toHaveLength(0)
})
it("should handle multiline strings without secrets", async () => {
const code = `
const template = \`
Hello World
This is a test
No secrets here
\`
`
const violations = await detector.detectAll(code, "template.ts")
expect(violations).toHaveLength(0)
})
it("should handle code with URLs", async () => {
const code = `
const apiUrl = "https://api.example.com/v1"
const websiteUrl = "http://localhost:3000"
`
const violations = await detector.detectAll(code, "urls.ts")
expect(violations).toHaveLength(0)
})
it("should handle imports and requires", async () => {
const code = `
import { something } from "some-package"
const fs = require('fs')
`
const violations = await detector.detectAll(code, "imports.ts")
expect(violations).toHaveLength(0)
})
it("should return violations with correct file path", async () => {
const code = `const secret = "test-secret-value"`
const filePath = "src/config/secrets.ts"
const violations = await detector.detectAll(code, filePath)
violations.forEach((v) => {
expect(v.file).toBe(filePath)
})
})
it("should handle .js files", async () => {
const code = `const test = "value"`
const violations = await detector.detectAll(code, "test.js")
expect(violations).toBeInstanceOf(Array)
})
it("should handle .jsx files", async () => {
const code = `const Component = () => <div>Test</div>`
const violations = await detector.detectAll(code, "Component.jsx")
expect(violations).toBeInstanceOf(Array)
})
it("should handle .tsx files", async () => {
const code = `const Component: React.FC = () => <div>Test</div>`
const violations = await detector.detectAll(code, "Component.tsx")
expect(violations).toBeInstanceOf(Array)
})
it("should handle errors gracefully", async () => {
const code = null as unknown as string
const violations = await detector.detectAll(code, "test.ts")
expect(violations).toHaveLength(0)
})
it("should handle malformed code gracefully", async () => {
const code = "const = = ="
const violations = await detector.detectAll(code, "malformed.ts")
expect(violations).toBeInstanceOf(Array)
})
})
describe("parseOutputToViolations", () => {
it("should parse empty output", async () => {
const code = ""
const violations = await detector.detectAll(code, "test.ts")
expect(violations).toHaveLength(0)
})
it("should handle whitespace-only output", async () => {
const code = " \n \n "
const violations = await detector.detectAll(code, "test.ts")
expect(violations).toHaveLength(0)
})
})
describe("extractSecretType", () => {
it("should handle various secret types correctly", async () => {
const code = `const value = "test"`
const violations = await detector.detectAll(code, "test.ts")
violations.forEach((v) => {
expect(v.secretType).toBeTruthy()
expect(typeof v.secretType).toBe("string")
expect(v.secretType.length).toBeGreaterThan(0)
})
})
})
describe("integration", () => {
it("should work with TypeScript code", async () => {
const code = `
interface Config {
apiKey: string
}
const config: Config = {
apiKey: process.env.API_KEY || "default"
}
`
const violations = await detector.detectAll(code, "config.ts")
expect(violations).toBeInstanceOf(Array)
})
it("should work with ES6+ syntax", async () => {
const code = `
const fetchData = async () => {
const response = await fetch(url)
return response.json()
}
const [data, setData] = useState(null)
`
const violations = await detector.detectAll(code, "hooks.ts")
expect(violations).toBeInstanceOf(Array)
})
it("should work with JSX/TSX", async () => {
const code = `
export const Button = ({ onClick }: Props) => {
return <button onClick={onClick}>Click me</button>
}
`
const violations = await detector.detectAll(code, "Button.tsx")
expect(violations).toBeInstanceOf(Array)
})
it("should handle concurrent detections", async () => {
const code1 = "const test1 = 'value1'"
const code2 = "const test2 = 'value2'"
const code3 = "const test3 = 'value3'"
const [result1, result2, result3] = await Promise.all([
detector.detectAll(code1, "file1.ts"),
detector.detectAll(code2, "file2.ts"),
detector.detectAll(code3, "file3.ts"),
])
expect(result1).toBeInstanceOf(Array)
expect(result2).toBeInstanceOf(Array)
expect(result3).toBeInstanceOf(Array)
})
})
describe("edge cases", () => {
it("should handle very long code", async () => {
const longCode = "const value = 'test'\n".repeat(1000)
const violations = await detector.detectAll(longCode, "long.ts")
expect(violations).toBeInstanceOf(Array)
})
it("should handle special characters in code", async () => {
const code = `
const special = "!@#$%^&*()_+-=[]{}|;:',.<>?"
const unicode = "日本語 🚀"
`
const violations = await detector.detectAll(code, "special.ts")
expect(violations).toBeInstanceOf(Array)
})
it("should handle code with regex patterns", async () => {
const code = `
const pattern = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i
const matches = text.match(pattern)
`
const violations = await detector.detectAll(code, "regex.ts")
expect(violations).toBeInstanceOf(Array)
})
it("should handle code with template literals", async () => {
const code = `
const message = \`Hello \${name}, your balance is \${balance}\`
`
const violations = await detector.detectAll(code, "template.ts")
expect(violations).toBeInstanceOf(Array)
})
})
})