ssvc
Version:
TypeScript implementation of SSVC (Stakeholder-Specific Vulnerability Categorization). A prioritization framework to triage CVE vulnerabilities as an alternative or compliment to CVSS
291 lines (245 loc) • 10.1 kB
text/typescript
/**
* Runtime System Validation Tests
*
* Tests to validate that the runtime system works correctly with all existing methodologies
*/
import * as fs from 'fs';
import * as path from 'path';
import {
createRuntimePlugin,
customFromYAML,
getMethodologyMetadata,
supportsVectorStrings,
validateYAML
} from './index';
describe('Runtime System - Methodology Validation', () => {
let methodologyFiles: string[];
let validMethodologies: { name: string; content: string; metadata: any }[];
beforeAll(() => {
// Get all methodology files
const methodologiesDir = path.join(__dirname, '../../methodologies');
methodologyFiles = fs.readdirSync(methodologiesDir)
.filter(file => file.endsWith('.yaml'))
.map(file => path.join(methodologiesDir, file));
// Validate and collect valid methodologies
validMethodologies = [];
for (const filePath of methodologyFiles) {
const content = fs.readFileSync(filePath, 'utf8');
const validation = validateYAML(content);
if (validation.valid && validation.methodology) {
const metadata = getMethodologyMetadata(content);
validMethodologies.push({
name: metadata.name,
content,
metadata
});
}
}
console.log(`Found ${validMethodologies.length} valid methodologies: ${validMethodologies.map(m => m.name).join(', ')}`);
});
test('should validate all existing methodology files', () => {
expect(methodologyFiles.length).toBeGreaterThan(0);
for (const filePath of methodologyFiles) {
const fileName = path.basename(filePath);
const content = fs.readFileSync(filePath, 'utf8');
const validation = validateYAML(content);
if (!validation.valid) {
console.error(`${fileName} validation errors:`, validation.errors);
}
expect(validation.valid).toBe(true);
}
});
test('should create runtime plugins for all valid methodologies', () => {
for (const methodology of validMethodologies) {
const plugin = createRuntimePlugin(methodology.content);
expect(plugin.name).toBe(methodology.name);
expect(plugin.description).toBe(methodology.metadata.description);
expect(plugin.version).toBe(methodology.metadata.version);
}
});
describe('CISA Methodology', () => {
let cisaMethodology: { name: string; content: string; metadata: any };
beforeAll(() => {
cisaMethodology = validMethodologies.find(m => m.name === 'CISA')!;
expect(cisaMethodology).toBeDefined();
});
test('should evaluate CISA decisions correctly', () => {
const testCases = [
{
params: { exploitation: 'active', automatable: 'yes', technical_impact: 'total', mission_wellbeing: 'high' },
expectedAction: 'ACT'
},
{
params: { exploitation: 'none', automatable: 'no', technical_impact: 'partial', mission_wellbeing: 'low' },
expectedAction: 'TRACK'
},
{
params: { exploitation: 'poc', automatable: 'yes', technical_impact: 'total', mission_wellbeing: 'medium' },
expectedAction: 'TRACK_STAR'
}
];
for (const testCase of testCases) {
const outcome = customFromYAML(cisaMethodology.content, testCase.params);
expect(outcome.action).toBe(testCase.expectedAction);
expect(outcome.priority).toBeDefined();
}
});
test('should support vector strings for CISA', () => {
const supportsVectors = supportsVectorStrings(cisaMethodology.content);
expect(supportsVectors).toBe(true);
});
});
describe('Coordinator Triage Methodology', () => {
let coordinatorMethodology: { name: string; content: string; metadata: any };
beforeAll(() => {
coordinatorMethodology = validMethodologies.find(m => m.name.includes('Coordinator') && m.name.includes('Triage'))!;
if (coordinatorMethodology) {
console.log(`Found coordinator methodology: ${coordinatorMethodology.name}`);
}
});
test('should evaluate coordinator triage decisions', () => {
if (!coordinatorMethodology) {
console.log('Skipping coordinator triage test - methodology not found');
return;
}
const testCases = [
{
params: {
report_public: 'yes',
supplier_contacted: 'yes',
report_credibility: 'credible',
supplier_cardinality: 'multiple'
}
},
{
params: {
report_public: 'no',
supplier_contacted: 'no',
report_credibility: 'credible',
supplier_cardinality: 'single'
}
}
];
for (const testCase of testCases) {
const outcome = customFromYAML(coordinatorMethodology.content, testCase.params);
expect(outcome.action).toBeDefined();
expect(outcome.priority).toBeDefined();
}
});
});
describe('All Methodologies - Basic Functionality', () => {
test('should create plugins and handle basic operations for all methodologies', () => {
for (const methodology of validMethodologies) {
// Create plugin
const plugin = createRuntimePlugin(methodology.content);
expect(plugin).toBeDefined();
// Test basic decision creation (with empty parameters - should use defaults)
const decision = plugin.createDecision({});
expect(decision).toBeDefined();
// Should be able to evaluate even with empty params (will use default action)
const outcome = decision.evaluate();
expect(outcome.action).toBeDefined();
expect(outcome.priority).toBeDefined();
}
});
test('should handle vector string support correctly for all methodologies', () => {
for (const methodology of validMethodologies) {
const supportsVectors = supportsVectorStrings(methodology.content);
expect(typeof supportsVectors).toBe('boolean');
if (supportsVectors) {
console.log(`${methodology.name} supports vector strings`);
}
}
});
test('should extract metadata correctly for all methodologies', () => {
for (const methodology of validMethodologies) {
const metadata = getMethodologyMetadata(methodology.content);
expect(metadata.name).toBeDefined();
expect(metadata.description).toBeDefined();
expect(metadata.version).toBeDefined();
expect(metadata.version).toMatch(/^\d+\.\d+/); // semver format
}
});
});
describe('Parameter Flexibility Tests', () => {
test('should handle various parameter naming conventions', () => {
const cisaMethodology = validMethodologies.find(m => m.name === 'CISA');
if (!cisaMethodology) return;
const baseParams = {
exploitation: 'active',
automatable: 'yes',
technical_impact: 'total',
mission_wellbeing: 'high'
};
// Test snake_case
const outcome1 = customFromYAML(cisaMethodology.content, baseParams);
// Test camelCase variants
const camelParams = {
exploitation: 'active',
automatable: 'yes',
technicalImpact: 'total',
missionWellbeing: 'high'
};
const outcome2 = customFromYAML(cisaMethodology.content, camelParams);
// Test mixed case enum values
const mixedParams = {
exploitation: 'ACTIVE',
automatable: 'YES',
technical_impact: 'TOTAL',
mission_wellbeing: 'HIGH'
};
const outcome3 = customFromYAML(cisaMethodology.content, mixedParams);
// All should produce the same result
expect(outcome1.action).toBe(outcome2.action);
expect(outcome1.action).toBe(outcome3.action);
expect(outcome1.priority).toBe(outcome2.priority);
expect(outcome1.priority).toBe(outcome3.priority);
});
});
describe('Error Handling and Edge Cases', () => {
test('should handle missing parameters gracefully', () => {
for (const methodology of validMethodologies) {
// Should not throw with empty parameters - should use default action
const outcome = customFromYAML(methodology.content, {});
expect(outcome.action).toBeDefined();
expect(outcome.priority).toBeDefined();
}
});
test('should handle invalid parameter values', () => {
const cisaMethodology = validMethodologies.find(m => m.name === 'CISA');
if (!cisaMethodology) return;
// Invalid enum values should fall back to default action
const outcome = customFromYAML(cisaMethodology.content, {
exploitation: 'invalid_value',
automatable: 'maybe',
technical_impact: 'unknown',
mission_wellbeing: 'undefined'
});
// Should still produce a valid outcome (likely the default action)
expect(outcome.action).toBeDefined();
expect(outcome.priority).toBeDefined();
});
});
describe('Performance and Memory', () => {
test('should handle multiple evaluations efficiently', () => {
const cisaMethodology = validMethodologies.find(m => m.name === 'CISA');
if (!cisaMethodology) return;
const startTime = Date.now();
const iterations = 100;
for (let i = 0; i < iterations; i++) {
const outcome = customFromYAML(cisaMethodology.content, {
exploitation: i % 2 === 0 ? 'active' : 'none',
automatable: i % 3 === 0 ? 'yes' : 'no',
technical_impact: i % 2 === 0 ? 'total' : 'partial',
mission_wellbeing: ['low', 'medium', 'high'][i % 3]
});
expect(outcome.action).toBeDefined();
expect(outcome.priority).toBeDefined();
}
const duration = Date.now() - startTime;
console.log(`${iterations} evaluations completed in ${duration}ms (${(duration/iterations).toFixed(2)}ms per evaluation)`);
// Should complete reasonably quickly (less than 1 second for 100 evaluations)
expect(duration).toBeLessThan(10000);
});
});
});