From f67638f00e3dc5d2b507de9c555caba39011e5bc Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 12 Jun 2026 16:04:25 +0530 Subject: [PATCH 1/6] test: add curlRequest method tests to ApexGuruAuthService Add comprehensive test coverage for new curlRequest method: - GET request with correct headers and URL construction - POST request with body serialization - Error handling for non-200 HTTP responses - Network error handling with descriptive messages Tests verify Authorization Bearer pattern and Content-Type headers. --- node_modules | 1 + packages/ENGINE-TEMPLATE/node_modules | 1 + .../node_modules | 1 + .../src/services/ApexGuruAuthService.ts | 39 +++++++++ .../test/ApexGuruAuthService.test.ts | 80 +++++++++++++++++++ packages/code-analyzer-core/node_modules | 1 + .../code-analyzer-engine-api/node_modules | 1 + .../code-analyzer-eslint-engine/node_modules | 1 + .../code-analyzer-eslint8-engine/node_modules | 1 + .../code-analyzer-flow-engine/node_modules | 1 + .../code-analyzer-pmd-engine/node_modules | 1 + .../code-analyzer-regex-engine/node_modules | 1 + .../node_modules | 1 + .../code-analyzer-sfge-engine/node_modules | 1 + 14 files changed, 131 insertions(+) create mode 120000 node_modules create mode 120000 packages/ENGINE-TEMPLATE/node_modules create mode 120000 packages/code-analyzer-apexguru-engine/node_modules create mode 120000 packages/code-analyzer-core/node_modules create mode 120000 packages/code-analyzer-engine-api/node_modules create mode 120000 packages/code-analyzer-eslint-engine/node_modules create mode 120000 packages/code-analyzer-eslint8-engine/node_modules create mode 120000 packages/code-analyzer-flow-engine/node_modules create mode 120000 packages/code-analyzer-pmd-engine/node_modules create mode 120000 packages/code-analyzer-regex-engine/node_modules create mode 120000 packages/code-analyzer-retirejs-engine/node_modules create mode 120000 packages/code-analyzer-sfge-engine/node_modules diff --git a/node_modules b/node_modules new file mode 120000 index 00000000..852e7d60 --- /dev/null +++ b/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/node_modules \ No newline at end of file diff --git a/packages/ENGINE-TEMPLATE/node_modules b/packages/ENGINE-TEMPLATE/node_modules new file mode 120000 index 00000000..21586c3c --- /dev/null +++ b/packages/ENGINE-TEMPLATE/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/ENGINE-TEMPLATE/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-apexguru-engine/node_modules b/packages/code-analyzer-apexguru-engine/node_modules new file mode 120000 index 00000000..8b66a021 --- /dev/null +++ b/packages/code-analyzer-apexguru-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-apexguru-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts index b92caf75..ce9c50ba 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts @@ -191,4 +191,43 @@ export class ApexGuruAuthService { } return await this.mintOrgJwt(); } + + /** + * Perform HTTP request using fetch with Authorization Bearer token + * @param method - HTTP method (GET, POST, etc.) + * @param path - API path (e.g., '/services/data/v64.0/apexguru/validate') + * @param body - Optional request body + * @returns Promise - Parsed JSON response + * @throws Error if request fails or returns non-200 status + */ + private async curlRequest(method: string, path: string, body?: unknown): Promise { + try { + const url = `${this.getInstanceUrl()}${path}`; + const accessToken = this.getAccessToken(); + + const response = await fetch(url, { + method, + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json' + }, + body: body ? JSON.stringify(body) : undefined + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `HTTP ${response.status} ${response.statusText} at ${path}: ${errorText}` + ); + } + + return await response.json() as T; + } catch (error) { + if (error instanceof Error && error.message.startsWith('HTTP ')) { + throw error; + } + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Request failed for ${path}: ${errorMessage}`); + } + } } diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts index c8fe8c35..dae83b22 100644 --- a/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruAuthService.test.ts @@ -118,4 +118,84 @@ describe('ApexGuruAuthService', () => { // - Should mint new JWT if not cached // - Should return cached JWT if available }); + + describe('curlRequest', () => { + beforeEach(async () => { + const mockOrg = { + getConnection: jest.fn().mockReturnValue(mockConnection) + }; + (Org.create as jest.Mock).mockResolvedValue(mockOrg); + await authService.initialize({ targetOrg: 'myorg' }); + }); + + it('should perform GET request with correct headers and URL', async () => { + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ status: 'success' }) + }); + global.fetch = mockFetch as any; + + const result = await (authService as any).curlRequest('GET', '/services/data/v64.0/apexguru/validate'); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://test.salesforce.com/services/data/v64.0/apexguru/validate', + { + method: 'GET', + headers: { + 'Authorization': 'Bearer mock_access_token', + 'Content-Type': 'application/json' + }, + body: undefined + } + ); + expect(result).toEqual({ status: 'success' }); + }); + + it('should perform POST request with body', async () => { + const mockFetch = jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ requestId: '12345' }) + }); + global.fetch = mockFetch as any; + + const requestBody = { classContent: 'base64string' }; + const result = await (authService as any).curlRequest('POST', '/services/data/v64.0/apexguru/request', requestBody); + + expect(mockFetch).toHaveBeenCalledWith( + 'https://test.salesforce.com/services/data/v64.0/apexguru/request', + { + method: 'POST', + headers: { + 'Authorization': 'Bearer mock_access_token', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(requestBody) + } + ); + expect(result).toEqual({ requestId: '12345' }); + }); + + it('should throw error for non-200 response', async () => { + const mockFetch = jest.fn().mockResolvedValue({ + ok: false, + status: 401, + statusText: 'Unauthorized', + text: jest.fn().mockResolvedValue('Invalid token') + }); + global.fetch = mockFetch as any; + + await expect((authService as any).curlRequest('GET', '/services/data/v64.0/apexguru/validate')) + .rejects + .toThrow('HTTP 401 Unauthorized at /services/data/v64.0/apexguru/validate: Invalid token'); + }); + + it('should handle network errors', async () => { + const mockFetch = jest.fn().mockRejectedValue(new Error('Network failure')); + global.fetch = mockFetch as any; + + await expect((authService as any).curlRequest('GET', '/services/data/v64.0/apexguru/validate')) + .rejects + .toThrow('Request failed for /services/data/v64.0/apexguru/validate: Network failure'); + }); + }); }); diff --git a/packages/code-analyzer-core/node_modules b/packages/code-analyzer-core/node_modules new file mode 120000 index 00000000..3e566faf --- /dev/null +++ b/packages/code-analyzer-core/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-core/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-engine-api/node_modules b/packages/code-analyzer-engine-api/node_modules new file mode 120000 index 00000000..aeabacc7 --- /dev/null +++ b/packages/code-analyzer-engine-api/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-engine-api/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-eslint-engine/node_modules b/packages/code-analyzer-eslint-engine/node_modules new file mode 120000 index 00000000..6e5ea208 --- /dev/null +++ b/packages/code-analyzer-eslint-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-eslint-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-eslint8-engine/node_modules b/packages/code-analyzer-eslint8-engine/node_modules new file mode 120000 index 00000000..247f7b2d --- /dev/null +++ b/packages/code-analyzer-eslint8-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-eslint8-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-flow-engine/node_modules b/packages/code-analyzer-flow-engine/node_modules new file mode 120000 index 00000000..0102dc28 --- /dev/null +++ b/packages/code-analyzer-flow-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-flow-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-pmd-engine/node_modules b/packages/code-analyzer-pmd-engine/node_modules new file mode 120000 index 00000000..26398ce4 --- /dev/null +++ b/packages/code-analyzer-pmd-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-pmd-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-regex-engine/node_modules b/packages/code-analyzer-regex-engine/node_modules new file mode 120000 index 00000000..5426a130 --- /dev/null +++ b/packages/code-analyzer-regex-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-regex-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-retirejs-engine/node_modules b/packages/code-analyzer-retirejs-engine/node_modules new file mode 120000 index 00000000..df85560b --- /dev/null +++ b/packages/code-analyzer-retirejs-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-retirejs-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-sfge-engine/node_modules b/packages/code-analyzer-sfge-engine/node_modules new file mode 120000 index 00000000..f96c2e36 --- /dev/null +++ b/packages/code-analyzer-sfge-engine/node_modules @@ -0,0 +1 @@ +/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-sfge-engine/node_modules \ No newline at end of file From 8cfedc5f45228ba8f6a909e5d8cb46b6ecee428b Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 12 Jun 2026 16:05:58 +0530 Subject: [PATCH 2/6] test: update validate tests to use curlRequest instead of Connection.request Update ApexGuruService.test.ts validate tests: - Mock authService.curlRequest instead of connection.request - Verify curlRequest called with correct method and path - Maintain test coverage for success, failure, and timeout scenarios - Add curlRequest mock to authService setup Make curlRequest public for testability. --- .../src/services/ApexGuruAuthService.ts | 2 +- .../src/services/ApexGuruService.ts | 6 +----- .../test/ApexGuruService.test.ts | 21 ++++++++++--------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts index ce9c50ba..349a404f 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruAuthService.ts @@ -200,7 +200,7 @@ export class ApexGuruAuthService { * @returns Promise - Parsed JSON response * @throws Error if request fails or returns non-200 status */ - private async curlRequest(method: string, path: string, body?: unknown): Promise { + async curlRequest(method: string, path: string, body?: unknown): Promise { try { const url = `${this.getInstanceUrl()}${path}`; const accessToken = this.getAccessToken(); diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts index 2667b115..ae6befc2 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts @@ -110,14 +110,10 @@ export class ApexGuruService { * Internal validate implementation (without timeout wrapper) */ private async performValidate(): Promise { - const connection: Connection = this.authService.getConnection(); const apiVersion = this.authService.getApiVersion(); const url = `/services/data/v${apiVersion}/apexguru/validate`; - const response = await connection.request({ - method: 'GET', - url - }) as { status?: string }; + const response: { status?: string } = await (this.authService as any).curlRequest('GET', url); if (response.status && response.status.toLowerCase() === ApexGuruResponseStatus.SUCCESS) { return; diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts index 0cfc0330..09d3194e 100644 --- a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts @@ -29,7 +29,8 @@ describe('ApexGuruService', () => { getAccessToken: jest.fn().mockReturnValue('test-token'), getInstanceUrl: jest.fn().mockReturnValue('https://test.salesforce.com'), getApiVersion: jest.fn().mockReturnValue('64.0'), - mintOrgJwt: jest.fn().mockResolvedValue('mock-jwt-token') + mintOrgJwt: jest.fn().mockResolvedValue('mock-jwt-token'), + curlRequest: jest.fn() } as any; jest.mocked(ApexGuruAuthService).mockImplementation(() => mockAuthService); @@ -59,20 +60,20 @@ describe('ApexGuruService', () => { describe('validate', () => { it('should succeed when validation returns success status', async () => { - (mockConnection.request as jest.Mock).mockResolvedValue({ + mockAuthService.curlRequest.mockResolvedValue({ status: ApexGuruResponseStatus.SUCCESS }); await expect(apexGuruService.validate()).resolves.toBeUndefined(); - expect(mockConnection.request).toHaveBeenCalledWith({ - method: 'GET', - url: '/services/data/v64.0/apexguru/validate' - }); + expect(mockAuthService.curlRequest).toHaveBeenCalledWith( + 'GET', + '/services/data/v64.0/apexguru/validate' + ); }); it('should succeed for uppercase SUCCESS status', async () => { - (mockConnection.request as jest.Mock).mockResolvedValue({ + mockAuthService.curlRequest.mockResolvedValue({ status: 'SUCCESS' }); @@ -80,7 +81,7 @@ describe('ApexGuruService', () => { }); it('should throw error when validation fails', async () => { - (mockConnection.request as jest.Mock).mockResolvedValue({ + mockAuthService.curlRequest.mockResolvedValue({ status: ApexGuruResponseStatus.FAILED }); @@ -89,7 +90,7 @@ describe('ApexGuruService', () => { }); it('should throw error on network failure', async () => { - (mockConnection.request as jest.Mock).mockRejectedValue(new Error('Network error')); + mockAuthService.curlRequest.mockRejectedValue(new Error('Network error')); await expect(apexGuruService.validate()) .rejects.toThrow('Network error'); @@ -98,7 +99,7 @@ describe('ApexGuruService', () => { it('should throw timeout error when validation takes too long', async () => { jest.useFakeTimers(); - (mockConnection.request as jest.Mock).mockImplementation(() => + mockAuthService.curlRequest.mockImplementation(() => new Promise(resolve => setTimeout(() => resolve({ status: ApexGuruResponseStatus.SUCCESS }), 200000)) ); From 64ed196002036e171c45921330e58ad880e10f8a Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 12 Jun 2026 16:08:46 +0530 Subject: [PATCH 3/6] test: update submitAnalysis and pollForResults tests to use curlRequest Update all remaining ApexGuruService tests to use mockAuthService.curlRequest: - analyzeApexClass tests for submit and poll operations - Progress callback test - Report parsing test - Timeout handling test - scanMetadata extraction test All 20 tests now passing with curlRequest pattern. --- .../src/services/ApexGuruService.ts | 14 +---- .../test/ApexGuruService.test.ts | 58 +++++++++---------- 2 files changed, 31 insertions(+), 41 deletions(-) diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts index ae6befc2..932d9614 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts @@ -163,7 +163,6 @@ export class ApexGuruService { * Submit Apex class for analysis */ private async submitAnalysis(classContent: string): Promise { - const connection: Connection = this.authService.getConnection(); const apiVersion = this.authService.getApiVersion(); const url = `/services/data/v${apiVersion}/apexguru/request`; @@ -171,12 +170,7 @@ export class ApexGuruService { const requestBody = { classContent: base64Content }; try { - const response: ApexGuruInitialResponse = await connection.request({ - method: 'POST', - url, - body: JSON.stringify(requestBody), - headers: { 'Content-Type': 'application/json' } - }); + const response: ApexGuruInitialResponse = await (this.authService as any).curlRequest('POST', url, requestBody); // Normalize status to lowercase if (response.status) { @@ -203,7 +197,6 @@ export class ApexGuruService { * Note: Timeout is handled by analyzeApexClass wrapper, not here */ private async pollForResults(requestId: string): Promise<{violations: ApexGuruViolation[], scanMetadata?: ApexGuruScanMetadata}> { - const connection: Connection = this.authService.getConnection(); const apiVersion = this.authService.getApiVersion(); const url = requestId === 'pending' ? `/services/data/v${apiVersion}/apexguru/request` @@ -230,10 +223,7 @@ export class ApexGuruService { this.progressCallback(asymptoticProgress); } - const response: ApexGuruQueryResponse = await connection.request({ - method: 'GET', - url - }); + const response: ApexGuruQueryResponse = await (this.authService as any).curlRequest('GET', url); // Normalize status if (response.status) { diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts index 09d3194e..a9fe5f00 100644 --- a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts @@ -129,13 +129,13 @@ describe('ApexGuruService', () => { }]; // Mock submit response - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: mockRequestId }); // Mock poll response with success - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') }); @@ -144,7 +144,7 @@ describe('ApexGuruService', () => { expect(result.violations).toEqual(mockViolations); expect(result.scanMetadata).toBeUndefined(); - expect(mockConnection.request).toHaveBeenCalledTimes(2); + expect(mockAuthService.curlRequest).toHaveBeenCalledTimes(2); }); it('should return scanMetadata when API response includes it', async () => { @@ -164,12 +164,12 @@ describe('ApexGuruService', () => { report_generated_ms: 1234567890 }; - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, report: Buffer.from(JSON.stringify(mockViolations)).toString('base64'), scanMetadata: mockScanMetadata @@ -182,34 +182,34 @@ describe('ApexGuruService', () => { }); it('should submit base64 encoded content', async () => { - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, report: Buffer.from(JSON.stringify([])).toString('base64') }); await apexGuruService.analyzeApexClass(testClassContent, testFilePath); - const submitCall = (mockConnection.request as jest.Mock).mock.calls[0][0]; - expect(submitCall.method).toBe('POST'); - expect(submitCall.url).toBe('/services/data/v64.0/apexguru/request'); + const submitCall = mockAuthService.curlRequest.mock.calls[0]; + expect(submitCall[0]).toBe('POST'); + expect(submitCall[1]).toBe('/services/data/v64.0/apexguru/request'); - const body = JSON.parse(submitCall.body); + const body = submitCall[2] as { classContent: string }; expect(body.classContent).toBe(Buffer.from(testClassContent).toString('base64')); }); it('should poll multiple times until success', async () => { - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); // First poll returns "new", second returns success - (mockConnection.request as jest.Mock) + mockAuthService.curlRequest .mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW }) .mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, @@ -218,18 +218,18 @@ describe('ApexGuruService', () => { await apexGuruService.analyzeApexClass(testClassContent, testFilePath); - expect(mockConnection.request).toHaveBeenCalledTimes(3); // 1 submit + 2 polls + expect(mockAuthService.curlRequest).toHaveBeenCalledTimes(3); // 1 submit + 2 polls }, 15000); it('should handle immediate success response', async () => { const mockViolations = [{ rule: 'Test', message: 'test', locations: [{ startLine: 1 }], primaryLocationIndex: 0, resources: [], severity: 1 }]; - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') }); @@ -240,7 +240,7 @@ describe('ApexGuruService', () => { }); it('should throw error when analysis fails', async () => { - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.FAILED, message: 'Analysis failed' }); @@ -250,12 +250,12 @@ describe('ApexGuruService', () => { }); it('should throw error on poll failure', async () => { - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.FAILED, message: 'Processing failed' }); @@ -265,12 +265,12 @@ describe('ApexGuruService', () => { }); it('should throw error on poll error status', async () => { - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.ERROR, message: 'Internal error' }); @@ -286,12 +286,12 @@ describe('ApexGuruService', () => { const progressCallback = jest.fn(); apexGuruService.setProgressCallback(progressCallback); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock) + mockAuthService.curlRequest .mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW }) .mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, @@ -324,12 +324,12 @@ describe('ApexGuruService', () => { } ]; - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') }); @@ -345,13 +345,13 @@ describe('ApexGuruService', () => { jest.useFakeTimers(); // Mock submit response - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); // Mock never-ending polling (keeps returning "processing") - (mockConnection.request as jest.Mock).mockImplementation(() => + mockAuthService.curlRequest.mockImplementation(() => new Promise(resolve => { setTimeout(() => resolve({ status: ApexGuruResponseStatus.NEW }), 100); }) @@ -390,12 +390,12 @@ describe('ApexGuruService', () => { report_generated_ms: 1234567890 }; - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.NEW, requestId: 'req-123' }); - (mockConnection.request as jest.Mock).mockResolvedValueOnce({ + mockAuthService.curlRequest.mockResolvedValueOnce({ status: ApexGuruResponseStatus.SUCCESS, report: Buffer.from(JSON.stringify(mockViolations)).toString('base64'), scanMetadata: mockScanMetadata From ffbdd8c79f43f7a4997a14206e613e7c27de130f Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 12 Jun 2026 16:10:07 +0530 Subject: [PATCH 4/6] refactor: remove unused Connection import from ApexGuruService Remove Connection import that's no longer used after replacing connection.request calls with authService.curlRequest. Connection is still used in ApexGuruAuthService for getConnection() method, but not in ApexGuruService anymore. All 62 apexguru-engine tests passing. --- .../src/services/ApexGuruService.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts index 932d9614..0a332529 100644 --- a/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts +++ b/packages/code-analyzer-apexguru-engine/src/services/ApexGuruService.ts @@ -1,6 +1,5 @@ -import { Connection } from '@salesforce/core'; import { LogLevel } from '@salesforce/code-analyzer-engine-api'; import { ApexGuruAuthService } from './ApexGuruAuthService'; import { From af722acf8922876fcb5160dc1ad3b50f3d617c67 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 12 Jun 2026 16:11:27 +0530 Subject: [PATCH 5/6] test: add integration tests for end-to-end curl flow Add comprehensive integration tests verifying: - curlRequest used for all ApexGuru API calls (validate, submit, poll) - Correct HTTP methods (GET, POST) and URLs for each endpoint - Base64 encoding of Apex class content in POST body - Full flow from validate through analyzeApexClass with polling All 64 apexguru-engine tests passing. --- .../test/ApexGuruService.test.ts | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts index a9fe5f00..45c23917 100644 --- a/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts +++ b/packages/code-analyzer-apexguru-engine/test/ApexGuruService.test.ts @@ -411,6 +411,90 @@ describe('ApexGuruService', () => { }); }); + describe('curl integration', () => { + it('should use curlRequest for all ApexGuru API calls', async () => { + const mockViolations = [{ + rule: 'TestRule', + message: 'test', + locations: [{ startLine: 1 }], + primaryLocationIndex: 0, + resources: [], + severity: 1 + }]; + + // Mock validate + mockAuthService.curlRequest.mockResolvedValueOnce({ + status: 'success' + }); + + // Mock submit + mockAuthService.curlRequest.mockResolvedValueOnce({ + status: 'new', + requestId: 'req-123' + }); + + // Mock poll + mockAuthService.curlRequest.mockResolvedValueOnce({ + status: 'success', + report: Buffer.from(JSON.stringify(mockViolations)).toString('base64') + }); + + await apexGuruService.validate(); + const result = await apexGuruService.analyzeApexClass('public class Test {}', 'Test.cls'); + + // Verify curlRequest was called for all operations (validate, submit, poll) + expect(mockAuthService.curlRequest).toHaveBeenCalledTimes(3); + + // Verify validate call + expect(mockAuthService.curlRequest).toHaveBeenNthCalledWith( + 1, + 'GET', + '/services/data/v64.0/apexguru/validate' + ); + + // Verify submit call + expect(mockAuthService.curlRequest).toHaveBeenNthCalledWith( + 2, + 'POST', + '/services/data/v64.0/apexguru/request', + expect.objectContaining({ + classContent: Buffer.from('public class Test {}').toString('base64') + }) + ); + + // Verify poll call + expect(mockAuthService.curlRequest).toHaveBeenNthCalledWith( + 3, + 'GET', + '/services/data/v64.0/apexguru/request/req-123' + ); + + // Verify result + expect(result.violations).toEqual(mockViolations); + }); + + it('should pass base64-encoded content in POST body', async () => { + const classContent = 'public class MyClass { void method() {} }'; + + mockAuthService.curlRequest.mockResolvedValueOnce({ + status: 'new', + requestId: 'req-123' + }); + + mockAuthService.curlRequest.mockResolvedValueOnce({ + status: 'success', + report: Buffer.from(JSON.stringify([])).toString('base64') + }); + + await apexGuruService.analyzeApexClass(classContent, 'MyClass.cls'); + + // Verify base64 encoding in submit call + const submitCall = mockAuthService.curlRequest.mock.calls[0]; + const requestBody = submitCall[2] as { classContent: string }; + expect(requestBody.classContent).toBe(Buffer.from(classContent, 'utf-8').toString('base64')); + }); + }); + describe('cleanup', () => { it('should not throw error', () => { expect(() => apexGuruService.cleanup()).not.toThrow(); From 46ab13d8477f7122cd96268da918ed097d2c4e48 Mon Sep 17 00:00:00 2001 From: Nikhil Mittal Date: Fri, 12 Jun 2026 16:38:35 +0530 Subject: [PATCH 6/6] chore: remove accidentally committed node_modules symlinks --- node_modules | 1 - packages/ENGINE-TEMPLATE/node_modules | 1 - packages/code-analyzer-apexguru-engine/node_modules | 1 - packages/code-analyzer-core/node_modules | 1 - packages/code-analyzer-engine-api/node_modules | 1 - packages/code-analyzer-eslint-engine/node_modules | 1 - packages/code-analyzer-eslint8-engine/node_modules | 1 - packages/code-analyzer-flow-engine/node_modules | 1 - packages/code-analyzer-pmd-engine/node_modules | 1 - packages/code-analyzer-regex-engine/node_modules | 1 - packages/code-analyzer-retirejs-engine/node_modules | 1 - packages/code-analyzer-sfge-engine/node_modules | 1 - 12 files changed, 12 deletions(-) delete mode 120000 node_modules delete mode 120000 packages/ENGINE-TEMPLATE/node_modules delete mode 120000 packages/code-analyzer-apexguru-engine/node_modules delete mode 120000 packages/code-analyzer-core/node_modules delete mode 120000 packages/code-analyzer-engine-api/node_modules delete mode 120000 packages/code-analyzer-eslint-engine/node_modules delete mode 120000 packages/code-analyzer-eslint8-engine/node_modules delete mode 120000 packages/code-analyzer-flow-engine/node_modules delete mode 120000 packages/code-analyzer-pmd-engine/node_modules delete mode 120000 packages/code-analyzer-regex-engine/node_modules delete mode 120000 packages/code-analyzer-retirejs-engine/node_modules delete mode 120000 packages/code-analyzer-sfge-engine/node_modules diff --git a/node_modules b/node_modules deleted file mode 120000 index 852e7d60..00000000 --- a/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/node_modules \ No newline at end of file diff --git a/packages/ENGINE-TEMPLATE/node_modules b/packages/ENGINE-TEMPLATE/node_modules deleted file mode 120000 index 21586c3c..00000000 --- a/packages/ENGINE-TEMPLATE/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/ENGINE-TEMPLATE/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-apexguru-engine/node_modules b/packages/code-analyzer-apexguru-engine/node_modules deleted file mode 120000 index 8b66a021..00000000 --- a/packages/code-analyzer-apexguru-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-apexguru-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-core/node_modules b/packages/code-analyzer-core/node_modules deleted file mode 120000 index 3e566faf..00000000 --- a/packages/code-analyzer-core/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-core/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-engine-api/node_modules b/packages/code-analyzer-engine-api/node_modules deleted file mode 120000 index aeabacc7..00000000 --- a/packages/code-analyzer-engine-api/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-engine-api/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-eslint-engine/node_modules b/packages/code-analyzer-eslint-engine/node_modules deleted file mode 120000 index 6e5ea208..00000000 --- a/packages/code-analyzer-eslint-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-eslint-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-eslint8-engine/node_modules b/packages/code-analyzer-eslint8-engine/node_modules deleted file mode 120000 index 247f7b2d..00000000 --- a/packages/code-analyzer-eslint8-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-eslint8-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-flow-engine/node_modules b/packages/code-analyzer-flow-engine/node_modules deleted file mode 120000 index 0102dc28..00000000 --- a/packages/code-analyzer-flow-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-flow-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-pmd-engine/node_modules b/packages/code-analyzer-pmd-engine/node_modules deleted file mode 120000 index 26398ce4..00000000 --- a/packages/code-analyzer-pmd-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-pmd-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-regex-engine/node_modules b/packages/code-analyzer-regex-engine/node_modules deleted file mode 120000 index 5426a130..00000000 --- a/packages/code-analyzer-regex-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-regex-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-retirejs-engine/node_modules b/packages/code-analyzer-retirejs-engine/node_modules deleted file mode 120000 index df85560b..00000000 --- a/packages/code-analyzer-retirejs-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-retirejs-engine/node_modules \ No newline at end of file diff --git a/packages/code-analyzer-sfge-engine/node_modules b/packages/code-analyzer-sfge-engine/node_modules deleted file mode 120000 index f96c2e36..00000000 --- a/packages/code-analyzer-sfge-engine/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/nikhil.mittal/Documents/code-analyzer/code-analyzer-core/packages/code-analyzer-sfge-engine/node_modules \ No newline at end of file