Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Has been cancelled
Deploy to Cloudflare Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

400 lines
11 KiB
JavaScript

const IntegrityValidator = require('../../src/validation/validators/IntegrityValidator');
const fs = require('fs-extra');
const path = require('path');
const crypto = require('crypto');
describe('IntegrityValidator', () => {
let validator;
const testRegistryPath = path.join(process.cwd(), '.claude/security/component-hashes.json');
beforeEach(() => {
validator = new IntegrityValidator();
});
afterEach(async () => {
// Clean up test registry file
if (await fs.pathExists(testRegistryPath)) {
await fs.remove(testRegistryPath);
}
});
describe('Hash Generation', () => {
it('should generate consistent SHA256 hash', () => {
const content = 'test content';
const hash1 = validator.generateHash(content);
const hash2 = validator.generateHash(content);
expect(hash1).toBe(hash2);
expect(hash1).toHaveLength(64); // SHA256 in hex is 64 chars
});
it('should generate different hashes for different content', () => {
const hash1 = validator.generateHash('content 1');
const hash2 = validator.generateHash('content 2');
expect(hash1).not.toBe(hash2);
});
it('should generate expected hash for known content', () => {
const content = 'Hello, World!';
const expectedHash = crypto.createHash('sha256').update(content, 'utf8').digest('hex');
const actualHash = validator.generateHash(content);
expect(actualHash).toBe(expectedHash);
});
});
describe('Basic Validation', () => {
it('should validate and generate hash for valid component', async () => {
const component = {
content: `---
name: test-agent
description: A test agent
tools: Read
version: 1.0.0
---
Content`,
path: 'test-agent.md',
type: 'agent',
version: '1.0.0'
};
const result = await validator.validate(component);
expect(result.valid).toBe(true);
expect(result.hash).toBeDefined();
expect(result.hash).toHaveLength(64);
expect(result.version).toBe('1.0.0');
});
it('should error when content is missing', async () => {
const component = {
content: null,
path: 'test.md',
type: 'agent'
};
const result = await validator.validate(component);
expect(result.valid).toBe(false);
expect(result.errors).toContainEqual(
expect.objectContaining({
code: 'INT_E001'
})
);
});
});
describe('Hash Verification', () => {
it('should pass when hash matches expected hash', async () => {
const content = 'test content';
const expectedHash = validator.generateHash(content);
const component = {
content,
path: 'test.md',
type: 'agent'
};
const result = await validator.validate(component, { expectedHash });
expect(result.valid).toBe(true);
expect(result.info.some(i => i.code === 'INT_I002')).toBe(true);
});
it('should error when hash does not match expected hash', async () => {
const component = {
content: 'actual content',
path: 'test.md',
type: 'agent'
};
const wrongHash = validator.generateHash('different content');
const result = await validator.validate(component, { expectedHash: wrongHash });
expect(result.valid).toBe(false);
expect(result.errors).toContainEqual(
expect.objectContaining({
code: 'INT_E002'
})
);
});
});
describe('Version Validation', () => {
it('should accept valid semantic version', async () => {
const component = {
content: 'test content',
path: 'test.md',
type: 'agent',
version: '1.2.3'
};
const result = await validator.validate(component);
expect(result.valid).toBe(true);
expect(result.info.some(i => i.code === 'INT_I007')).toBe(true);
});
it('should accept simple version format', async () => {
const component = {
content: 'test content',
path: 'test.md',
type: 'agent',
version: '1.0'
};
const result = await validator.validate(component);
expect(result.valid).toBe(true);
});
it('should warn about invalid version format', async () => {
const component = {
content: 'test content',
path: 'test.md',
type: 'agent',
version: 'v1.0-beta'
};
const result = await validator.validate(component);
expect(result.warnings).toContainEqual(
expect.objectContaining({
code: 'INT_W003'
})
);
});
it('should warn when no version is specified', async () => {
const component = {
content: 'test content',
path: 'test.md',
type: 'agent'
};
const result = await validator.validate(component);
expect(result.warnings).toContainEqual(
expect.objectContaining({
code: 'INT_W002'
})
);
});
});
describe('Hash Registry', () => {
it('should update registry when requested', async () => {
const component = {
content: 'test content',
path: 'test-agent.md',
type: 'agent',
version: '1.0.0'
};
await validator.validate(component, { updateRegistry: true });
const registry = await validator.loadHashRegistry();
const normalizedPath = validator.normalizePath('test-agent.md');
expect(registry[normalizedPath]).toBeDefined();
expect(registry[normalizedPath].hash).toBe(validator.generateHash('test content'));
expect(registry[normalizedPath].version).toBe('1.0.0');
});
it('should detect hash changes from registry', async () => {
const component1 = {
content: 'original content',
path: 'test-agent.md',
type: 'agent',
version: '1.0.0'
};
// First validation - update registry
await validator.validate(component1, { updateRegistry: true });
// Second validation with modified content
const component2 = {
content: 'modified content',
path: 'test-agent.md',
type: 'agent',
version: '1.0.0'
};
const result = await validator.validate(component2);
expect(result.warnings).toContainEqual(
expect.objectContaining({
code: 'INT_W001'
})
);
});
it('should detect version changes', async () => {
const component1 = {
content: 'test content',
path: 'test-agent.md',
type: 'agent',
version: '1.0.0'
};
await validator.validate(component1, { updateRegistry: true });
const component2 = {
content: 'test content',
path: 'test-agent.md',
type: 'agent',
version: '2.0.0'
};
const result = await validator.validate(component2);
expect(result.info.some(i => i.code === 'INT_I004')).toBe(true);
});
it('should handle new components not in registry', async () => {
const component = {
content: 'new content',
path: 'new-agent.md',
type: 'agent'
};
const result = await validator.validate(component);
expect(result.info.some(i => i.code === 'INT_I005' || i.code === 'INT_I006')).toBe(true);
});
});
describe('Batch Validation', () => {
it('should validate multiple components', async () => {
const components = [
{
content: 'content 1',
path: 'agent1.md',
type: 'agent',
version: '1.0.0'
},
{
content: 'content 2',
path: 'agent2.md',
type: 'agent',
version: '1.0.0'
},
{
content: 'content 3',
path: 'agent3.md',
type: 'agent',
version: '1.0.0'
}
];
const result = await validator.batchValidate(components, { updateRegistry: true });
expect(result.total).toBe(3);
expect(result.passed).toBe(3);
expect(result.failed).toBe(0);
expect(result.components).toHaveLength(3);
});
it('should count failures correctly in batch validation', async () => {
const components = [
{
content: 'valid content',
path: 'agent1.md',
type: 'agent',
version: '1.0.0'
},
{
content: null, // Invalid
path: 'agent2.md',
type: 'agent'
},
{
content: 'valid content',
path: 'agent3.md',
type: 'agent'
}
];
const result = await validator.batchValidate(components);
expect(result.total).toBe(3);
expect(result.passed).toBe(2);
expect(result.failed).toBe(1);
});
});
describe('Integrity Report', () => {
it('should generate comprehensive integrity report', async () => {
const component = {
content: `---
name: test-agent
description: A test agent
tools: Read
version: 1.0.0
---
Content`,
path: 'test-agent.md',
type: 'agent',
version: '1.0.0'
};
const report = await validator.generateIntegrityReport(component);
expect(report).toHaveProperty('valid');
expect(report).toHaveProperty('hash');
expect(report).toHaveProperty('version');
expect(report).toHaveProperty('timestamp');
expect(report).toHaveProperty('issues');
expect(report.issues).toHaveProperty('errors');
expect(report.issues).toHaveProperty('warnings');
});
});
describe('Real Component Validation', () => {
it('should validate real frontend-developer agent', async () => {
const agentPath = path.join(
__dirname,
'../../components/agents/development-team/frontend-developer.md'
);
if (!await fs.pathExists(agentPath)) {
console.log('Skipping test: frontend-developer.md not found');
return;
}
const content = await fs.readFile(agentPath, 'utf8');
const component = {
content,
path: agentPath,
type: 'agent',
version: '1.0.0'
};
const result = await validator.validate(component);
expect(result.valid).toBe(true);
expect(result.hash).toBeDefined();
expect(result.hash).toHaveLength(64);
});
});
describe('Path Normalization', () => {
it('should normalize absolute paths to relative', () => {
const absolutePath = path.join(process.cwd(), 'components/agent.md');
const normalized = validator.normalizePath(absolutePath);
expect(normalized).toBe('components/agent.md');
});
it('should keep relative paths unchanged', () => {
const relativePath = 'components/agent.md';
const normalized = validator.normalizePath(relativePath);
expect(normalized).toBe(relativePath);
});
});
});