Trailhead学习:Data Integration Specialist Superbadge

Trailhead学习:Data Integration Specialist Superbadge

欲破解该Superbadge需要先破解Lightning Flow模块,相关链接如下:

https://trailhead.salesforce.com/en/content/learn/superbadges/superbadge_integration

需安装如下’Data Integration Specialist Superbadge’ package:

https://login.salesforce.com/packaging/installPackage.apexp?p0=04t41000001T3kx

Configure outbound application and integration security

题目要求如下:

Install the unmanaged package from the prework if you haven’t already. Configure a named credential and remote site according to the specifications outlined in the business requirements. Enter the billing service credentials in the custom setting.

在设置(Setup)下->安全性控制 ->远程站点设置, 远程站点名称设置为BillingService,远程站点 URL为:http://sb-integration-bs.herokuapp.com

在安全性控制中添加新的命名凭证:
* 标签:ProjectService
* 名称:ProjectService
* URL:https://sb-integration-pms.herokuapp.com/projects
* 身份类型:命名首要
* 验证协议:密码验证
* 用户名:pmsUser1
* 密码:pmsUser1
* 生成授权标题:打上勾√

添加连接的应用程序:API 名称设置为ProjectService,系统会生成一个’客户键’和’消费者秘密’。然后在如下链接测试你的链接
https://sb-integration-pms.herokuapp.com.

结果会在URL体现出来,同时,会得到一个Token,成功的URL会如下:
https://sb-integration-pms.herokuapp.com/test?success=true

在Custom Setting添加一条记录ServiceCredentials,名字是BillingServiceCredential,username是bsUser1,Password是bsPass1。

Configure inbound integration security

题目要求如下
Configure the PMS Connected App according to the specifications outlined in the business requirements. Then register the connected app with the Org Registration Heroku app and test the connection. Enter the project service security token into the custom setting.

基于上面一题得到的Token,在Custom Setting中的ServiceTokens加一条名字为ProjectServiceToken的记录

Synchronize Salesforce opportunity data with Square Peg’s PMS external system

Build a Process Builder process on the opportunity object to invoke an Apex REST callout to the external PMS as outlined in the requirements section.
本题考查的是进程生成器(Process Builder)调用Apex类。

同时,需要在业务机会(Opportunity) Type字段加一个新的选项’New Project’

Apex 类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

public class ProjectCalloutService {
//method to be invoked by ProcessBuilder apex
@InvocableMethod
public static void postOpportunityToPMS(List<Id> oppoIds){
Opportunity opp = [SELECT Id,Name,Account.Name,CloseDate,Amount FROM Opportunity WHERE Id = :oppoIds[0]];
String serviceToken = ServiceTokens__c.getValues('ProjectServiceToken').Token__c;

String jsonInput = '{\n' +
' "opportunityId" : "'+opp.Id+'",\n'+
' "opportunityName" : "'+opp.Name+'",\n'+
' "accountName" : "'+opp.Account.Name+'",\n'+
' "closeDate" : "'+String.ValueOf(opp.CloseDate).mid(0,10)+'",\n'+
' "amount" : "'+opp.Amount+'"\n}';

System.enqueueJob(new QueueablePMSCall(serviceToken, jsonInput, opp.Id));
}

class QueueablePMSCall implements System.Queueable, Database.AllowsCallouts{
private String serviceToken;
private String jsonInput;
private Id oppId;

public QueueablePMSCall(String serviceToken, String jsonInput, Id oppId){
this.serviceToken = serviceToken;
this.jsonInput = jsonInput;
this.oppId = oppId;
}

public void execute(QueueableContext qc){
postToPMS(serviceToken, jsonInput, oppId);
}
}

@Future(callout=true)
private static void postToPMS(String serviceToken, String jsonInput, Id oppoId){
HTTPRequest req = new HTTPRequest();
req.setEndPoint('callout:ProjectService');
req.setMethod('POST');
req.setHeader('token',serviceToken);
req.setHeader('Content-Type', 'application/json;charset=UTF-8');
req.setBody(jsonInput);

HTTP http = new HTTP();
HTTPResponse res = http.send(req);

Opportunity opp = new Opportunity(Id=oppoId);
if(res.getStatusCode() == 201){
opp.StageName = 'Submitted Project';
System.debug('Success: ' + res.getStatus());
}else{
opp.StageName = 'Resubmit Project';
System.debug('Failure: ' + res.getStatusCode() + ' ' + res.getStatus());
}
update opp;
}
}

