/**
* @vitest-environment node
*/
import { vi } from 'vitest';
import { DeepSourceClient } from '../deepsource.js';
// Create a test subclass to expose private methods
class TestableDeepSourceClient extends DeepSourceClient {
static testValidateProjectKey(projectKey: string): void {
// @ts-expect-error - Accessing private method for testing
return DeepSourceClient.validateProjectKey(projectKey);
}
static testIsValidVulnerabilityNode(node: unknown): boolean {
// @ts-expect-error - Accessing private method for testing
return DeepSourceClient.isValidVulnerabilityNode(node);
}
static testValidateProjectRepository(project: Record<string, unknown>, projectKey: string): void {
// @ts-expect-error - Accessing private method for testing
return DeepSourceClient.validateProjectRepository(project, projectKey);
}
static testValidateNumber(value: unknown): number | null {
// @ts-expect-error - Accessing private method for testing
return DeepSourceClient.validateNumber(value);
}
}
// We need to access private methods for testing
// @ts-expect-error - Accessing private static method for testing
const isValidVulnerabilityNode = DeepSourceClient['isValidVulnerabilityNode'];
describe('DeepSourceClient validation methods', () => {
// Create a spy for the logger
const originalLogger = DeepSourceClient['logger'];
const mockWarn = vi.fn();
beforeEach(() => {
// Mock the logger's warn method
DeepSourceClient['logger'] = {
warn: mockWarn,
// Add other required methods to satisfy TypeScript
debug: vi.fn(),
info: vi.fn(),
error: vi.fn(),
};
vi.clearAllMocks();
});
afterAll(() => {
// Restore the original logger
DeepSourceClient['logger'] = originalLogger;
});
describe('isValidVulnerabilityNode', () => {
it('should return false for null or undefined', () => {
expect(isValidVulnerabilityNode(null)).toBe(false);
expect(isValidVulnerabilityNode(undefined)).toBe(false);
expect(mockWarn).toHaveBeenCalledTimes(2);
});
it('should return false for non-object values', () => {
expect(isValidVulnerabilityNode('string')).toBe(false);
expect(isValidVulnerabilityNode(123)).toBe(false);
expect(isValidVulnerabilityNode(true)).toBe(false);
expect(mockWarn).toHaveBeenCalledTimes(3);
});
it('should return false for object without an id', () => {
const node = { someField: 'value' };
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid ID',
node
);
});
it('should return false for object with non-string id', () => {
const node = { id: 123 };
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid ID',
node
);
});
it('should return false when package is missing or not an object', () => {
const node = { id: 'vuln1' };
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid package',
node
);
const nodeWithNonObjectPackage = { id: 'vuln1', package: 'not-an-object' };
expect(isValidVulnerabilityNode(nodeWithNonObjectPackage)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid package',
nodeWithNonObjectPackage
);
});
it('should return false when packageVersion is missing or not an object', () => {
const node = { id: 'vuln1', package: {} };
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid packageVersion',
node
);
const nodeWithNonObjectPackageVersion = {
id: 'vuln1',
package: {},
packageVersion: 'not-an-object',
};
expect(isValidVulnerabilityNode(nodeWithNonObjectPackageVersion)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid packageVersion',
nodeWithNonObjectPackageVersion
);
});
it('should return false when vulnerability is missing or not an object', () => {
const node = { id: 'vuln1', package: {}, packageVersion: {} };
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid vulnerability',
node
);
const nodeWithNonObjectVulnerability = {
id: 'vuln1',
package: {},
packageVersion: {},
vulnerability: 'not-an-object',
};
expect(isValidVulnerabilityNode(nodeWithNonObjectVulnerability)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability node with missing or invalid vulnerability',
nodeWithNonObjectVulnerability
);
});
it('should return false when package is missing required fields', () => {
const node = {
id: 'vuln1',
package: { missing: 'required fields' },
packageVersion: {},
vulnerability: {},
};
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability with incomplete package information',
node.package
);
});
it('should return false when packageVersion is missing required fields', () => {
const node = {
id: 'vuln1',
package: { id: 'pkg1', ecosystem: 'npm', name: 'express' },
packageVersion: { missing: 'required fields' },
vulnerability: {},
};
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability with incomplete package version information',
node.packageVersion
);
});
it('should return false when vulnerability is missing required fields', () => {
const node = {
id: 'vuln1',
package: { id: 'pkg1', ecosystem: 'npm', name: 'express' },
packageVersion: { id: 'ver1', version: '1.0.0' },
vulnerability: { missing: 'required fields' },
};
expect(isValidVulnerabilityNode(node)).toBe(false);
expect(mockWarn).toHaveBeenCalledWith(
'Skipping vulnerability with incomplete vulnerability information',
node.vulnerability
);
});
it('should return true for valid vulnerability node', () => {
const validNode = {
id: 'vuln1',
package: { id: 'pkg1', ecosystem: 'npm', name: 'express' },
packageVersion: { id: 'ver1', version: '1.0.0' },
vulnerability: { id: 'cve1', identifier: 'CVE-2022-1234' },
};
expect(isValidVulnerabilityNode(validNode)).toBe(true);
expect(mockWarn).not.toHaveBeenCalled();
});
});
describe('validateProjectKey', () => {
it('should throw an error for null or undefined project keys', () => {
expect(() => {
// @ts-expect-error - Testing with invalid type
TestableDeepSourceClient.testValidateProjectKey(null);
}).toThrow('Invalid project key: Project key must be a non-empty string');
expect(() => {
// @ts-expect-error - Testing with invalid type
TestableDeepSourceClient.testValidateProjectKey(undefined);
}).toThrow('Invalid project key: Project key must be a non-empty string');
});
it('should throw an error for non-string project keys', () => {
expect(() => {
// @ts-expect-error - Testing with invalid type
TestableDeepSourceClient.testValidateProjectKey(123);
}).toThrow('Invalid project key: Project key must be a non-empty string');
expect(() => {
// @ts-expect-error - Testing with invalid type
TestableDeepSourceClient.testValidateProjectKey({});
}).toThrow('Invalid project key: Project key must be a non-empty string');
expect(() => {
// @ts-expect-error - Testing with invalid type
TestableDeepSourceClient.testValidateProjectKey(true);
}).toThrow('Invalid project key: Project key must be a non-empty string');
});
it('should throw an error for empty string project keys', () => {
expect(() => {
TestableDeepSourceClient.testValidateProjectKey('');
}).toThrow('Invalid project key: Project key must be a non-empty string');
});
it('should not throw for valid project keys', () => {
expect(() => {
TestableDeepSourceClient.testValidateProjectKey('valid-project-key');
}).not.toThrow();
expect(() => {
TestableDeepSourceClient.testValidateProjectKey('12345abcdef');
}).not.toThrow();
});
});
describe('validateNumber', () => {
it('should validate and return number values', () => {
expect(TestableDeepSourceClient.testValidateNumber(42)).toBe(42);
expect(TestableDeepSourceClient.testValidateNumber(0)).toBe(0);
expect(TestableDeepSourceClient.testValidateNumber(-1)).toBe(-1);
expect(TestableDeepSourceClient.testValidateNumber(3.14)).toBe(3.14);
});
it('should return null for non-number values', () => {
expect(TestableDeepSourceClient.testValidateNumber('42')).toBeNull();
expect(TestableDeepSourceClient.testValidateNumber(null)).toBeNull();
expect(TestableDeepSourceClient.testValidateNumber(undefined)).toBeNull();
expect(TestableDeepSourceClient.testValidateNumber({})).toBeNull();
expect(TestableDeepSourceClient.testValidateNumber([])).toBeNull();
expect(TestableDeepSourceClient.testValidateNumber(true)).toBeNull();
});
});
describe('validateProjectRepository', () => {
it('should not throw for valid project data', () => {
const project = {
name: 'Test Project',
repository: {
login: 'test-org',
provider: 'github',
},
};
expect(() => {
TestableDeepSourceClient.testValidateProjectRepository(project, 'test-key');
}).not.toThrow();
});
it('should throw for invalid project data', () => {
const invalidProject = {
name: 'Test Project',
// Missing repository property
};
expect(() => {
TestableDeepSourceClient.testValidateProjectRepository(invalidProject, 'test-key');
}).toThrow();
});
});
});