Skip to main content
Glama
security-client.test.ts15.6 kB
/** * @fileoverview Tests for security client * This file adds coverage for the previously untested security-client.ts */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { SecurityClient } from '../../client/security-client.js'; import type { SecurityClientTestable, MockDeepSourceClient } from '../test-types.js'; import { TestableSecurityClient } from '../utils/test-utils.js'; interface MockBaseClient { findProjectByKey: ReturnType<typeof vi.fn>; executeGraphQL: ReturnType<typeof vi.fn>; createEmptyPaginatedResponse: ReturnType<typeof vi.fn>; normalizePaginationParams: ReturnType<typeof vi.fn>; logger: { info: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn>; debug: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn>; }; } describe('SecurityClient', () => { let securityClient: SecurityClient; let mockBaseClient: MockDeepSourceClient; let mockedClient: MockBaseClient; beforeEach(() => { securityClient = new SecurityClient('test-api-key'); mockBaseClient = securityClient as unknown as MockDeepSourceClient; // Mock the methods we need mockedClient = mockBaseClient as unknown as MockBaseClient; mockedClient.findProjectByKey = vi.fn(); mockedClient.executeGraphQL = vi.fn(); mockedClient.createEmptyPaginatedResponse = vi.fn(); mockedClient.normalizePaginationParams = vi.fn(); mockedClient.logger = { info: vi.fn(), error: vi.fn(), debug: vi.fn(), warn: vi.fn(), }; }); describe('getComplianceReport', () => { it('should fetch compliance report successfully', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: { repository: { reports: { owaspTop10: { status: 'FAILING', categories: [ { name: 'Injection', status: 'FAILING', criticalCount: 2, majorCount: 1, minorCount: 0, total: 3, }, { name: 'Broken Authentication', status: 'PASSING', criticalCount: 0, majorCount: 0, minorCount: 0, total: 0, }, ], }, }, }, }, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockedClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse as any); // skipcq: JS-0323 const result = await securityClient.getComplianceReport('test-project', 'OWASP_TOP_10'); expect(mockedClient.findProjectByKey).toHaveBeenCalledWith('test-project'); expect(mockedClient.executeGraphQL).toHaveBeenCalledWith( expect.stringContaining('query getComplianceReports'), expect.objectContaining({ login: 'test-org', name: 'test-repo', provider: 'github', }) ); expect(result).not.toBeNull(); if (result) { expect(result.reportType).toBe('OWASP_TOP_10'); expect(result.status).toBe('FAILING'); expect(result.categories).toHaveLength(2); expect(result.categories[0].name).toBe('Injection'); } }); it('should return null when project not found', async () => { mockedClient.findProjectByKey = vi.fn().mockResolvedValue(null); const result = await securityClient.getComplianceReport( 'nonexistent-project', 'OWASP_TOP_10' ); expect(result).toBeNull(); }); it('should handle GraphQL errors', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); vi.spyOn(mockedClient, 'executeGraphQL').mockRejectedValue(new Error('GraphQL error')); await expect( securityClient.getComplianceReport('test-project', 'OWASP_TOP_10') ).rejects.toThrow('GraphQL error'); }); it('should return null when no data in response', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: null, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockedClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse as any); // skipcq: JS-0323 const result = await securityClient.getComplianceReport('test-project', 'OWASP_TOP_10'); expect(result).toBeNull(); }); it('should handle missing compliance report in response', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: { repository: { complianceReport: null, }, }, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockedClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse as any); // skipcq: JS-0323 const result = await securityClient.getComplianceReport('test-project', 'OWASP_TOP_10'); expect(result).toBeNull(); }); }); describe('getDependencyVulnerabilities', () => { it('should fetch dependency vulnerabilities successfully', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: { repository: { dependencyVulnerabilities: { edges: [ { node: { id: 'vuln-1', package: { id: 'pkg-1', ecosystem: 'npm', name: 'lodash', }, packageVersion: { id: 'ver-1', version: '4.17.20', }, vulnerability: { id: 'cve-1', identifier: 'CVE-2023-1234', summary: 'Prototype pollution vulnerability', severity: 'HIGH', fixedVersions: ['4.17.21'], }, }, }, { node: { id: 'vuln-2', package: { id: 'pkg-2', ecosystem: 'npm', name: 'express', }, packageVersion: { id: 'ver-2', version: '4.16.0', }, vulnerability: { id: 'cve-2', identifier: 'CVE-2023-5678', summary: 'Security bypass vulnerability', severity: 'MEDIUM', fixedVersions: ['4.18.0'], }, }, }, ], pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null, }, }, }, }, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockedClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 20, after: null, }); mockedClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse as any); // skipcq: JS-0323 const result = await securityClient.getDependencyVulnerabilities('test-project'); expect(mockedClient.findProjectByKey).toHaveBeenCalledWith('test-project'); expect(mockedClient.executeGraphQL).toHaveBeenCalledWith( expect.stringContaining('query getDependencyVulnerabilities'), expect.objectContaining({ login: 'test-org', name: 'test-repo', provider: 'github', }) ); expect(result.items).toHaveLength(2); expect(result.items[0].vulnerability.identifier).toBe('CVE-2023-1234'); expect(result.items[0].package.name).toBe('lodash'); expect(result.items[1].vulnerability.severity).toBe('MEDIUM'); expect(result.pageInfo.hasNextPage).toBe(false); }); it('should return empty response when project not found', async () => { mockedClient.findProjectByKey = vi.fn().mockResolvedValue(null); mockedClient.createEmptyPaginatedResponse = vi.fn().mockReturnValue({ items: [], pageInfo: { hasNextPage: false, hasPreviousPage: false }, totalCount: 0, }); const result = await securityClient.getDependencyVulnerabilities('nonexistent-project'); expect(result.items).toHaveLength(0); expect(result.totalCount).toBe(0); }); it('should handle GraphQL errors', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockedClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 20, }); vi.spyOn(mockedClient, 'executeGraphQL').mockRejectedValue(new Error('GraphQL error')); await expect(securityClient.getDependencyVulnerabilities('test-project')).rejects.toThrow( 'GraphQL error' ); }); it('should handle pagination parameters', async () => { const mockProject = { repository: { login: 'test-org', name: 'test-repo', provider: 'github', }, }; const mockResponse = { data: { repository: { vulnerabilities: { edges: [], pageInfo: { hasNextPage: false, hasPreviousPage: false, startCursor: null, endCursor: null, }, }, }, }, }; mockedClient.findProjectByKey = vi.fn().mockResolvedValue(mockProject); mockedClient.normalizePaginationParams = vi.fn().mockReturnValue({ first: 10, after: 'cursor123', }); mockedClient.executeGraphQL = vi.fn().mockResolvedValue(mockResponse as any); // skipcq: JS-0323 await securityClient.getDependencyVulnerabilities('test-project', { first: 10, after: 'cursor123', }); // The normalizePaginationParams is now a static method and its behavior // is tested separately in base-client tests }); }); describe('buildComplianceReportQuery', () => { it('should build correct GraphQL query', () => { const query = TestableSecurityClient.testBuildComplianceReportQuery(); expect(query).toContain('query getComplianceReports'); expect(query).toContain('$login: String!'); expect(query).toContain('$name: String!'); expect(query).toContain('$provider: VCSProvider!'); expect(query).toContain('reports'); }); }); describe('buildVulnerabilitiesQuery', () => { it('should build correct GraphQL query', () => { // Add a test method for buildVulnerabilitiesQuery to TestableSecurityClient const query = TestableSecurityClient.testBuildVulnerabilitiesQuery(); expect(query).toContain('query getDependencyVulnerabilities'); expect(query).toContain('$login: String!'); expect(query).toContain('$name: String!'); expect(query).toContain('$provider: VCSProvider!'); expect(query).toContain('dependencyVulnerabilities'); }); }); describe('extractComplianceReportFromResponse', () => { it('should extract compliance report from GraphQL response', () => { const mockResponseData = { repository: { reports: { owaspTop10: { status: 'FAILING', categories: [ { name: 'Injection', status: 'FAILING', criticalCount: 2, majorCount: 1, minorCount: 0, total: 3, }, ], }, }, }, }; const report = ( securityClient as unknown as SecurityClientTestable ).extractComplianceReportFromResponse(mockResponseData, 'OWASP_TOP_10', 'owaspTop10'); expect(report).not.toBeNull(); expect(report.reportType).toBe('OWASP_TOP_10'); expect(report.status).toBe('FAILING'); expect(report.categories).toHaveLength(1); }); it('should return null when compliance report is missing', () => { const mockResponseData = { repository: { reports: { owaspTop10: null, }, }, }; const report = ( securityClient as unknown as SecurityClientTestable ).extractComplianceReportFromResponse(mockResponseData, 'OWASP_TOP_10', 'owaspTop10'); expect(report).toBeNull(); }); it('should handle missing repository in response', () => { const mockResponseData = {}; const report = ( securityClient as unknown as SecurityClientTestable ).extractComplianceReportFromResponse(mockResponseData, 'OWASP_TOP_10', 'owaspTop10'); expect(report).toBeNull(); }); }); describe('extractVulnerabilitiesFromResponse', () => { it('should extract vulnerabilities from GraphQL response', () => { const mockResponseData = { repository: { dependencyVulnerabilities: { edges: [ { node: { id: 'vuln-1', package: { id: 'pkg-1', ecosystem: 'npm', name: 'lodash', }, packageVersion: { id: 'ver-1', version: '4.17.20', }, vulnerability: { id: 'cve-1', identifier: 'CVE-2023-1234', summary: 'Prototype pollution vulnerability', severity: 'HIGH', fixedVersions: ['4.17.21'], }, }, }, ], }, }, }; const vulnerabilities = ( securityClient as unknown as SecurityClientTestable ).extractVulnerabilitiesFromResponse(mockResponseData); expect(vulnerabilities).toHaveLength(1); expect(vulnerabilities[0].vulnerability.identifier).toBe('CVE-2023-1234'); expect(vulnerabilities[0].package.name).toBe('lodash'); }); it('should handle missing vulnerabilities in response', () => { const mockResponseData = { repository: {}, }; const vulnerabilities = ( securityClient as unknown as SecurityClientTestable ).extractVulnerabilitiesFromResponse(mockResponseData); expect(vulnerabilities).toHaveLength(0); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sapientpants/deepsource-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server