之后需要新建一个进程生成器(Process Builder) ,可以命名为Update Opportunity with Opportunity object selection.

总图为:

image

选择Opportunity,中间判断条件可以设置为:Opportunity Type with stage change

image

下面是用进程生成器(Process Builder)调用Apex类

image

Test outbound Apex REST callout logic

Build tests for your Apex outbound service logic using the included stubs (ProjectCalloutServiceMock and ProjectCalloutServiceMockFailure) and callout test class (ProjectCalloutServiceTest) in the package. You must have 90% test coverage to pass this challenge and assert values to prove that your logic is working as expected.

基于上一题写一个测试类,同时要求90%代码覆盖率。
成功例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@isTest
global class ProjectCalloutServiceMock implements HttpCalloutMock{
//Implement http mock callout here
// Implement this interface method
global HTTPResponse respond(HTTPRequest request){
// Create a fake response
HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', 'application/json');
response.setStatus('OK');
response.setStatusCode(201);

return response;
}
}

失败的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@isTest
global class ProjectCalloutServiceMockFailure implements HttpCalloutMock{
//Implement http mock callout here
// Implement this interface method
global HTTPResponse respond(HTTPRequest request){
// Create a fake response
HttpResponse response = new HttpResponse();
response.setHeader('Content-Type', 'application/json');
response.setStatus('Bad Response');
response.setStatusCode(500);

return response;
}
}

最后写测试类,其中testSetupdata是新建测试数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

@isTest
private class ProjectCalloutServiceTest {
//Implement mock callout tests here
@testSetup static void testSetupdata(){
//create the opportunity record
Opportunity opp1 = new Opportunity();
opp1.Name = 'Test Opp1';
opp1.Type = 'New Project';
opp1.Amount = 100;
opp1.CloseDate = Date.today();
opp1.StageName = 'Submitted Project';
insert opp1;
//create the opportunity record
Opportunity opp2 = new Opportunity();
opp2.Name = 'Test Opp2';
opp2.Type = 'New Project';
opp2.Amount = 200;
opp2.CloseDate = Date.today();
opp2.StageName = 'Resubmit Project';
insert opp2;
//create the Custom Settings
ServiceTokens__c servToken = new ServiceTokens__c();
servToken.Name = 'ProjectServiceToken';
servToken.Token__c = 'qwertyuiopnjhgft';
insert servToken;
}

@isTest
static void testSuccessMessage(){
Opportunity opp = [Select Id, Name FROM Opportunity WHERE Name = 'Test Opp1' Limit 1];
List<Id> lstOfOppIds = new List<Id>();
lstOfOppIds.add(opp.Id);
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new ProjectCalloutServiceMock());
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
Test.startTest();
ProjectCalloutService.postOpportunityToPMS(lstOfOppIds);
Test.stopTest();
// Verify that the response received contains fake values
opp = [select StageName from Opportunity where id =: opp.Id];
System.assertEquals('Submitted Project',opp.StageName);
}

@isTest
static void testFailureMessage(){
Opportunity opp = [Select Id, Name FROM Opportunity WHERE Name = 'Test Opp2' Limit 1];
List<Id> lstOfOppIds = new List<Id>();
lstOfOppIds.add(opp.Id);
// Set mock callout class
Test.setMock(HttpCalloutMock.class, new ProjectCalloutServiceMockFailure());
// This causes a fake response to be sent
// from the class that implements HttpCalloutMock.
Test.startTest();
ProjectCalloutService.postOpportunityToPMS(lstOfOppIds);
Test.stopTest();
// Verify that the response received contains fake values
opp = [select StageName from Opportunity where id =: opp.Id];
System.assertEquals('Resubmit Project',opp.StageName);
}
}

Synchronize external PMS system project data with Salesforce

Use the requirements above to implement an Apex REST service to process related project and opportunity data that comes in from the Square Peg external application. Before checking this section, run the service method in the ProjectRESTService class to confirm that it’s working as expected.

本题考查的是如何使得外部系统PMS 的数据和Salesforce保持一致。根据题目要求,需要写一个Apex REST服务类,用于接收外部系统Post过来的数据,获得的数据可以更新或者插入到Salesforce数据库中。
写好之后的测试URL可以如下:

