feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -0,0 +1,141 @@
|
||||
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/);
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户