/**
* Tests for SecurityCIPipeline
*
* Tests the CI/CD security integration system including gates,
* checks, reporting, and automated remediation workflows.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { SecurityCIPipeline } from "@/security/SecurityCIPipeline.js";
// Mock all security dependencies
vi.mock("../../dist/security/AISecurityScanner.js", () => ({
AISecurityScanner: vi.fn().mockImplementation(() => ({
scanCodeForVulnerabilities: vi.fn().mockResolvedValue({
vulnerabilities: [],
riskScore: 0.1,
confidence: 0.95,
}),
scanDependencies: vi.fn().mockResolvedValue({
vulnerabilities: [],
outdatedPackages: 0,
securityScore: 95,
}),
scanSecrets: vi.fn().mockResolvedValue({
secrets: [],
patterns: [],
confidence: 0.98,
}),
})),
}));
vi.mock("../../dist/security/SecurityReviewer.js", () => ({
SecurityReviewer: vi.fn().mockImplementation(() => ({
reviewCode: vi.fn().mockResolvedValue({
issues: [],
score: 95,
recommendations: [],
}),
reviewConfiguration: vi.fn().mockResolvedValue({
issues: [],
securityLevel: "high",
compliance: true,
}),
})),
}));
vi.mock("../../dist/security/AutomatedRemediation.js", () => ({
AutomatedRemediation: vi.fn().mockImplementation(() => ({
autoFix: vi.fn().mockResolvedValue({
fixed: [],
failed: [],
report: "No issues found",
}),
generateRecommendations: vi.fn().mockResolvedValue([]),
})),
}));
vi.mock("../../dist/security/SecurityConfigManager.js", () => ({
SecurityConfigManager: vi.fn().mockImplementation(() => ({
getSecurityConfig: vi.fn().mockReturnValue({
gates: {
preCommit: { enabled: true, blocking: true },
preBuild: { enabled: true, blocking: true },
preDeploy: { enabled: true, blocking: false },
},
thresholds: {
maxCritical: 0,
maxHigh: 2,
maxMedium: 10,
minSecurityScore: 80,
},
}),
validateConfiguration: vi.fn().mockResolvedValue(true),
})),
}));
// Mock logger to avoid console output
vi.mock("../../dist/utils/logger.js", () => ({
LoggerFactory: {
security: () => ({
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
time: vi.fn().mockImplementation((name, fn) => fn()),
}),
},
}));
describe("SecurityCIPipeline", () => {
let pipeline;
let mockConfig;
beforeEach(() => {
mockConfig = {
projectPath: "/test/project",
environment: "test",
branch: "main",
commitHash: "abc123",
enabledGates: ["pre-commit", "pre-build", "pre-deploy"],
notificationConfig: {
enabled: true,
channels: ["email", "slack"],
},
reportingConfig: {
enabled: true,
outputDir: "/tmp/security-reports",
format: "json",
},
};
pipeline = new SecurityCIPipeline(mockConfig);
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("Constructor", () => {
it("should initialize with configuration", () => {
expect(pipeline).toBeDefined();
expect(pipeline.config).toEqual(mockConfig);
});
it("should initialize security components", () => {
expect(pipeline.scanner).toBeDefined();
expect(pipeline.reviewer).toBeDefined();
expect(pipeline.remediation).toBeDefined();
});
it("should validate configuration on initialization", () => {
const invalidConfig = { ...mockConfig, projectPath: "" };
expect(() => new SecurityCIPipeline(invalidConfig)).toThrow();
});
});
describe("Security Gates", () => {
it("should execute pre-commit gate successfully", async () => {
const result = await pipeline.executePreCommitGate();
expect(result).toBeDefined();
expect(result.stage).toBe("pre-commit");
expect(result.status).toBe("passed");
expect(result.gates).toBeDefined();
});
it("should execute pre-build gate successfully", async () => {
const result = await pipeline.executePreBuildGate();
expect(result).toBeDefined();
expect(result.stage).toBe("pre-build");
expect(result.status).toBe("passed");
});
it("should execute pre-deploy gate with warnings", async () => {
// Mock some medium-level issues
pipeline.scanner.scanCodeForVulnerabilities.mockResolvedValue({
vulnerabilities: [{ severity: "medium", description: "Minor issue" }],
riskScore: 0.3,
confidence: 0.9,
});
const result = await pipeline.executePreDeployGate();
expect(result).toBeDefined();
expect(result.stage).toBe("pre-deploy");
expect(["passed", "warning"]).toContain(result.status);
});
it("should fail gate when critical vulnerabilities found", async () => {
pipeline.scanner.scanCodeForVulnerabilities.mockResolvedValue({
vulnerabilities: [
{ severity: "critical", description: "SQL injection" },
{ severity: "high", description: "XSS vulnerability" },
],
riskScore: 0.9,
confidence: 0.95,
});
const result = await pipeline.executePreCommitGate();
expect(result.status).toBe("failed");
expect(result.summary.criticalIssues).toBeGreaterThan(0);
});
});
describe("Security Checks", () => {
it("should perform vulnerability scanning", async () => {
// Ensure the mock is properly set up
pipeline.scanner.scanCodeForVulnerabilities = vi.fn().mockResolvedValue({
vulnerabilities: [],
riskScore: 0.1,
confidence: 0.95,
});
const result = await pipeline.runVulnerabilityCheck();
expect(result).toBeDefined();
expect(result.checkId).toBe("vulnerability-scan");
expect(result.status).toBe("passed");
expect(pipeline.scanner.scanCodeForVulnerabilities).toHaveBeenCalled();
});
it("should perform dependency scanning", async () => {
// Ensure the mock is properly set up
pipeline.scanner.scanDependencies = vi.fn().mockResolvedValue({
vulnerabilities: [],
outdatedPackages: 0,
securityScore: 95,
});
const result = await pipeline.runDependencyCheck();
expect(result).toBeDefined();
expect(result.checkId).toBe("dependency-scan");
expect(pipeline.scanner.scanDependencies).toHaveBeenCalled();
});
it("should perform secrets scanning", async () => {
// Ensure the mock is properly set up
pipeline.scanner.scanSecrets = vi.fn().mockResolvedValue({
secrets: [],
patterns: [],
confidence: 0.98,
});
const result = await pipeline.runSecretsCheck();
expect(result).toBeDefined();
expect(result.checkId).toBe("secrets-scan");
expect(pipeline.scanner.scanSecrets).toHaveBeenCalled();
});
it("should perform code review", async () => {
// Ensure the mock is properly set up
pipeline.reviewer.reviewCode = vi.fn().mockResolvedValue({
issues: [],
score: 95,
recommendations: [],
});
const result = await pipeline.runCodeReviewCheck();
expect(result).toBeDefined();
expect(result.checkId).toBe("code-review");
expect(pipeline.reviewer.reviewCode).toHaveBeenCalled();
});
it("should handle check failures gracefully", async () => {
pipeline.scanner.scanCodeForVulnerabilities.mockRejectedValue(new Error("Scan failed"));
const result = await pipeline.runVulnerabilityCheck();
expect(result.status).toBe("failed");
expect(result.error).toBeDefined();
});
it("should respect check timeouts", async () => {
// Mock slow scan
pipeline.scanner.scanCodeForVulnerabilities.mockImplementation(
() => new Promise((resolve) => setTimeout(resolve, 10000)),
);
const startTime = Date.now();
const result = await pipeline.runVulnerabilityCheck({ timeout: 100 });
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(200);
expect(result.status).toBe("timeout");
});
});
describe("Gate Configuration", () => {
it("should configure security gates dynamically", () => {
const gateConfig = {
id: "test-gate",
name: "Test Security Gate",
stage: "pre-commit",
enabled: true,
blocking: false,
checks: [],
thresholds: {
maxCritical: 0,
maxHigh: 1,
maxMedium: 5,
minSecurityScore: 85,
},
exceptions: [],
};
pipeline.configureGate(gateConfig);
const gates = pipeline.getConfiguredGates();
expect(gates.some((gate) => gate.id === "test-gate")).toBe(true);
});
it("should validate gate configuration", () => {
const invalidGate = {
id: "",
name: "Invalid Gate",
stage: "invalid-stage",
};
expect(() => pipeline.configureGate(invalidGate)).toThrow();
});
it("should enable/disable gates dynamically", () => {
pipeline.enableGate("pre-commit");
expect(pipeline.isGateEnabled("pre-commit")).toBe(true);
pipeline.disableGate("pre-commit");
expect(pipeline.isGateEnabled("pre-commit")).toBe(false);
});
});
describe("Reporting", () => {
it("should generate comprehensive security report", async () => {
const report = await pipeline.generateReport();
expect(report).toBeDefined();
expect(report.reportId).toBeDefined();
expect(report.timestamp).toBeInstanceOf(Date);
expect(report.summary).toBeDefined();
expect(report.gates).toBeInstanceOf(Array);
});
it("should export reports in multiple formats", async () => {
const jsonReport = await pipeline.exportReport("json");
const htmlReport = await pipeline.exportReport("html");
const xmlReport = await pipeline.exportReport("xml");
expect(jsonReport).toBeDefined();
expect(htmlReport).toBeDefined();
expect(xmlReport).toBeDefined();
});
it("should include gate results in report", async () => {
await pipeline.executePreCommitGate();
const report = await pipeline.generateReport();
expect(report.gates).toHaveLength(1);
expect(report.gates[0].gateName).toBe("pre-commit");
});
it("should calculate security metrics correctly", async () => {
const metrics = await pipeline.calculateSecurityMetrics();
expect(metrics).toBeDefined();
expect(metrics.overallScore).toBeGreaterThanOrEqual(0);
expect(metrics.overallScore).toBeLessThanOrEqual(100);
expect(metrics.riskLevel).toBeDefined();
expect(metrics.complianceStatus).toBeDefined();
});
});
describe("Automated Remediation", () => {
it("should trigger auto-remediation for fixable issues", async () => {
pipeline.scanner.scanCodeForVulnerabilities.mockResolvedValue({
vulnerabilities: [
{
severity: "medium",
type: "dependency",
autoFixable: true,
description: "Outdated package",
},
],
riskScore: 0.4,
confidence: 0.9,
});
const result = await pipeline.executeAutoRemediation();
expect(result).toBeDefined();
expect(pipeline.remediation.autoFix).toHaveBeenCalled();
});
it("should generate remediation recommendations", async () => {
const recommendations = await pipeline.generateRemediationPlan();
expect(recommendations).toBeInstanceOf(Array);
expect(pipeline.remediation.generateRecommendations).toHaveBeenCalled();
});
it("should handle remediation failures gracefully", async () => {
pipeline.remediation.autoFix.mockRejectedValue(new Error("Remediation failed"));
const result = await pipeline.executeAutoRemediation();
expect(result.status).toBe("failed");
expect(result.error).toBeDefined();
});
});
describe("Integration Workflow", () => {
it("should execute complete CI pipeline", async () => {
const result = await pipeline.executeFullPipeline();
expect(result).toBeDefined();
expect(result.stages).toBeDefined();
expect(result.overallStatus).toBeDefined();
expect(result.duration).toBeGreaterThan(0);
});
it("should stop pipeline on blocking failures", async () => {
pipeline.scanner.scanCodeForVulnerabilities.mockResolvedValue({
vulnerabilities: [{ severity: "critical", description: "Critical vulnerability" }],
riskScore: 0.95,
confidence: 0.98,
});
const result = await pipeline.executeFullPipeline();
expect(result.overallStatus).toBe("failed");
expect(result.blockedBy).toBeDefined();
});
it("should continue pipeline on non-blocking failures", async () => {
// Configure gates as non-blocking
pipeline.configureGate({
id: "pre-deploy",
blocking: false,
stage: "pre-deploy",
enabled: true,
});
pipeline.scanner.scanCodeForVulnerabilities.mockResolvedValue({
vulnerabilities: [{ severity: "medium", description: "Medium issue" }],
riskScore: 0.4,
confidence: 0.9,
});
const result = await pipeline.executeFullPipeline();
expect(["passed", "warning"]).toContain(result.overallStatus);
});
});
describe("Notifications", () => {
it("should send notifications on security failures", async () => {
const notificationSpy = vi.spyOn(pipeline, "sendNotification");
// Ensure the mock is properly set up
pipeline.scanner.scanCodeForVulnerabilities = vi.fn().mockResolvedValue({
vulnerabilities: [{ severity: "critical", description: "Critical issue" }],
riskScore: 0.9,
confidence: 0.95,
});
await pipeline.executePreCommitGate();
expect(notificationSpy).toHaveBeenCalled();
});
it("should format notifications correctly", () => {
const notification = pipeline.formatNotification({
stage: "pre-commit",
status: "failed",
criticalIssues: 1,
highIssues: 2,
});
expect(notification).toBeDefined();
expect(notification.subject).toContain("Security Gate Failed");
expect(notification.body).toContain("critical");
});
});
describe("Performance", () => {
it("should execute gates within reasonable time", async () => {
const startTime = Date.now();
await pipeline.executePreCommitGate();
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(5000); // Should complete within 5 seconds
});
it("should handle concurrent gate execution", async () => {
const promises = [
pipeline.executePreCommitGate(),
pipeline.executePreBuildGate(),
pipeline.executePreDeployGate(),
];
const results = await Promise.all(promises);
expect(results).toHaveLength(3);
results.forEach((result) => {
expect(result.status).toBeDefined();
});
});
});
describe("Error Handling", () => {
it("should handle scanner initialization failures", () => {
const brokenConfig = {
...mockConfig,
scannerConfig: { invalid: true },
};
expect(() => new SecurityCIPipeline(brokenConfig)).toThrow();
});
it("should recover from transient failures", async () => {
let callCount = 0;
pipeline.scanner.scanCodeForVulnerabilities.mockImplementation(() => {
callCount++;
if (callCount === 1) {
return Promise.reject(new Error("Transient failure"));
}
return Promise.resolve({
vulnerabilities: [],
riskScore: 0.1,
confidence: 0.95,
});
});
const result = await pipeline.runVulnerabilityCheck({ retries: 1 });
expect(result.status).toBe("passed");
expect(callCount).toBe(2);
});
it("should handle malformed security responses", async () => {
pipeline.scanner.scanCodeForVulnerabilities.mockResolvedValue(null);
const result = await pipeline.runVulnerabilityCheck();
expect(result.status).toBe("failed");
expect(result.error).toContain("Invalid response");
});
});
describe("Configuration Management", () => {
it("should reload configuration dynamically", () => {
const newConfig = {
...mockConfig,
environment: "production",
};
pipeline.reloadConfiguration(newConfig);
expect(pipeline.config.environment).toBe("production");
});
it("should validate configuration changes", () => {
const invalidConfig = {
...mockConfig,
projectPath: null,
};
expect(() => pipeline.reloadConfiguration(invalidConfig)).toThrow();
});
it("should preserve gate states across reconfigurations", () => {
pipeline.disableGate("pre-commit");
pipeline.reloadConfiguration({
...mockConfig,
environment: "staging",
});
expect(pipeline.isGateEnabled("pre-commit")).toBe(false);
});
});
});