https://YOUR_INSTANCE.salesforce.com/services/apexrest/project

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@RestResource(urlMapping = '/project/*')
global with sharing class ProjectRESTService {
@HttpPost
global static String postProjectData(String ProjectRef, String ProjectName, String OpportunityId,
Date StartDate, Date EndDate, Double Amount, String Status){
String retMsg = 'Error';

SavePoint sp1 = Database.setSavePoint();
try{
List<Opportunity> lstOfOpps = new List<Opportunity>();

if(OpportunityId != null && OpportunityId.trim().length() > 0){
Opportunity opp = [SELECT Id, DeliveryInstallationStatus__c, Discount_Percent__c FROM Opportunity WHERE Id = :OpportunityId];
opp.DeliveryInstallationStatus__c = 'In progress';

lstOfOpps.add(opp);
}
UPDATE lstOfOpps;

List<Project__c> lstOfRrjts = new List<Project__c>();

Project__c prjt = new Project__c();
prjt.ProjectRef__c = ProjectRef;
prjt.Name = ProjectName;
prjt.Opportunity__c = OpportunityId;
prjt.Start_Date__c = StartDate;
prjt.End_Date__c = EndDate;
prjt.Billable_Amount__c = Amount;
prjt.Status__c = Status;

lstOfRrjts.add(prjt);

UPSERT lstOfRrjts;

retMsg = 'OK';
}catch(Exception ex){
Database.rollback(sp1);
retMsg = ex.getMessage();
}
return retMsg;
}
}

Test inbound Apex REST service logic

Build tests for your Apex REST service logic using the stub for the test class (ProjectRESTServiceTest) in the package. You must have 90% test coverage to pass this challenge and assert values to prove that your logic is working as expected.

写好了Apex 类,现在需要写一个测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@isTest
private class ProjectRESTServiceTest {
@testSetup
static void loadServiceData(){
Opportunity opp = new Opportunity();
opp.Name = 'Test Opportunity';
opp.DeliveryInstallationStatus__c = 'In progress';
opp.CloseDate = (Date.today()).addDays(20);
opp.StageName = 'Submitted Project';
INSERT opp;

Project__c prjt = new Project__c();
prjt.ProjectRef__c = 'ProjectRef';
prjt.Name = 'ProjectName';
prjt.Opportunity__c = opp.Id;
prjt.Start_Date__c = Date.today();
prjt.End_Date__c = (Date.today()).addDays(10);
prjt.Billable_Amount__c = 1000;
prjt.Status__c = 'Running';
INSERT prjt;
}

@isTest
static void testProjectRESTService(){
Project__c prjt = [SELECT Id, ProjectRef__c, Name, Opportunity__c, Start_Date__c, End_Date__c, Billable_Amount__c, Status__c FROM Project__c LIMIT 1];
Test.startTest();
Opportunity opp = [SELECT Id FROM Opportunity LIMIT 1];
System.assertEquals(1,[SELECT count() FROM Opportunity]);
String returnMessage = ProjectRESTService.postProjectData('ProjectRef', 'ProjectName', String.valueOf(opp.Id), Date.today(), Date.today(), 1000, 'Running');
Test.stopTest();
}
}

Synchronize Salesforce project data with Square Peg’s external billing system

Perform the necessary steps, as outlined in the requirements, to make an outbound authenticated Apex callout to Square Peg’s external billing system’s SOAP service. To pass this challenge, delete the unused Async process class that is autogenerated from the WSDL AsyncBillingServiceProxy.

下载XML:

http://sb-integration-bs.herokuapp.com/ws/invoices.wsdl

在Apex Class 里,点击’ Generate from WSDL’,并生成一个名为BillingServiceProxy 的proxy类。

相关参考链接如下:
https://help.salesforce.com/articleView?id=code_wsdl_to_package.htm&type=5

同时题目要求ProjectTrigger 调用 ‘BillingCalloutService.callBillingService()并且传入新旧记录的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class BillingCalloutService {
@future(callout = true)
public static void callBillingService(String projectRef, Decimal billingAmount){
ServiceCredentials__c srvcCrd = ServiceCredentials__c.getValues('BillingServiceCredential');

BillingServiceProxy.project projectInst = new BillingServiceProxy.project();
projectInst.username = srvcCrd.Username__c;
projectInst.password = srvcCrd.Password__c;
projectInst.billAmount = billingAmount;

BillingServiceProxy.InvoicesPortSoap11 invPortSoapInst = new BillingServiceProxy.InvoicesPortSoap11();
String response = invPortSoapInst.billProject(projectInst);

List<Project__c> lstOfProjects = new List<Project__c>();
if(response != null && response.equalsIgnoreCase('OK')){
List<Project__c> lstOfPrjts = [SELECT Status__c FROM Project__c WHERE ProjectRef__c = :projectRef];
for(Project__c prjt : lstOfPrjts){
prjt.Status__c = 'Billed';

lstOfProjects.add(prjt);
}

UPDATE lstOfProjects;
}
}
}

