// salesforce.client caches the OAuth token at module scope, so every test // resets the module registry for a clean cache and re-requires axios + the // module under test after the reset (same approach as // test/feedback/salesforce.service.test.ts). export {}; // isolate module scope from other script-style test files jest.mock('axios'); const freshImports = () => { const axios = require('axios'); const {salesforceApexRestPost, salesforceEnabled} = require('../../src/common/salesforce.client'); return {axios, salesforceApexRestPost, salesforceEnabled}; }; const ORIGINAL_ENV = {...process.env}; const ENABLED_ENV = { ...ORIGINAL_ENV, SALESFORCE_ENABLED: 'true', SALESFORCE_API_URL: 'https://example.my.salesforce.com', SALESFORCE_CLIENT_ID: 'client-id', SALESFORCE_CLIENT_SECRET: 'client-secret' }; beforeEach(() => { jest.resetModules(); process.env = {...ENABLED_ENV}; }); afterAll(() => { process.env = {...ORIGINAL_ENV}; }); describe('salesforceEnabled', () => { it('is true only when SALESFORCE_ENABLED === "true"', () => { process.env.SALESFORCE_ENABLED = 'true'; expect(freshImports().salesforceEnabled()).toBe(true); jest.resetModules(); process.env.SALESFORCE_ENABLED = 'false'; expect(freshImports().salesforceEnabled()).toBe(false); }); }); describe('salesforceApexRestPost', () => { it('fetches a token, posts to the given Apex REST path, and returns the response body', async () => { const {axios, salesforceApexRestPost} = freshImports(); axios.post.mockImplementation((url: string) => { if (url.endsWith('/services/oauth2/token')) return Promise.resolve({data: {access_token: 'tok-1'}}); return Promise.resolve({data: {ok: true}}); }); const result = await salesforceApexRestPost('/services/apexrest/email/send', {to: 'x@example.com'}); expect(result).toEqual({ok: true}); expect(axios.post).toHaveBeenCalledWith( 'https://example.my.salesforce.com/services/oauth2/token', expect.any(String), expect.objectContaining({headers: {'Content-Type': 'application/x-www-form-urlencoded'}}) ); expect(axios.post).toHaveBeenCalledWith( 'https://example.my.salesforce.com/services/apexrest/email/send', {to: 'x@example.com'}, expect.objectContaining({headers: {Authorization: 'Bearer tok-1'}}) ); }); it('reuses the cached token across calls instead of fetching twice', async () => { const {axios, salesforceApexRestPost} = freshImports(); axios.post.mockImplementation((url: string) => { if (url.endsWith('/services/oauth2/token')) return Promise.resolve({data: {access_token: 'tok-1'}}); return Promise.resolve({data: {}}); }); await salesforceApexRestPost('/services/apexrest/email/send', {}); await salesforceApexRestPost('/services/apexrest/email/send', {}); const tokenCalls = axios.post.mock.calls.filter(([url]: [string]) => url.endsWith('/services/oauth2/token')); expect(tokenCalls).toHaveLength(1); }); it('retries once with a fresh token on a 401, then succeeds', async () => { const {axios, salesforceApexRestPost} = freshImports(); let tokenFetches = 0; axios.post.mockImplementation((url: string) => { if (url.endsWith('/services/oauth2/token')) { tokenFetches += 1; return Promise.resolve({data: {access_token: `tok-${tokenFetches}`}}); } if (tokenFetches === 1) { const err: any = new Error('Unauthorized'); err.response = {status: 401}; return Promise.reject(err); } return Promise.resolve({data: {ok: true}}); }); const result = await salesforceApexRestPost('/services/apexrest/email/send', {}); expect(result).toEqual({ok: true}); expect(tokenFetches).toBe(2); }); it('does not retry on a non-401 error and rethrows it', async () => { const {axios, salesforceApexRestPost} = freshImports(); axios.post.mockImplementation((url: string) => { if (url.endsWith('/services/oauth2/token')) return Promise.resolve({data: {access_token: 'tok-1'}}); const err: any = new Error('Server error'); err.response = {status: 500}; return Promise.reject(err); }); await expect(salesforceApexRestPost('/services/apexrest/email/send', {})).rejects.toThrow('Server error'); const endpointCalls = axios.post.mock.calls.filter(([url]: [string]) => url.endsWith('/email/send')); expect(endpointCalls).toHaveLength(1); }); it('throws a clear error when client credentials are not configured', async () => { process.env.SALESFORCE_CLIENT_ID = ''; const {salesforceApexRestPost} = freshImports(); await expect(salesforceApexRestPost('/services/apexrest/email/send', {})).rejects.toThrow('SALESFORCE_CLIENT_ID'); }); });