文件
OmniCard/backend/test/modules/crm/salesforce.test.ts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

142 行
4.4 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

import axios from 'axios';
import { CoolCommException } from '@cool-midway/core';
import { CrmSalesforceService } from '../../../src/modules/crm/service/salesforce';
describe('CrmSalesforceService.createLead', () => {
let service: CrmSalesforceService;
const validPayload = {
FirstName: 'Jane',
LastName: 'Doe',
Email: 'jane@acme.com',
Company: 'Acme',
Phone: '+15551234567',
Status: 'Open - Not Contacted',
};
const validInstanceUrl = 'https://myorg.my.salesforce.com';
beforeEach(() => {
service = new CrmSalesforceService();
jest.clearAllMocks();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('accessToken 为空时应抛出 CoolCommException,说明未配置 Salesforce', async () => {
const postSpy = jest.spyOn(axios, 'post');
await expect(
service.createLead('', validInstanceUrl, validPayload)
).rejects.toThrow(CoolCommException);
await expect(
service.createLead(' ', validInstanceUrl, validPayload)
).rejects.toThrow(/Salesforce/);
expect(postSpy).not.toHaveBeenCalled();
});
it('instanceUrl 为空时应抛出 CoolCommException,说明未配置 Salesforce', async () => {
const postSpy = jest.spyOn(axios, 'post');
await expect(
service.createLead('sf-token', '', validPayload)
).rejects.toThrow(CoolCommException);
await expect(
service.createLead('sf-token', ' ', validPayload)
).rejects.toThrow(/Salesforce/);
expect(postSpy).not.toHaveBeenCalled();
});
it('非法 instanceUrl(非 https 或主机名不符)应抛错且不发请求', async () => {
const postSpy = jest.spyOn(axios, 'post');
await expect(
service.createLead('sf-token', 'http://myorg.my.salesforce.com', validPayload)
).rejects.toThrow(CoolCommException);
await expect(
service.createLead('sf-token', 'https://evil.example.com', validPayload)
).rejects.toThrow(CoolCommException);
await expect(
service.createLead('sf-token', 'https://127.0.0.1', validPayload)
).rejects.toThrow(CoolCommException);
expect(postSpy).not.toHaveBeenCalled();
});
it('有效时应向 Salesforce Lead API 发起 Bearer 请求并返回 id 与原始响应', async () => {
const token = 'sf-access-token-xyz';
const raw = { id: '00Qxx000001Lead', success: true, errors: [] };
const postSpy = jest.spyOn(axios, 'post').mockResolvedValueOnce({
status: 201,
data: raw,
});
const result = await service.createLead(token, validInstanceUrl, validPayload);
expect(postSpy).toHaveBeenCalledTimes(1);
const [url, body, config] = postSpy.mock.calls[0];
expect(url).toBe(
`${validInstanceUrl}/services/data/v59.0/sobjects/Lead`
);
expect(body).toEqual(validPayload);
expect(config?.headers?.Authorization).toBe(`Bearer ${token}`);
expect(config?.timeout).toBe(8000);
expect(result.id).toBe('00Qxx000001Lead');
expect(result.raw).toEqual(raw);
});
it('force.com 主机名应被允许', async () => {
const instanceUrl = 'https://na1.salesforce.com';
const forceUrl = 'https://custom.force.com';
const token = 'sf-token';
const raw = { id: '00Qxx000002', success: true };
const postSpy = jest
.spyOn(axios, 'post')
.mockResolvedValueOnce({ status: 201, data: raw })
.mockResolvedValueOnce({ status: 201, data: raw });
await service.createLead(token, instanceUrl, validPayload);
await service.createLead(token, forceUrl, validPayload);
expect(postSpy).toHaveBeenCalledTimes(2);
expect(postSpy.mock.calls[0][0]).toBe(
`${instanceUrl}/services/data/v59.0/sobjects/Lead`
);
expect(postSpy.mock.calls[1][0]).toBe(
`${forceUrl}/services/data/v59.0/sobjects/Lead`
);
});
it('对方返回 4xx 时应抛出包含状态码与错误信息的异常,不返回 success:true', async () => {
const axiosError = Object.assign(new Error('Request failed with status code 400'), {
isAxiosError: true,
response: {
status: 400,
data: [
{
message: 'Required fields are missing: [LastName]',
errorCode: 'REQUIRED_FIELD_MISSING',
},
],
},
});
jest.spyOn(axios, 'post').mockRejectedValueOnce(axiosError);
await expect(
service.createLead('sf-valid', validInstanceUrl, validPayload)
).rejects.toThrow(/400|Required fields are missing/);
});
});