Trigger这样写:

1
2
3
4
5
6
7
8
9
trigger ProjectTrigger on Project__c (after update) {
if(Trigger.isAfter && Trigger.isUpdate){
for(Project__c prjt : Trigger.new){
if(prjt.Status__c != null && prjt.Status__c.equals('Billable')){
BillingCalloutService.callBillingService(prjt.ProjectRef__c, prjt.Billable_Amount__c);
}
}
}
}

Test outbound Apex SOAP callout logic

Build tests for your SOAP callout and assert proper behavior using the included stubs (BillingCalloutServiceMock and BillingCalloutServiceMockFailure) and callout test class (BillingCalloutServiceTest) in the package. You must have 90% test coverage to pass this challenge and assert values to prove that your logic is working as expected.

1
2
3
4
5
6
7
8
@isTest
global class BillingCalloutServiceMock implements WebServiceMock {
global void doInvoke(Object stub,Object request,Map<String, Object> response,String endpoint,String soapAction,String requestName,String responseNS, String responseName,String responseType){
BillingServiceProxy.billProjectResponse_element response_x = new BillingServiceProxy.billProjectResponse_element();
response_x.status = 'OK';
response.put('response_x', response_x);
}
}
1
2
3
4
5
6
7
global class BillingCalloutServiceMockFailure implements WebServiceMock {
global void doInvoke(Object stub,Object request,Map<String, Object> response,String endpoint,String soapAction, String requestName, String responseNS,String responseName,String responseType) {
BillingServiceProxy.billProjectResponse_element response_x = new BillingServiceProxy.billProjectResponse_element();
response_x.status = 'ERROR';
response.put('response_x', response_x);
}
}

测试类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@isTest
private class BillingCalloutServiceTest {
@testSetup static void loadData(){

Opportunity oppo = new Opportunity();
oppo.Name = 'TestOpp1';
oppo.CloseDate = Date.today();
oppo.StageName = 'Prospecting';
insert oppo;

Project__c proj = new Project__c();
proj.Name = 'TestProj1';
proj.Billable_Amount__c = 1000;
proj.ProjectRef__c = 'TestRef1';
proj.Status__c = 'Running';
proj.Opportunity__c = oppo.Id;
insert proj;

ServiceCredentials__c servCred = new ServiceCredentials__c();
servCred.Name = 'BillingServiceCredential';
servCred.Username__c = 'usr1';
servCred.Password__c = 'pwd1';
insert servCred;
}

@isTest static void testCalloutSuccess(){
Test.setMock(WebServiceMock.class, new BillingCalloutServiceMock());
List<Project__c> prjt = [SELECT Status__C FROM Project__c WHERE ProjectRef__c = 'TestRef1'];
System.assertEquals(1, prjt.size());
Test.startTest();
prjt[0].Status__c = 'Billable';
update prjt;
Test.stopTest();
}

@isTest static void testCalloutFailure(){
Test.setMock(WebServiceMock.class, new BillingCalloutServiceMockFailure());
List<Project__c> prjt = [SELECT Status__C FROM Project__c WHERE ProjectRef__c = 'TestRef1'];
System.assertEquals(1, prjt.size());
Test.startTest();
prjt[0].Status__c = 'Running';
update prjt;
Test.stopTest();
}
}

Synchronize external billing data with Salesforce in real time

Configure Salesforce Connect to integrate with Square Peg’s external billing system to expose invoice information as children of projects as outlined in the requirements.

根据题目要求,需要设置一个external data source的连接。路径为:setup–>external data source

然后输入如下信息:

External Data Source: BillingService
Name: BillingService
Type: Salesforce Connect OData 2.0
URL: https://sb-integration-is.herokuapp.com/odata
Identity Type: Anonymous
Authentication Protocol:No Authentication

同时需要新建一个external对象切名字为 “invoices”。

image

就这样,我们获得了Data Integration Specialist,一次性得到了6500分。

image

Reference