salesforce lwc/APEX/SFDX CLI関連


##### 目次
- sfdxとmetadata
  - [salesforce inspectorでmetadata取得](#salesforce-inspectorでmetadata取得)
  - [salesforceライセンスとオブジェクト権限](#salesforceライセンスとオブジェクト権限)
  - [salesforceコンポーネントとmetadataの対照](#salesforceコンポーネントとmetadataの対照)
  - [metadataでプロファイルをdeploy時の注意点](#metadataでプロファイルをdeploy時の注意点)
  - [metadataで権限セットをdeploy時の注意点](#metadataで権限セットをdeploy時の注意点)
  - [metadataで「フロー」資材をdeploy / 削除](#metadataでフロー資材をdeploy--削除)
  - [外部オブジェクト項目削除](#外部オブジェクト項目削除)
  - [apex class管理](#apex-class管理)
  - [sf環境との接続エラー](#sf環境との接続エラー)
  - package.xml資材のdeployにつて
    - [gitブランチ間の差分でmetadataのpackageファイルを作成](#gitブランチ間の差分でmetadataのpackageファイルを作成)
    - [deplpoy検証](#deplpoy検証)

- LWC関連
  - [lwc基礎tipsと開発環境設定関連](#lwc基礎tipsと開発環境設定関連)
  - [lwcに選択リスト](#lwcに選択リスト)
  - [lwcページでレコード作成(apexなし)](#lwcページでレコード作成)
  - [lwcとapex使ってデータ操作](#lwcとapex使ってデータ操作)
  - [サーバエラーの処理](#サーバエラーの処理)
  - [LWCコンポーネントのテストフォルダー](#lwcコンポーネントのテストフォルダー)
  - [salesforceデータを読み取りlwcをテストする方法](#salesforceデータを読み取りlwcをテストする方法)
  - [jsコードのtroubleshooting](#jsコードのtroubleshooting)
  - [子lwcから親lwcとの連動](#子lwcから親lwcとの連動)
  - [親lwcから子lwcとの連動](#親lwcから子lwcとの連動)

- API関連
  - [REST API](#rest-api)

- apex関連
  - [http calloutコード書き方](#http-calloutコード書き方)
  - [http callout testコード書き方](#http-callout-testコード書き方)
  - [apex web サービス](#apex-web-サービス)
  - [apex class通じ、APIをアクセス](#apex-class通じapiをアクセス)
  - [apex classテストクラス作成時、参照関係の書き方](#apex-classテストクラス作成時参照関係の書き方)
  - [vscode上、apex classテストコード走る](#vscode上apex-classテストコード走る)
  - [apexでレコード大量作成](#apexでレコード大量作成)
- SFDX Bug関連
  - 2022/10時点のbug：
    sfdx deployコマンドでdeploy途中妙に組織と繋がらないエラーが出て、でもsf組織のリリース記録を観ると、deployが出来ている；ならこのエラーがsfdxのbugかも知れない [参考URL](https://github.com/forcedotcom/cli/issues/1448)

--- 
##### salesforce inspectorでmetadata取得
- [Salesforce inspector](https://chrome.google.com/webstore/detail/salesforce-inspector/aodjmnfhjibkcdimpodiifdjnnncaafh)をChromeに追加
- 目標sf組織にログイン（システム管理者権限ユーザ）、salesforce inspectorを開く、右上のこのアイコン
  ![スクリーンショット 2023-01-31 10.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/777d1ca6-9fc4-deea-15b0-6160b28861c3.png)
- 「download metadata」をクリック、次の画面でdownloadしたいコンポーネントを選び、「download metadata」をクリック、そしてdownload完了後「save download metadata」をクリック
  ![スクリーンショット 2023-01-31 11.00.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/f94fc5db-bbb8-45a2-d150-6805dbf05ef8.png)
  ![スクリーンショット 2023-01-31 11.01.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/5622dee3-f6bc-6a55-cf35-2ae863c0d7aa.png)
  ![スクリーンショット 2023-01-31 11.01.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/7b84b85c-1383-515a-c6dd-ae157958d874.png)
- download出来たzipファイルの中、package.xmlを取り出し、目標sf組織に繋ぐvscodeのsfプロジェクトフォルダに入れ、package.xml右クリックし、「Retrieve Source in Manifest from Org」をクリック、そしてpackage.xml中のコンポーネントはdownloadされる。
[DevToolsについて](https://www.sunbridge.com/blog/tips/salesforce-devtools/)

##### salesforceライセンスとオブジェクト権限
https://github.com/PluckYang/Development-Note/blob/3cb30b292f2db87e827de87741f684de4cfbdea4/salesforce/User%20License%20Comparison%20(003).pdf


##### lwc基礎tipsと開発環境設定関連
- lwcがブラウザ側のCaching機能を使わ無い場合、クイック検索 →セッションの設定 →「キャッシュ」下の「パフォーマンスを向上させるためにブラウザの安全で永続的なキャッシュを有効にする」をoffにする。
- sf環境のデバッグモードを有効したい場合、クイック検索 →デバッグモード →適用したいユーザ左にチェック入れ、「有効化」を押す。
- lwc中apex使わず直接レコード操作行うには、jsコードに`import { getRecord, getFieldValue } from 'lightning/uiRecordApi';`をimportし、`const FIELD_IN_JSCODE = 'OBJECTNAME.FIELDNAME';`でjsコード中のフィールとを定義、`const`の使用でフィールドを変更不可にする。
- lwcが埋められたレコードのrecordIDを取得には、`@api recordId;`をjsコードに埋める。
- lwc画面に、データの取得を確定後、該当lwcを表示させるには`<template if:true={JS_FIELD.data}></template>`で、JS_FIELD取得出来た場合のみ、template内部の画面を表示する。
- html側でjsからのレコードを反復処理の場合`<template for:each={js_field} for:item="property_name">`を使用、じゃ側のコードは以下の様に：
  ```javascript:js側よく使う、命令型apexでレコードlistをjsフィールド渡す方法
  loadRecords() {
		getAllRecords() //ここは大体apexメソッド使用し、レコードlist取得
			.then(result => {
				this.records = result;
			})
			.catch(error => {
				this.error = error;
			});
	}
  ```
  ワイヤードApexでjsフィールドをデコレートして、JSコードを大幅に簡略化できる。この場合、htmlに渡されたrecordsには`.data`成功結果と`.error`失敗結果両方含まれているため、`<template if:true={records}>`で画面表示コントロールしたい場合、`<template if:true={records.data}>`に置き換える。
  ```javascript:js側よく使う、ワイヤードapexでレコードlistをjsフィールド渡す方法
  @wire(getAllRecords) records;
  ```


##### lwcに選択リスト
lightning-comboboxコンポーネントで選択リスト実現
```html:html
//選択リスト表示コントローラー、まずjsコードで表示用list作成、そしてコントローラーをtrueにする
<lightning-layout-item size="6" padding="around-small"> 
        <template if:true={loaded}>
          <lightning-combobox
            name="machine_code"
            label="カメラの機器コード"
            value={value}
            placeholder="カメラの機器コードを選び下し"
            options={options}
            onchange={handleChange}
          ></lightning-combobox>
        </template>
      </lightning-layout-item>
```

```javascript:javascript
import { LightningElement, api, wire, track } from "lwc";
import getProductNoSmartVCAList from "@salesforce/apex/RMS_RegisterServiceCtrl.getProductNoSmartVCAList";
export default class RmsRegisterServiceCameraInf extends LightningElement {
  //カメラの機器コード選択リスト表示内容保存リスト
  @track machineCodeList = [];
  //カメラの機器コード選択リスト表示コントローラー
  loaded = false;
  //「カメラの機器コード」と「smartVCA対応」数値紐つくためのmap
  productNoSmartVCAMap = new Map();
  //カメラの機器コード選択リスト結果保存用テキスト
  machineCode = '';
  //選択されたカメラの機器コードに該当する「smartVCA対応」保存用数値
  smartVCA = 1;

  // ApexからList<ProductNoSmartVCA>受け取る
  @wire(getProductNoSmartVCAList) 
  wiredLabels({error, data}){
    if(data){
      for (var key in data) {
        //カメラの機器コード選択リストに内容追加
        this.machineCodeList.push({
          label: data[key].productNo, 
          value: data[key].productNo
          //value如果有重复的话会导致label没法正确选择；value如果是数字会导致选择之后没有显示被选项；；lightning-combobox label是选项上方的标题，每次选完有变化的是value；不在html加上<template if:true={loaded}>会导致html不能加载lightning-combobox的list
          //value: data[key].smartVCA.toString()
        });
        //「カメラの機器コード」と「smartVCA対応」数値を紐つくためのmapに内容追加
        this.productNoSmartVCAMap.set(
          data[key].productNo,
          data[key].smartVCA
        );
      };
      //カメラの機器コード選択リスト表示出来る様にする
      this.loaded = true;
    }
    if(error){
      this.selectOptions = undefined;
      this.error=error;
    }
  }
  
  //カメラの機器コード選択リストに表示内容を渡す
  get options() {
    return this.machineCodeList;
  }
  //カメラの機器コード選択後、選択結果と紐付いた「smartVCA対応」数値を保存
  handleChange(event){
    this.machineCode = event.target.value;
    this.smartVCA = this.productNoSmartVCAMap.get(this.machineCode);
    console.log('Value :'+ this.machineCode)
    //console.log('options.label :'+ event.target.options.find(opt => opt.value === event.detail.value).label)    
    //this.machineCode = event.target.value;
    console.log('smartVCA :'+ this.smartVCA)
  }

  //「smartVCA対応」数値が”0”（該当カメラにはAI対応機能がない）の場合、連動項目に”0”を固定
  get setDisabled() {
    //console.log("machine_code" + this.template.querySelector('[data-id="machine_code"]').options)
    return !(this.smartVCA != 0);
  }
  /* 画面入力を監視し、入力値に合わせて、disablesdを更新
  machineCode;
  machine_codeChange(event){
    this.machineCode = this.template.querySelector('[data-id="machine_code"]').value;
  }
  get setDisabled() {
    return !(this.machineCode != 'FD9166-HN');
  }
  */
```
注意点：
- htmlに渡す項目は`@track`の宣言が必要、js内部のみ使う項目にはアノテーションが不要、でも引用の時、`this. `の宣言が必要
- htmlで選択listを実現したいとき、listを作成し、表示用の”label”と値”value”を設置し、このlistをhtmlに渡す。ただし、valueは重複したり、数字になった利すると、html側”label”の表示が消えたら、”value”との組み合わせがズレたりするので、そこが注意必要。
- html側にjsの項目を渡すとき、js getメソッドを用意し、returnで項目を渡す。html側では`{項目}`パラメータで項目を引き取る。選択リストの場合、html側はoptionsでリストを取得、そしてvalueで選択された値を保存。
- html項目が変わったとき、jsにそれを合わせて処理を追加の場合、html側目標項目にonchangeを追加し、js側で`onchange項目(event)`メソッドを作成
- ページを開く時、html側でjsの処理が終わった後、その結果を表示したい場合、html項目に`<template if:true={loaded}>`を追加、そしてjsで先ずloadedをfalseにし、該当処理が終わった後、loadedをtrueに変える

```apex:apex
public class ProductNoSmartVCA {
    @AuraEnabled
    public String productNo { get; set; }
    @AuraEnabled
    public Decimal smartVCA { get; set; }
    public ProductNoSmartVCA() {
    }
  }
  /**
   * デバイスマスタID（device_mst_id）で
   * 機器マスタ（DEVICE_MST）型番（product_no）と
   * Sightsカメラ機能マスタ（SIGHTS_CAMERA_FUNCTION_MST）smartVCA対応（smart_vca）
   * を紐つく。
   *
   * @param null 
   * @return List<ProductNoSmartVCA>
   */
  @AuraEnabled(cacheable=true)
  public static List<ProductNoSmartVCA> getProductNoSmartVCAList() {    
    //List<Map<Integer,Integer>> productNoSmartVCAList = new List<Map<Integer, Integer>>();
    //Map<String, Integer> productNoSmartVCAMap = new Map<String, Integer>();
    List<ProductNoSmartVCA> productNoSmartVCAList = new List<ProductNoSmartVCA>();
    //ProductNoSmartVCA productNoSmartVCA = new ProductNoSmartVCA();

    List<DEVICE_MST__x> deviceMSTList = [
      SELECT
        device_mst_id__c, 
        product_no__c
      FROM DEVICE_MST__x
      order by device_mst_id__c
    ];
    List<SIGHTS_CAMERA_FUNCTION_MST__x> sightsCameraFunctionMSTList = [
      SELECT
        device_mst_id__c, 
        smart_vca__c
      FROM SIGHTS_CAMERA_FUNCTION_MST__x
      order by device_mst_id__c
    ];

    for(Integer i = 0; i < deviceMSTList.size(); i++) {
      ProductNoSmartVCA productNoSmartVCA = new ProductNoSmartVCA();
      productNoSmartVCA.productNo = deviceMSTList[i].product_no__c;
      productNoSmartVCA.smartVCA = sightsCameraFunctionMSTList[i].smart_vca__c;
      productNoSmartVCAList.add(productNoSmartVCA);
      //productNoSmartVCAList.add(new Map<Integer, Integer> {'label'=>deviceMSTList[i].product_no__c, 'value'=>sightsCameraFunctionMSTList[i].smart_vca__c});
    }
    return productNoSmartVCAList;
  }

  /**
   * デバイスマスタ（DEVICE_MST）テーブルのレコードlistを取得
   *
   * @param null 
   * @return List<DEVICE_MST__x> 
   */
  /*
  @AuraEnabled(cacheable=true)
  public static List<DEVICE_MST__x> getDeviceMSTList() {
    return [
      SELECT
        device_mst_id__c, 
        product_no__c //, 
        //AI_Flag__c
      FROM DEVICE_MST__x
      order by product_no__c
    ];
  }
  */
  
  /**
   * 申込情報登録時、カメラ情報「カメラの機器コード」項目で「デバイスマスタ（DEVICE_MST）テーブル」を検索、「AI設定の有/無フラグ」項目を返す
   *
   * @param string 画面で入力されたカメラの機器コード
   * @return int AI設定の有/無フラグ
   */
  /*  
  public static Integer getCameraAIConfigFlag(String machineCode) {
    System.debug('★★★★★ getCameraAIConfigFlag ★★★★★');
    System.debug('★' + 'カメラの機器コード : ' + machineCode);
    
    List<DEVICE_MST__x> deviceMSTList = [
        SELECT
          device_mst_id__c
          , AI_Flag__c
        FROM
          DEVICE_MST__x
        WHERE
          product_no__c = :machineCode
      ];
    System.debug('★★★★★ getCameraAIConfigFlag ★★★★★');
    System.debug('★' + '機器マスタList.size() : ' + deviceMSTList.size());
    if (deviceMSTList.size() == 1) {
      return deviceMSTList[0].AI_Flag__c;
    } else if (eviceMSTList.size() < 1) {
      //該当カメラの機器コードは未登録
      return 1;
    } else if (eviceMSTList.size() > 1) {
      //該当カメラの機器コードは重複登録
      return 1;
    }
  }
  */
```
注意点：
- apexのsoqlでは普通のSQLみたいのinner join機能がないため、一つのkey　columnで二つのオブジェクトtableを結合できない。
- ループ内soqlの使用を避けるべき
- そのため、オブジェクトlistをそれぞれ単独で取得、この時もし、key　columnが一意の場合、order by 「key　column」で二つのオブジェクトリスト順番を一致化出来る。
- jsコードに検索結果を返す時、カスタムオブジェクトのlistを返した方が操作し易い

##### lwcページでレコード作成
apexコードは使わず、直接レコードの作成や変更できるが、ロジックの整理が困難なので、lwcを純粋のページにし、ダータベースの操作やロジックをapexで行った方がいい
account（取引先）レコード作成を例に、
```html:accountCreator.html
<template>
    <lightning-card>
        <lightning-record-form
            object-api-name={objectApiName}
            fields={fields}
            onsuccess={handleSuccess}>
        </lightning-record-form>
    </lightning-card>
</template>
```
画面イメージ
![スクリーンショット 2023-01-19 11.36.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/c635351b-9084-188b-add4-4db1a8aba7c3.png)

```javascript:accountCreator.js
import { LightningElement } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
export default class AccountCreator extends LightningElement {
    objectApiName = ACCOUNT_OBJECT;
    fields = [NAME_FIELD, REVENUE_FIELD, INDUSTRY_FIELD];
    handleSuccess(event) {
        const toastEvent = new ShowToastEvent({
            title: "Account created",
            message: "Record ID: " + event.detail.id,
            variant: "success"
        });
        this.dispatchEvent(toastEvent);
    }
}
```
`onsuccess={handleSuccess}`はレコード作成が成功の時、図の様なポップアップが表示される。表示内容詳細は`handleSuccess(event)`で設定
![スクリーンショット 2023-01-19 11.36.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/5363f09f-4e1a-088d-d3f6-d15491ff173e.png)

```xml:accountCraetor.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
    </targets>
</LightningComponentBundle>
```
今回はapp内でlwcページを表示する為、xmlファイルに`<target>lightning__AppPage</target>`を追加。lwcページ表示出来る様に、該当lwcをオブジェクトの編集ページ、またAPPページに入れる必要がある。APPページの新規方法は：クイック検索「Lightning アプリケーションビルダー」→ 「新規」→ 「アプリケーションページ」。このAPPページはsfの「アプリケーションランチャー」で検索出来る。

##### lwcとapex使ってデータ操作
apexメソッドに`@AuraEnabled`をつけてLWCにも使用出来る様にする。
- そのうち、`(cacheable=true)`をつけると、メソッドがキャッシュ可能なる。
- この場合、データ操作言語 (DML) による操作は許可されません。そしてキャッシュが更新されるまで新しく追加または変更されたレコードのバージョンが返されません、手動更新が必要。
- ただ、jsファイルで、命令的なApexコールすると、手動更新がでいない。命令的なApexコール例：
```apex:CallApexImperativeのgetContactsBornAfterメソッドをコール
import { LightningElement, api, wire } from 'lwc';
import getContactsBornAfter from '@salesforce/apex/ContactController.getContactsBornAfter';
export default class CallApexImperative extends LightningElement {
    @api minBirthDate;
    handleButtonClick() {
        getContactsBornAfter({ //imperative Apex call
            birthDate: this.minBirthDate
        })
            .then(contacts => {
                //code to execute if related contacts are returned successfully
            })
            .catch(error => {
                //code to execute if related contacts are not returned successfully
            });
    }
}
```
命令的なApexコールする以外、`@wire`を使用したApexコールの場合、以下の例で、account（取引先）のName、AnnualRevenue、Industryを取得し、lwcページで表示する。
```apex:account（取引先）のName、AnnualRevenue、Industryを取得apex class
public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> getAccounts() {
        return [
            SELECT Name, AnnualRevenue, Industry
            FROM Account
            WITH SECURITY_ENFORCED
            ORDER BY Name
        ];
    }
}
```
```javascript:apexメソッドで取得したデータ表示用のlist formを構成
import { LightningElement, wire } from 'lwc';
import NAME_FIELD from '@salesforce/schema/Account.Name';
import REVENUE_FIELD from '@salesforce/schema/Account.AnnualRevenue';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';
const COLUMNS = [
    { label: 'Account Name', fieldName: NAME_FIELD.fieldApiName, type: 'text' },
    { label: 'Annual Revenue', fieldName: REVENUE_FIELD.fieldApiName, type: 'currency' },
    { label: 'Industry', fieldName: INDUSTRY_FIELD.fieldApiName, type: 'text' }
];
export default class AccountList extends LightningElement {
    columns = COLUMNS;
    // 結果を accounts プロパティに保存します。操作が成功すると、レコードは accounts.data でアクセス可能になります。失敗するとエラーが account.error に表示されます。
    // 命令的なApexコールする場合、「.catch(error =>」を使う必要がある、@wireデコレートの場合、errorプロパティ属性でアクセスできる
    @wire(getAccounts)
    accounts;
}
```
```html:accountList.html 
<template>
    <lightning-card>
        <template if:true={accounts.data}>
            <lightning-datatable
                key-field="Id"
                data={accounts.data}
                columns={columns}
            >
           </lightning-datatable>
        </template>
    </lightning-card>
</template>
```
コンポーネントをアプリケーションページで使用できるように
```xml:accountList.js-meta.xml 
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
    </targets>
</LightningComponentBundle>
```
参考： [Apex を使用したデータの操作](https://trailhead.salesforce.com/ja/content/learn/modules/lightning-web-components-and-salesforce-data/use-apex-to-work-with-data?trail_id=build-lightning-web-components&trailmix_creator_id=saif-malik&trailmix_slug=lightning-web-components)

##### サーバエラーの処理
- `@wire`を使用してプロパティをデコレートしている場合、エラーは error プロパティ属性でアクセスできる、エラー情報の取得方法二つある、
  - `@wire`デコレートされたメソッド結果取得後、errorsのget関数でエラー情報取得
  ```javascript:wireApexProperty.js
  import { LightningElement, api, wire } from 'lwc';
  // reduceErrors ヘルパー関数を ldsUtils モジュールからインポート
  import { reduceErrors } from 'c/ldsUtils';
  import getContactsBornAfter from '@salesforce/apex/AccountController.getContactsBornAfter';
  export default class WireApexProperty extends LightningElement {
      @api minBirthDate;
      @wire(getContactsBornAfter, { birthDate: '$minBirthDate' })
      contacts;
      // getter で reduceErrors ヘルパー関数を使用して this.contacts.error の形式を設定し、この関数はエラーオブジェクトを受け取り、発生したすべてのエラーメッセージの配列を返す
      get errors() {
          return (this.contacts.error) ?
              reduceErrors(this.contacts.error) : [];
      }
  }
  ```
  - `@wire`デコレートされたメソッドパラメーターとしてエラー (エラーがあった場合) を含むオブジェクトを受け取る
  ```javascript:wireApexFunction.js
  import { LightningElement, api, wire } from 'lwc';
  // reduceErrors ヘルパー関数を ldsUtils モジュールからインポート
  import { reduceErrors } from 'c/ldsUtils';
  import getContactsBornAfter from '@salesforce/apex/AccountController.getContactsBornAfter';
  export default class WireApexFunction extends LightningElement {
      @api minBirthDate;
      // errorsという名前のプロパティを定義
      errors;
      //  結び付けられた関数がエラーを受け取るたびに、reduceErrorsヘルパー関数を使用してエラーの形式を設定し、この関数は発生したすべてのエラーの配列を返す
      @wire(getContactsBornAfter, { birthDate: '$minBirthDate' })
      wiredContacts({data, error}) {
          if (error)
              this.errors = reduceErrors(error);
      }
  }
  ```
- `@wire`を使用してプロパティをデコレートし無い、命令的に関数をコールする場合のエラー処理
```javascript:callApexImperative.js
  import { LightningElement, api, wire } from 'lwc';
  import { reduceErrors } from 'c/ldsUtils';
  import getContactsBornAfter from '@salesforce/apex/AccountController.getContactsBornAfter';
  export default class CallApexImperative extends LightningElement {
      @api minBirthDate;
      //errors という名前のプロパティを定義
      errors;
      //getContactsBornAfter関数を命令的に呼び出し、関数はpromiseを返す、promiseが解決された場合、contactsを処理；promiseが却下された場合、reduceErrorsヘルパー関数を使用して受け取ったエラーの形式を設定し、errorsプロパティに保存
      handleButtonClick() {
          getContactsBornAfter({
              birthDate: this.minBirthDate
          })
              .then(contacts => {
                  // code to execute if the promise is resolved
              })
              .catch(error => {
                  this.errors = reduceErrors(error); // code to execute if the promise is rejected
              });
      }
  }

```
- その中、[ldsUtilsのreduceErrorsコンポーネント](https://github.com/trailheadapps/lwc-recipes/tree/main/force-app/main/default/lwc/ldsUtils)はヘルパー関数、lwcとして組織にimportできる
- htmlでerrorsの配列を表示するコード、エラーメッセージの配列として表示
```html:accountList.html <lightning-card>にエラー情報表示用templateを追加
<template if:true={errors}>
    <p>{errors}</p>
</template>
```
![スクリーンショット 2023-01-24 12.11.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/2b54e882-71c1-e897-83d1-1c74fe868045.png)

apexメソッドに、以下のコード入れは、強制的にエラーを作成できる
```apex:メソッドの本文をコメントにし、コードをその場所に追加して強制的にエラーを発生
throw new AuraHandledException('Forced error');
```

##### LWCコンポーネントのテストフォルダー
LWCを作成の時`__tests__`と言うテストフォルダーが自動生成される、この中にはjsコード為のJestコードを入れる事ができるが、Jestテストではsf組織と接続出来無いため、テスト出来るのはjsのロジックだけです。
![スクリーンショット 2023-01-24 14.48.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/f8bddf38-8c69-1924-4282-c1facc00ab2c.png)
このテストフォルダーを無理矢理にsf組織にdeployすると、エラーが発生になる、そのため、ローカル環境い`.forceignore`ファイルの下に、`**/__tests__/**`を入れる事で、lwcコンポーネントをsf環境へdeployの時、フォルダー内ののテストフォルダーを無視出来る。
参考記事： [Jest テストの記述](https://trailhead.salesforce.com/ja/content/learn/modules/test-lightning-web-components/write-a-jest-test?trail_id=build-lightning-web-components&trailmix_creator_id=saif-malik&trailmix_slug=lightning-web-components)

##### salesforceデータを読み取りlwcをテストする方法
- 汎用ワイヤーアダプター
  詳細は下の 参考記事 を参照
- Lightning データサービス (LDS) ワイヤーアダプター。カスタムオブジェクトや標準オブジェクトにすばやくアクセスできる。`__test__`フォルダ下でテスト用レコードjsonファイルを用意し、sf環境データとして、lwc資材をテスト出来る。詳細は 参考記事 を参照。
テストの時注意必要のは、もしテストコードでjsonレコードファイルを読み込み時、`require('./data/getRecord.json');`を使用の場合、lwcテスト画面の①の部分で虫マークをクリック必要がある。もし②の部分の虫マークでテスト始めたら、jsonファイルが見つから無いエラーが出る。
![スクリーンショット 2023-01-24 17.23.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/4c8cb851-39dc-ab9e-9d31-be2d77e2a01b.png)
- Apex ワイヤーアダプター
上の「Lightning データサービス (LDS) ワイヤーアダプター」テストに似ているが、apex class使用されたlwcをテストする方法。
参考記事： [ワイヤサービスの Jest テストの記述](https://trailhead.salesforce.com/ja/content/learn/modules/test-lightning-web-components/write-a-jest-test-for-wire-service?trail_id=build-lightning-web-components&trailmix_creator_id=saif-malik&trailmix_slug=lightning-web-components)

##### jsコードのtroubleshooting
- chrome上devtools開き、「sources」タブに入り、lwc資材のjsコードなら`lighting/n /modules/c`の下にある、もしコードは図の様に１行だけの場合、左下の`{}`をクリックし、formattedのコードが表示される。
![スクリーンショット 2023-01-25 10.41.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/a2544c7f-931d-d753-2080-9f353f9025d0.png)

- lwcのframeworkコードをdevtoolsから無視させるため、「ignore　list」に`/aura_prod.*.js$`と`/components/.*.js$`を追加する。
![スクリーンショット 2023-01-25 10.56.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/ef668268-56d7-8d50-837d-3edff0b001f1.png)

- devtools使って、コードにbreakpoint入れる操作は[Use Breakpoints](https://trailhead.salesforce.com/ja/content/learn/modules/lwc-troubleshooting/use-breakpoints?trail_id=build-lightning-web-components&trailmix_creator_id=saif-malik&trailmix_slug=lightning-web-components)を参考


##### 子lwcから親lwcとの連動
- 子lwcのボタンクリックで親lwc値をコントロール、コード例の中、「count」値のIncrement(+)とDecrement(-)はこれで操作する。
  - 先ず子lwcを親lwcに含まれる、`<c-子lwc名>`下の「numerator.html（親）」を参照。
  - そして子jsコードの中に、子画面ボタンと紐付けたメソッドに中に`this.dispatchEvent(new CustomEvent('イベント名'));`で親側で受けたイベントを定義、そして親側の`<c-子lwc名>`の中、`onイベント名={親ベント名}`で親イベントと紐つく。
下の「numerator.html（親）」と「controls.html（子）」「controls.js（子）」を参照

- 子lwcのイベント中のデータを親lwcに渡す、コード例の中、「count」値のMultiply(x2, x3)はこれで操作する。
  - この場合先ず子lwcの中、イベント中のデータを定義、`<lightning-button>`の中`data-factor="数値"`を定義、`event.target.dataset.factor`でfactorを取得し、CustomEventのdetailに渡す`this.dispatchEvent(new CustomEvent('multiply', { detail: factor }));`
コード例の「controls.html（子）」「controls.js（子）」を参照
  - そして親側で同じく`onmultiply={handleMultiply}`でイベント受け取り、そすて`handleMultiply(event) { const factor = event.detail;`で`event.detail`を受け取る。ここで注意必要のは、上のボタンクリックの例では、単純に親lwcイベント呼ぶ為、親jsコードに`event`を渡す必要が無いが、データを渡す場合`handleMultiply(event)`の様に、`event`を渡す必要がある
コード例の「numerator.html（親）」「numerator.js（親）」を参照

![スクリーンショット 2023-01-27 11.37.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/789c946f-3a69-9155-1876-48d1440050b5.png)

```html:numerator.html（親）
<template>
    <lightning-card title="Numerator" icon-name="action:manage_perm_sets">    
        <p class="slds-text-align_center slds-var-m-vertical_medium">
          Count: <lightning-formatted-number value={counter}></lightning-formatted-number>
        </p>
        <!-- ここの<c-controls>は”controls”と言うlwcを子として入れる 
        onadd={handleIncrement}は Count のプラス
        onsubtract={handleDecrement}は Count のマイナス
        onmultiply={handleMultiply}>は Count の掛け算
        -->
        <c-controls
            class="slds-show slds-is-relative"
            onadd={handleIncrement}
            onsubtract={handleDecrement}
            onmultiply={handleMultiply}>
        </c-controls>
    </lightning-card>
</template>
```
```javascript:numerator.js（親）
import { LightningElement } from 'lwc';

export default class Numerator extends LightningElement {
    counter = 0;
    handleIncrement() {
      this.counter++;
    }
    handleDecrement() {
      this.counter--;
    }
    handleMultiply(event) {
        const factor = event.detail;
        this.counter *= factor;
    }
}
```
```xml:numerator.js-meta.xml（親）
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
      <target>lightning__AppPage</target>
    </targets>
</LightningComponentBundle>
```
```html:controls.html（子）
<template>
    <lightning-card title="Controls" icon-name="action:upload">
        <lightning-layout>
          <lightning-layout-item flexibility="auto" padding="around-small">
            <lightning-button
              label="Subtract"
              icon-name="utility:dash"
              onclick={handleSubtract}>
            </lightning-button>
          </lightning-layout-item>
          <lightning-layout-item flexibility="auto" padding="around-small">
            <lightning-button
              label="2"
              data-factor="2"
              icon-name="utility:close"
              onclick={handleMultiply}>
            </lightning-button>
            <lightning-button
              label="3"
              data-factor="3"
              icon-name="utility:close"
              onclick={handleMultiply}>
            </lightning-button>
          </lightning-layout-item>
          <lightning-layout-item flexibility="auto" padding="around-small">
            <lightning-button
              label="Add"
              icon-name="utility:add"
              onclick={handleAdd}
              icon-position="right">
            </lightning-button>
          </lightning-layout-item>
        </lightning-layout>
      </lightning-card>
</template>
```
```javascript:controls.js（子）
import { LightningElement } from 'lwc';

export default class Controls extends LightningElement {
    handleAdd() {
        this.dispatchEvent(new CustomEvent('add'));
      }
    handleSubtract() {
        this.dispatchEvent(new CustomEvent('subtract'));
      }
    handleMultiply(event) {
        const factor = event.target.dataset.factor;
        this.dispatchEvent(new CustomEvent('multiply', {
          detail: factor
        }));
      }
}
```
- Bubbling機能で、子イベントを親に渡す、「x0~6」ボタンをクリックの時、botton→ controls→　numeratorで「count」をコントロール。
![スクリーンショット 2023-01-27 18.57.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/5b4ecf16-b63f-99d2-4c20-4c48dfa963a5.png)

```html:「botton」lwcを含めるcontrols.html
<!-- 「x2」[x3]ボタン作成コードを以下のと入れ替え、「x0~6」のボタンを自動生成、ボタンは「botton」lwcで定義 -->
<lightning-layout-item flexibility="auto" padding="around-small" onbuttonclick={handleMultiply}>
  <template for:each={factors} for:item="factor">
    <c-button
      key={factor}
      label={factor}
      data-factor={factor}
      icon="utility:close">
    </c-button>
  </template>
</lightning-layout-item>
```
```javascript:controls.jsに以下のpropertyを追加
// 「x0~6」のボタンを自動生成
factors = [0,2,3,4,5,6];  
```
```html:button.html ボタン画面表示を定義
<template>
    <lightning-button
    label={label}
    data-factor={label}
    icon-name={icon}
    onclick={handleButton}>
  </lightning-button>
</template>
```
```javascript:button.js
import { LightningElement, api } from 'lwc';

export default class Button extends LightningElement {
    @api label;
    @api icon;
    handleButton() {
      this.dispatchEvent(new CustomEvent('buttonclick',{
        // ここの定義は、botton　lwcがクリックされた時、controls.htmlの「onbuttonclick={handleMultiply}」を呼ぶ
        bubbles: true
      }));
    }
}
```
- [参考URL](https://trailhead.salesforce.com/ja/content/learn/projects/communicate-between-lightning-web-components/communicate-from-child-to-parent?trail_id=build-lightning-web-components&trailmix_creator_id=saif-malik&trailmix_slug=lightning-web-components)

##### 親lwcから子lwcとの連動
- publicのプロパティの更新、`@api`デコレーターを使って、lwcコンポーネントのプロパティを暴露（public）させる、そして親から子のpublicプロパティを更新
![スクリーンショット 2023-01-30 11.33.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/87c0cfe4-00b7-4a28-5503-8c0d82e0a409.png)

```javascript:numerator.js（augmentorの子）
// 先ず本来のnumerator.jsのcounterに @api デコレーターをつける、以下の様に
  @api counter = 0;
```
```html:augmentor.html（numeratorの親）
<template>
    <lightning-card title="Augmentor" icon-name="action:download">
        <lightning-layout>
          <lightning-layout-item flexibility="auto" padding="around-small">
            <lightning-input
              label="Set Starting Counter"
              type="number"
              min="0"
              max="1000000"
              value={startCounter}
              onchange={handleStartChange}>
            </lightning-input>
          </lightning-layout-item>
        </lightning-layout>
        <!-- ここでnumeratorを子コンポーネントとして入れる、
             そしてnumerator.jsのcounterプロパティが @api がある為、startCounterの値を渡せる -->
        <c-numerator
          class="slds-show slds-is-relative"
          counter={startCounter}>
        </c-numerator>
      </lightning-card>
</template>
```
```javascript:augmentor.js（numeratorの親）
import { LightningElement } from 'lwc';

export default class Augmentor extends LightningElement {
    startCounter = 0;
    handleStartChange(event) {
      // ここのparseIntは入力string値を10ベースのintに変換。ここでstring入れる時、keyboard入力は「英字」に必要がある、「日本語」の場合表示され無い
      this.startCounter = parseInt(event.target.value);
    }
}
```
```xml:augmentor.xml（numeratorの親）
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
      <target>lightning__AppPage</target>
    </targets>
</LightningComponentBundle>
```
- 親コンポーネントから子コンポーネントのpublicメソッドを呼ぶ、`@api`をメソッドにつけると、このメソッドもpublicになり、親から呼ぶ事ができる。
![スクリーンショット 2023-01-30 11.51.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/ae6da8b3-d692-eebc-2252-752aca950cac.png)
```javascript:numerator.js（augmentorの子）
    // 以下メソッド追加、numeratorのcounterプロパティを直接1000000になる
    @api
    maximizeCounter() {
      this.counter += 1000000;
    }
```
```html:augmentor.html（numeratorの親）
<!-- このボタンをlightning-inputの後ろに入れ -->
        <lightning-button 
          class="slds-var-p-vertical_xx-small"
          label="Add 1m To Counter"
          onclick={handleMaximizeCounter}>
        </lightning-button>
```
```javascript:augmentor.js（numeratorの親）
  // 以下のメソッドを追加、template.querySelectorでnumeratorコンポーネントを取得し、そのmaximizeCounter()を呼ぶ
  handleMaximizeCounter() {
    this.template.querySelector('c-numerator').maximizeCounter();
  }
```
- publicのgetterとsetterで子のprivateプロパティを制御
```html:numerator.html
      <!-- 以下内容を<P>countの前に追加 -->
      <p class="slds-text-align_center slds-var-m-vertical_medium">
        Prior Count: <lightning-formatted-number value={priorCount}></lightning-formatted-number>
      </p>
```
```javascript:numerator.js
  // @api counter = 0;  counterをコメントアウト

  // 以下内容を追加
  // 現在の数値表す_currentCountと前一個の数値表示用priorCountを用意、そして本来public　counterプロパティに対する取得、と変更をpublicメソッドに変える、jsの中にparameterとmethod調用方法がほぼ同じ
  _currentCount = 0;
  priorCount = 0;
  @api
  get counter() {
    return this._currentCount;
  }
  set counter(value) {
    this.priorCount = this._currentCount;
    this._currentCount = value;
  }
```
- [参考URL](https://trailhead.salesforce.com/ja/content/learn/projects/communicate-between-lightning-web-components/communicate-from-parent-to-child?trail_id=build-lightning-web-components&trailmix_creator_id=saif-malik&trailmix_slug=lightning-web-components)


##### REST API
参考資料：[REST API を使用する](https://trailhead.salesforce.com/ja/content/learn/modules/api_basics/api_basics_rest)

##### http calloutコード書き方
- 先ずアクセスしたいエンドポイントをsf環境に追加のは必要、「設定」→「リモートサイトの設定」→「新規」→end point情報追加
![スクリーンショット 2023-01-12 16.47.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/1124a972-483f-9f47-da67-85d8c28ab495.png)

- apexコードでそのend pointにrequest投げる。
コードtestの時は「開発者コンソール」「Debug (デバッグ)」「Open Execute Anonymous Window (実行匿名ウィンドウを開く)」でテスト用ウインドを開き。「open log」にチェック入れexecuteすると、直接実行logが表示される
![スクリーンショット 2023-01-12 16.54.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/0a2c7e44-95d1-3bd1-3f64-d97a63d6059a.png)
そして「debug only」にチェック入れると、”System.debug()”の内容だけが表示される
![スクリーンショット 2023-01-12 17.05.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/055e0223-73b2-a2c5-49da-ff93d8aa281e.png)

```apex:簡単なapex http get calloutコード
Http http = new Http();
HttpRequest request = new HttpRequest();
//end point設置
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
//requestメソッド設置
request.setMethod('GET');
HttpResponse response = http.send(request);
// If the request is successful, parse the JSON response.
// 返されるresponseは {"animals":["majestic badger","fluffy bunny","scary bear","chicken"]} の様なjsonコード
if(response.getStatusCode() == 200) {
    // Deserialize the JSON string into collections of primitive data types.
    Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
    // Cast the values in the 'animals' key as a list
    List<Object> animals = (List<Object>) results.get('animals');
    //”System.debug()”を使ってlogに出力文字は”|DEBUG|”が付いている
    System.debug('Received the following animals:');
    for(Object animal: animals) {
        System.debug(animal);
    }
}
```
- http　postメソッドでrequest投げる時、投げるjsonコードの”Content-Type”の入力も必要
```apex:簡単なapex http post calloutコード
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
request.setMethod('POST');
request.setHeader('Content-Type', 'application/json;charset=UTF-8');
// Set the body as a JSON object
request.setBody('{"name":"mighty moose"}');
HttpResponse response = http.send(request);
// Parse the JSON response
if(response.getStatusCode() != 201) {
    System.debug('The status code returned was not expected: ' + response.getStatusCode() + ' ' + response.getStatus());
} else {
    System.debug(response.getBody());
}
```
- jsonをオブジェクトへの解析は [JSON2Apex](https://json2apex.herokuapp.com/?_ga=2.132815817.677949313.1673507252-454646666.1640672360) で可能

##### http callout testコード書き方
apex testコードでは本番のend pointへのアクセス出来ないが、予想の正しいend pointからのresponseを作成し、疑似コールアウトをcalloutに返す。この場合、apex test class`Test.setMock`メソッドを使う
- classでhttp calloutコードの書き方
```apex:classでhttp calloutコードの書き方
public class AnimalsCallouts {
    public static HttpResponse makeGetCallout() {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
        request.setMethod('GET');
        HttpResponse response = http.send(request);
        // If the request is successful, parse the JSON response.
        if(response.getStatusCode() == 200) {
            // Deserializes the JSON string into collections of primitive data types.
            Map<String, Object> results = (Map<String, Object>) JSON.deserializeUntyped(response.getBody());
            // Cast the values in the 'animals' key as a list
            List<Object> animals = (List<Object>) results.get('animals');
            System.debug('Received the following animals:');
            for(Object animal: animals) {
                System.debug(animal);
            }
        }
        return response;
    }
    public static HttpResponse makePostCallout() {
        Http http = new Http();
        HttpRequest request = new HttpRequest();
        request.setEndpoint('https://th-apex-http-callout.herokuapp.com/animals');
        request.setMethod('POST');
        request.setHeader('Content-Type', 'application/json;charset=UTF-8');
        request.setBody('{"name":"mighty moose"}');
        HttpResponse response = http.send(request);
        // Parse the JSON response
        if(response.getStatusCode() != 201) {
            System.debug('The status code returned was not expected: ' +
                response.getStatusCode() + ' ' + response.getStatus());
        } else {
            System.debug(response.getBody());
        }
        return response;
    }        
}
```
- 疑似コールアウトは二つの方法で作成できる、一つは静的リソース、もう一つは疑似インターフェース
  - 静的リソースの場合、まずテスト用の静的リソース作成、「開発者コンソール」→[File (ファイル)」→「New (新規)」→「Static Resource (静的リソース)」、「MIME タイプ」に”text/plain”を選択。入れるresponse　bodyは１行に収まるべき。
  - 疑似インターフェースの場合、まず上静的リソースの代わり、responseを返すapex　classの作成が必要（今回は AnimalsHttpCalloutMock を作成）、そして`Test.setMock(HttpCalloutMock.class, mock);`を`Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock());`に変える。

```apex:疑似コールアウトは静的リソース場合のtestコード（getメソッドテスト）
@isTest
private class AnimalsCalloutsTest {
    @isTest 
    static  void testGetCallout() {
    // Create the mock response based on a static resource
    StaticResourceCalloutMock mock = new StaticResourceCalloutMock();
    // 取得したい静的リソース名を入れる
    mock.setStaticResource('GetAnimalResource');
    mock.setStatusCode(200);
    mock.setHeader('Content-Type', 'application/json;charset=UTF-8');
    // 本来end pointのresponseを疑似コールアウトに入れ替える
    Test.setMock(HttpCalloutMock.class, mock);
    // 実際responseを投げるapex http calloutメソッドを呼ぶ　
    HttpResponse result = AnimalsCallouts.makeGetCallout();
    // Verify mock response is not null
    System.assertNotEquals(null,result, 'The callout returned a null response.');
    // Verify status code
    System.assertEquals(200,result.getStatusCode(), 'The status code is not 200.');
    // Verify content type   
    System.assertEquals('application/json;charset=UTF-8',
      result.getHeader('Content-Type'),
      'The content type value is not expected.');  
    // Verify the array contains 3 items     
    Map<String, Object> results = (Map<String, Object>) 
      JSON.deserializeUntyped(result.getBody());
    List<Object> animals = (List<Object>) results.get('animals');
    System.assertEquals(3, animals.size(), 'The array should only contain 3 items.');          
    }   
}
```
```apex:疑似コールアウトは疑似インターフェース場合のtestコード（postメソッドテスト）
@isTest 
static void testPostCallout() {
    // Set mock callout class 
    Test.setMock(HttpCalloutMock.class, new AnimalsHttpCalloutMock()); 
    // This causes a fake response to be sent
    // from the class that implements HttpCalloutMock. 
    HttpResponse response = AnimalsCallouts.makePostCallout();
    // Verify that the response received contains fake values
    String contentType = response.getHeader('Content-Type');
    System.assert(contentType == 'application/json');
    String actualValue = response.getBody();
    System.debug(response.getBody());
    String expectedValue = '{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}';
    System.assertEquals(expectedValue, actualValue);
    System.assertEquals(200, response.getStatusCode());
}
```
```apex:疑似インターフェースを実装クラス
@isTest
global class AnimalsHttpCalloutMock implements HttpCalloutMock {
    // Implement this interface method
    global HTTPResponse respond(HTTPRequest request) {
        // Create a fake response
        HttpResponse response = new HttpResponse();
        response.setHeader('Content-Type', 'application/json');
        response.setBody('{"animals": ["majestic badger", "fluffy bunny", "scary bear", "chicken", "mighty moose"]}');
        response.setStatusCode(200);
        return response; 
    }
}
```
- テストの時「Always Run Asynchronously (常に非同期に実行)」の選択について補充説明。非同期 Apexは、複数の処理を後で別個のスレッドで実行するために使用します。非同期処理とは、ユーザーがタスクの終了まで待つことなく、タスクを「バックグラウンドで」実行するプロセスまたは機能です。そして、このオプションが有効になっていない場合は、1 つのクラスのテストのみが含まれるテスト実行が同期して実行されます。複数のクラスが含まれるテスト実行は、このオプションが有効になっているかどうかに関係なく非同期に実行されます。
![スクリーンショット 2023-01-12 18.14.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/5527d33f-673c-97f7-60a9-c9412b334661.png)
- 参考記事：[Apex REST コールアウト](https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_rest_callouts) [HttpCalloutMock インターフェースの実装による HTTP コールアウトのテスト](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_classes_restful_http_testing_httpcalloutmock.htm)

##### apex web サービス
- Apex クラスを rest Web サービスとして公開。sample code中Apex REST のベースエンドポイントは、
`https://<sf組織インスタンス>.my.salesforce.com/services/apexrest/Account/*` です。URL 対応付けでは大文字と小文字が区別される。
```apex:get request受けるsample code
// 一個のREST エンドポイント（apex　class）中に、get、post、putなどのメソッドは一回のみ使用できる
@RestResource(urlMapping='/Account/*')
global with sharing class MyRestResource {
    @HttpGet
    global static Account getRecord() {
        // Add your code
    }
}
```
- 作ったrest　end pointを動作確認の時、[workBench](https://workbench.developerforce.com/restExplorer.php)を使用できる。画面に入って、「Environment (環境)」で環境選択（Playgroundの場合は”Production (本番)”）、環境に合わせたアカウントを登録、そして「utilities (ユーティリティ)」を”REST Explorer”選択、メソッドを選び、相対パス`/services/apexrest/＜apexコードに合わせ＞`を入れる。実行結果を見たい場合「show/hide raw response」をクリック
![スクリーンショット 2023-01-13 12.37.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/a8fe8c98-4875-12ac-5838-358e43122830.png)
```apex:CaseのGet、Post、Delete、Put、Patchメソッド例
@RestResource(urlMapping='/Cases/*')
global with sharing class CaseManager {
    @HttpGet
    global static Case getCaseById() {
        RestRequest request = RestContext.request;
        // grab the caseId from the end of the URL
        String caseId = request.requestURI.substring(
          request.requestURI.lastIndexOf('/')+1);
        Case result =  [SELECT CaseNumber,Subject,Status,Origin,Priority
                        FROM Case
                        WHERE Id = :caseId];
        return result;
    }
    @HttpPost
    global static ID createCase(String subject, String status,
        String origin, String priority) {
        Case thisCase = new Case(
            Subject=subject,
            Status=status,
            Origin=origin,
            Priority=priority);
        insert thisCase;
        return thisCase.Id;
    }   
    @HttpDelete
    global static void deleteCase() {
        RestRequest request = RestContext.request;
        String caseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        delete thisCase;
    }     
    @HttpPut
    global static ID upsertCase(String subject, String status,
        String origin, String priority, String id) {
        Case thisCase = new Case(
                Id=id,
                Subject=subject,
                Status=status,
                Origin=origin,
                Priority=priority);
        // Match case by Id, if present.
        // Otherwise, create new case.
        upsert thisCase;
        // Return the case ID.
        return thisCase.Id;
    }
    @HttpPatch
    global static ID updateCaseFields() {
        RestRequest request = RestContext.request;
        String caseId = request.requestURI.substring(
            request.requestURI.lastIndexOf('/')+1);
        Case thisCase = [SELECT Id FROM Case WHERE Id = :caseId];
        // Deserialize the JSON string into name-value pairs
        Map<String, Object> params = (Map<String, Object>)JSON.deserializeUntyped(request.requestbody.tostring());
        // Iterate through each parameter field and value
        for(String fieldName : params.keySet()) {
            // Set the field and value on the Case sObject
            thisCase.put(fieldName, params.get(fieldName));
        }
        update thisCase;
        return thisCase.Id;
    }    
}
```
- workBench以外、コマンドラインcURLを使って、end pointアクセスできる。windowsやMacやLinuxのコマンドを入力前、目標組織に接続用アプリ作成必要、「アプリケーションマネージャ」→「新規接続アプリケーション」→「OAuth 設定の有効化」チェック入れ →「コールバック URL」に”https://”入れ →「選択した OAuth 範囲」に”選択した OAuth 範囲”入れる。
![スクリーンショット 2023-01-13 16.02.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/bfef984a-aa95-1367-d4ea-50c879abae96.png)
もし下のコマンド入れて、`{
  "error" : "invalid_grant",
  "error_description" : "authentication failure"
*Connection #0 to host login.salesforce.com left intact
}`が出たら、接続アプリ「manage」で「IP 制限の緩和」を”IP 制限の緩和”に変えたら多分できるようになる。
```shell:sf組織からaccess_token取得コマンド（最後の”-H”は返すjsonの改行）
curl -v https://login.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=<your_consumer_key>" -d "client_secret=<your_consumer_secret>" -d "username=<your_username>" -d "password=<your_password_and_security_token>" -H "X-PrettyPrint:1"
```
- 上のコマンドで「access_token」取得した後、Getメソッドで取得したいレコードIDとユーザ認証用のaccess_tokenをコマンドに入れる。ここの「yourInstance」は組織の識別名、例えPlaygroundホームページURLは`https://resilient-fox-gcanmz-dev-ed.lightning.force.com/lightning/page/home`の場合、`yourInstance == resilient-fox-gcanmz-dev-ed`
```shell:sf組織からレコード情報取得コマンド
curl https://<yourInstance>.my.salesforce.com/services/apexrest/Cases/<Record_ID> -H "Authorization: Bearer <access_token>" -H "X-PrettyPrint:1"
```
- Apex RESTテストコード書き方
```apex:上記apex restクラスのテストコード
@IsTest
private class CaseManagerTest {
    @isTest static void testGetCaseById() {
        Id recordId = createTestRecord();
        // Set up a test request
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
            + recordId;
        request.httpMethod = 'GET';
        RestContext.request = request;
        // Call the method to test
        Case thisCase = CaseManager.getCaseById();
        // Verify results
        System.assert(thisCase != null);
        System.assertEquals('Test record', thisCase.Subject);
    }
    @isTest static void testCreateCase() {
        // Call the method to test
        ID thisCaseId = CaseManager.createCase(
            'Ferocious chipmunk', 'New', 'Phone', 'Low');
        // Verify results
        System.assert(thisCaseId != null);
        Case thisCase = [SELECT Id,Subject FROM Case WHERE Id=:thisCaseId];
        System.assert(thisCase != null);
        System.assertEquals(thisCase.Subject, 'Ferocious chipmunk');
    }
    @isTest static void testDeleteCase() {
        Id recordId = createTestRecord();
        // Set up a test request
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
            + recordId;
        request.httpMethod = 'DELETE';
        RestContext.request = request;
        // Call the method to test
        CaseManager.deleteCase();
        // Verify record is deleted
        List<Case> cases = [SELECT Id FROM Case WHERE Id=:recordId];
        System.assert(cases.size() == 0);
    }
    @isTest static void testUpsertCase() {
        // 1. Insert new record
        ID case1Id = CaseManager.upsertCase(
                'Ferocious chipmunk', 'New', 'Phone', 'Low', null);
        // Verify new record was created
        System.assert(Case1Id != null);
        Case case1 = [SELECT Id,Subject FROM Case WHERE Id=:case1Id];
        System.assert(case1 != null);
        System.assertEquals(case1.Subject, 'Ferocious chipmunk');
        // 2. Update status of existing record to Working
        ID case2Id = CaseManager.upsertCase(
                'Ferocious chipmunk', 'Working', 'Phone', 'Low', case1Id);
        // Verify record was updated
        System.assertEquals(case1Id, case2Id);
        Case case2 = [SELECT Id,Status FROM Case WHERE Id=:case2Id];
        System.assert(case2 != null);
        System.assertEquals(case2.Status, 'Working');
    }    
    @isTest static void testUpdateCaseFields() {
        Id recordId = createTestRecord();
        RestRequest request = new RestRequest();
        request.requestUri =
            'https://yourInstance.my.salesforce.com/services/apexrest/Cases/'
            + recordId;
        request.httpMethod = 'PATCH';
        request.addHeader('Content-Type', 'application/json');
        request.requestBody = Blob.valueOf('{"status": "Working"}');
        RestContext.request = request;
        // Update status of existing record to Working
        ID thisCaseId = CaseManager.updateCaseFields();
        // Verify record was updated
        System.assert(thisCaseId != null);
        Case thisCase = [SELECT Id,Status FROM Case WHERE Id=:thisCaseId];
        System.assert(thisCase != null);
        System.assertEquals(thisCase.Status, 'Working');
    }  
    // Helper method
    static Id createTestRecord() {
        // Create test record
        Case caseTest = new Case(
            Subject='Test record',
            Status='New',
            Origin='Phone',
            Priority='Medium');
        insert caseTest;
        return caseTest.Id;
    }          
}
```
- 参考 [Apex Web サービス](https://trailhead.salesforce.com/ja/content/learn/modules/apex_integration_services/apex_integration_webservices)


##### apex class通じ、APIをアクセス
- salesforceのApex class通じ、APIをアクセス

- 先ず「指定ログイン情報」を作成、
![スクリーンショット 2022-07-26 22.42.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/3c9a92fb-e1b7-3d7f-3cda-7434dd3a6a88.png)
- 次Apex classの中request作成、send()メソッドでresponseを貰う、
    ```apex:
    Http http = new Http();
    HttpRequest request = new HttpRequest();
    request.setTimeout(120000);

    //setEndpoint()で「指定ログイン情報」/「アクセスパス」の形で設置
    //以下の例では、「RMS_APIGW」と言う指定ログイン情報のapiをアクセス、apiの具体のパスは：「/devices/cameras/standard_img/approval」
    request.setEndpoint(
      'callout:RMS_APIGW/api/devices/cameras/standard_img/approval'
    );

    request.setMethod('POST');
    request.setHeader('Authorization', IdToken);
    //受信可能なレスポンスデータのメディアタイプを指定
    request.setHeader('Accept', 'application/json');
      
    response = http.send(request);
    System.debug('★' + 'response : ' + response);

    return response;
    ```
- apiの設定により、requestにbodyが必要と必要無い場合がある、
  - 必要の場合（大体json形式）
    ```apex:
    String json =
      '{' +
        '"項目名１" : "' + 値１ + '",' +
        '"項目名２" : "' + 値２ + '",' +
        '"項目名３" : "' + 値３ + '",' +
        '"サブjson" : {' +
          '"サブ項目名１" : "' + サブ値１ + '",' +
          '"サブ項目名２" : "' + サブ値２ + '"' +
       '}' +
     '}';
     request.setBody(json);
     ```
  - 必要無い場合（大体pathで情報含む）
    例えば：apiの具体のパスは：「/devices/cameras/[項目]/standard_img/approval」の場合、request.setEndpoint()には以下のように設定：
    ```apex:
    request.setEndpoint(
      'callout:RMS_APIGW/api/devices/cameras/' + 
      [項目] + 
      '/standard_img/approval'
    );
    ```
参考：[コールアウトエンドポイントとしての指定ログイン情報](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_callouts_named_credentials.htm)

##### salesforceコンポーネントとmetadataの対照
- salesforce資材名
  各salesorce資材やカスタマイズ設置が「変更セット」で表示された名前。下のコンポーネント一覧をコピー出来る、そのままexclにペースト、整理できる。
  ![スクリーンショット 2023-06-29 13.43.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/2fc75bf0-e74c-1fc7-aeb4-b30b200e2cd9.png)
- metaData保存パス：
  ローカル環境salesforce project中、資材の保存パス。`force-app/main/default/`の下（vscodeの例）
  ![スクリーンショット 2023-06-28 15.15.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/79dbfb38-a051-e299-1df0-4ce4018c8ce4.png)
- metaData取得パス：
  vscode「org Browser」上salesfroce metadataの保存先（vscodeの例）
  ![スクリーンショット 2023-06-28 15.16.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/fde55dad-9503-bfb8-97d9-01f7d662d577.png)
- package.xml 項目名：
  metadataをdeployや取得用のpackage.xml中で、`<name>`中に入れる資材名  [package.xml参考ファイル](https://gist.github.com/iamsonal/1f4a97d9bdec14248613e8675ccf5981)
  ![スクリーンショット 2023-06-28 15.17.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/954a3409-4103-1d44-856c-711da25ac579.png)

| salesforce資材名 | metaData保存パス | metaData取得パス | package.xml 項目名 | 追加説明 |
| :-------- | :-----  | :-----  | :-----  | :----- |
| サイト |  | custom sites | CustomSite |  |
| リモートサイト |  | remote site settings | RemoteSiteSetting |  |
| Aura コンポーネントバンドル | aura | aura components | AuraDefinitionBundle |  |
| visualforce page | pages | visualforce pages | ApexPage |  |
| Apex クラス | classes | apex classes | ApexClass |  |
| Apex トリガ | triggers | apex triggers | ApexTrigger |  |
| カスタムオブジェクト | objects | custom objects | CustomObject |  |
| カスタム項目 | objects/オブジェクト名/fields | objects/オブジェクト名/fields | CustomField | custom objectと同時にdownload |
| 標準項目選択リストカスタマイズ後の値 |  | standard value sets | StandardValueSet | [参考記事](https://salesforce.stackexchange.com/questions/176182/opportunity-stagename-metadata-retrieve-in-api-39) |
| ページレイアウト | objects/オブジェクト名/layouts | layouts | Layout | record画面のオブジェクトを編集で開く |
| lightingページ | flexipages | flexipages | FlexiPage | record画面の編集ページで開く & オブジェクト詳細「Lightning レコードページ」画面 |
| リストビュー | objects/オブジェクト名/listViews | custom objects | ListView | custom objectと同時にdownload |
| ボタン、リンク、およびアクション |  | quickActions | QuickAction | record画面のオブジェクトを編集で開く |
| レコードタイプ | objects/オブジェクト名/recordTypes | custom objects | RecordType |  |
| 入力規則 | objects/オブジェクト名/validationRules | custom objects | ValidationRule |  |
| カスタムメタデータ型 |  | custom objects | CustomObject | [参考記事](https://developer.salesforce.com/docs/atlas.ja-jp.api_meta.meta/api_meta/meta_custommetadatatypes.htm) |
| カスタムメタデータ型の項目 | fields | custom objects | CustomField | custom objectと同時にdownload |
| カスタムメタデータ型のレコード |  | custom metadatas | CustomMetadata | [参考記事](https://developer.salesforce.com/docs/atlas.ja-jp.api_meta.meta/api_meta/meta_custommetadatatypes.htm) |
| 標準項目タブと表示ラベルの名称変更 | objectTranslations | Custom Object Translation | CustomObjectTranslation | metadataをdeployの時オブジェクトを分けてdeployする [参考記事](https://developer.salesforce.com/docs/atlas.ja-jp.api_meta.meta/api_meta/meta_translations.htm) |
| タブ | tabs | custom tabs | CustomTab |  |
| ケースの割り当てルール | assignmentRules | Assignment Rules | AssignmentRules | metadata通じての直接削除でき無い、環境上手動で必要 |
| フロー | flows | flows | Flow | flowsはフロー本体を保存用、flowDefinitionsはバージョン記録用 |
| カスタム表示ラベル | labels | custom labels | CustomLabel（CustomLabelsは全部まとめ用） |  |
| アセットファイル | contentassets | content assets | ContentAsset |  |
| アプリケーション | applications | custom applications | CustomApplication |  |
| メールテンプレート | email | email templates | EmailTemplate |  |
| ワークフローメールアラート | workflows | workflows | Workflow |  |
| レポート作成スナップショット | analyticSnapshots | Analytic Snapshot | AnalyticSnapshot | [”*”で全て選ぶは有効しない](https://www.asagarwal.com/salesforce-metadata-types-that-do-not-support-wildcard-characters-in-package-xml/) |
|  |  |  |  |

上記資材がsalesforce画面上のイメージ：
- オブジェクト、「設定」の「オブジェクトマネージャ」画面
  ![スクリーンショット 2023-06-28 15.19.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/8c929445-b98a-7f2d-98e2-01f00702a696.png)
- カスタム項目、オブジェクト詳細画面に入った後、「項目とリレーション」をクリック
  ![スクリーンショット 2023-06-28 15.22.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/1dc4353d-2ebe-f32b-1601-61ad1e0c0a4f.png)
- アクション、オブジェクト詳細画面に入った後、「ボタン、リンク、およびアクション」をクリック
  ![スクリーンショット 2023-06-28 15.24.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/68c29f58-4c1b-566a-0263-82c4bab44e82.png)
- ページレイアウト、オブジェクト詳細画面に入った後、「ページレイアウト」をクリック
  ![スクリーンショット 2023-06-28 15.26.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/97b72eea-1f90-16c2-604e-db1db6523449.png)
- 入力規則、オブジェクト詳細画面に入った後、「入力規則」をクリック
  ![スクリーンショット 2023-06-28 15.27.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/6f12c651-968f-03f1-e355-69a951a0bda5.png)
- リストビュー、オブジェクト詳細画面に入った後、「リストビューボタンレイアウト」をクリック
  ![スクリーンショット 2023-06-28 15.28.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/619b454f-b421-cea5-cb5c-16274303be62.png)
- Apex クラス ＆ Apex トリガ、「設定」の「Apex クラス」と「Apex トリガ」画面
  ![スクリーンショット 2023-06-28 15.30.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/fa459661-d8d6-4dad-9b4e-5089f9ee9480.png)
- フロー、「設定」の「フロー」画面
  ![スクリーンショット 2023-06-28 15.33.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/de597287-5de5-c830-3737-8e5eda118d4f.png)
- lightingページ、オブジェクト詳細画面に入った後、「Lightning レコードページ」をクリック
  ![スクリーンショット 2023-06-28 15.37.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/29d7f301-fee5-0212-1700-6d8691992432.png)
- 標準項目タブと表示ラベルの名称変更、「設定」の「タブと表示ラベルの名称変更」画面、その内「標準タブ」は標準オブジェクトの表示名と項目の表示名をカスタマイズ
  ![スクリーンショット 2023-06-28 17.05.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/4da4dac1-0fa2-0af1-dea6-05fccd8f6fa3.png)
- アセットファイル、「設定」の「アセットファイル」画面
  ![スクリーンショット 2023-06-29 14.35.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/4a3df9d0-d024-0475-d7bd-c4732a46f9d9.png)
- アプリケーション、「設定」の「アプリケーションマネージャ」画面
![スクリーンショット 2023-06-29 14.35.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/e3ef935a-bde1-1502-f52b-e72b9efaea6f.png)
- タブ、「設定」の「タブ」画面
![スクリーンショット 2023-06-29 14.36.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/b0737ea7-0f3b-7d60-5dc3-9faf9508760f.png)
- カスタム表示ラベル、「設定」の「カスタム表示ラベル」画面
![スクリーンショット 2023-06-29 14.37.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/9df0a8a8-1f5a-4b52-dae4-85dea21d6c66.png)
- レポートタイプ、「設定」の「レポートタイプ」画面
![スクリーンショット 2023-06-29 14.38.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/5c510dfd-77dd-190e-bbff-6c7097b27ea3.png)
- メールテンプレート、「設定」の「Classic メールテンプレート」画面
![スクリーンショット 2023-06-29 14.38.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/47b26eed-c542-2c75-a933-c8ff985b6ed3.png)
- Aura コンポーネントバンドル、「設定」の「Lightning コンポーネント」画面
![スクリーンショット 2023-06-29 14.40.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/939d0a6e-4e99-e305-7366-0430d5f27c86.png)
- ワークフローメールアラート、「設定」の「メールアラート」画面
![スクリーンショット 2023-06-29 14.43.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/a4228741-421d-ba4a-0119-62318cbed6f3.png)
- レポート作成スナップショット、「設定」の「レポート作成スナップショット」画面
![スクリーンショット 2023-06-29 14.43.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/fc2d12f1-f4b4-199b-fe0e-86df5ad22ea8.png)


##### metadataでプロファイルをdeploy時の注意点
追加説明：
spring26（約2025年）プロファイル中のアクセス権限まわりが権限セットに移行され、プロファイルはレコードタイプやログインIPなどの権限を持つ [参考URL](https://admin.salesforce.com/blog/2023/permissions-updates-learn-moar-spring-23)

結論から言う：
1. salesforceはmetadataの形でA環境のプロファイルをB環境に移すのは推奨し無い
   - metadataでも、変更セットでも、プロファイルの移行が効率よくないので；
   - 効率が良い方法は：まず本番環境で空っぽのプロファイルを作成、そして本番からsandboxを作る。登録時間帯やIPのみこの空っぽプロファイルからコピーを取得し、コントロールする。オブジェクトやシステム権限などは権限セットでコントロール。
2. salesforce環境（webページ）でプロファイル設置する時、項目レベルセキュリティーは画面遷移ある為、時間がかかる上、抜け漏れしやすい。でも他の内容は画面遷移がないため、設定の手間が少ない。加えて、プロファイルのmetadataで取得出来る内容は少ない、[オフィシャル説明](https://developer.salesforce.com/docs/atlas.ja-jp.api_meta.meta/api_meta/meta_profile.htm)にもハッキリ書いていない。
以上の原因で、metadataでプロファイルをdeployする場合、「項目レベルセキュリティー」と「オブジェクトアクセス」と「タグ」と「アプリ」の設定だけmetadataで取得し、目標環境にdeployして、他の部分は手動で追加。
   - sfdxでは、”プロファイル”と言うフォルダが無いので、package.xmlでプロファイルのmetadataを取得する、package.xml例↓　[標準オブジェクト含むversion](https://github.com/PluckYang/Development-Note/blob/main/salesforce/getProfileMetaData.xml)
   ```apex:
   //"*"でカスタム・外部オブジェクトの設定を取得出来るが、標準オブジェクトの設定を取得ため、
   //手動追加が必要”Account”の様に、全ての標準オブジェクトを含むpackage.xmlファイルは上の”標準オブジェクト含むversion”を参考
   <?xml version="1.0" encoding="UTF-8"?>
   <Package xmlns="http://soap.sforce.com/2006/04/metadata">
   <types>
        <members>Account</members>
        <members>*</members>
        <name>CustomObject</name>
   </types>
   <types> 
        <members>*</members>
        <name>CustomApplication</name>
   </types>
   <types> 
        <members>*</members>
        <name>CustomTab</name>
   </types>
   <types>
        <members>*</members>
        <name>Profile</name>
   </types>
   <version>53.0</version>
   </Package>
   ```
3. 標準オブジェクトのmetadata取得の時、もし標準オブジェクトは何の変更がなあい場合は、そのmetadataは取得し無い。
4. もし既に目標環境の中には”ABCプロファイル”が存在した場合、”ABCプロファイル”のmetadataをdeployすると、metadataの中にある項目は目標環境”ABCプロファイル”の項目を上書きする；metadataの中にない項目なら、上書きし無い
5. [補充説明](https://benahm0.medium.com/profiles-permission-sets-deployment-in-salesforce-d53bd3a4771a)

##### metadataで権限セットをdeploy時の注意点
以下図のように、「dreamhouse」と言う権限セットのmetadata取得出来るかどうかを検証。
![スクリーンショット 2023-03-21 15.19.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/5865a150-df16-e2ce-8942-807f0a7698d1.png)
結果として：
- 取得出来る
  - 割り当てられたアプリケーション
  - オブジェクト設定
    - 標準オブジェクト
      - タブの設定
      - オブジェクト権限
      - 項目権限
  - アプリケーション権限
  - Apex クラスアクセス
  - Visualforce ページのアクセス
  - フローアクセス
  - システム権限
- 取得できない
  - 割り当てられた接続アプリケーション
  - 指定ログイン情報アクセス
- 未確認
  - （未確認）外部データソースアクセス
  - （未確認）カスタム権限
  - （未確認）カスタムメタデータ型
  - （未確認）カスタム設定の定義
  - （未確認）サービスプロバイダ

##### metadataで「フロー」資材をdeploy / 削除
- （deploy）取得先該当フローが無効化状態
  - 最新versionのフロー資材が取得、フローAPI名.flow-meta.xml ファイルに<status>Obsolete</status>ステータスが表示されます
  - 資材追加用package.xml構成例：API名が「test03」のフロー資材を追加
    ```apex:フローdeploy用package.xml例
    <?xml version="1.0" encoding="UTF-8"?>
    <Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
      <members>test03</members>
      <name>flow</name>
    </types>
    <version>53.0</version>
    </Package>
    ```
  - deploy結果：
    - deploy先環境で該当フロー存在しない場合
      - 該当フローが作成され、version番号１に設定、フローが無効化されます。
    - deploy先環境で該当フロー存在する場合
      - 該当フロー有効化されたversionが最新versionの場合、versionが追い番され、deploy資材になります
      - 該当フロー有効化されたversionが最新versionでは無い場合、該当フロー最新のversionがdeploy資材に上書きされます

- （deploy）取得先該当フローが有効化状態
  - 有効化されたversionの資材が取得、フローAPI名.flow-meta.xml ファイルに<status>Active</status>ステータスが表示されます
  - deploy結果：
    - deploy先環境で該当フロー存在しない場合：該当フローが作成され、version番号１に設定、フローが有効化されます。
    - deploy先環境で該当フロー存在する場合：該当フローversionが追い番され、且つ最新versionが有効化されます。

- salesforce組織上「フロー」資材を削除
  - フロー資材を削除の時、一つのフロー丸ごと削除はできません、フローのversion番号を指定し、version別々削除する必要があります。フローの全versionが削除後、フロー自体も消します
    例：API名「test03」のフローには三つのversionが存在します、該当フローを削除するため、三つのversion全部削除必要があります
    ```apex:フロー削除用package.xml例
    <?xml version="1.0" encoding="UTF-8"?>
    <Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <types>
      <members>test03-1</members>
      <members>test03-2</members>
      <members>test03-3</members>
      <name>flow</name>
    </types>
    </Package>
    ```
  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/ee0668f2-6391-5371-00a2-7871baed651d.png)

- 追加説明
  - salesforce「フロー」をmetadataで組織へdeployするには、以下の二つパーツと絡む
    - 「flow」一つのフローの中身、version情報とか付いていない、
    - 「flowDefinitions」フローのversion情報とかをつ
  - vscode、githubでmetadata管理、deploy（所謂CI/CD）を行う場合 
    - 「flowDefinitions」フォルダをgitignoreに入れ、触らない方が便利
    - フローの削除や特定versionの削除操作は、SF環境でやった方がいい；もしsfdx使って、ローカル環境でpackage.xmlの方法でSF環境上のフローの特定versionの削除操作を行う場合；sfdxGUIのフローは図の様に、綺麗に名前で分けるではなく、「flow　name-versionXXX」の羅列表示になってしまう、かなり醜くなる、余り進めない。そうなった場合、も一回sfdx環境を構築したら図の様な表示に戻る。
  ![スクリーンショット 2022-10-15 12.26.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/747564a4-4268-5ccd-62b4-e8bc4539bb4c.png)
    - 「flowDefinitions」作用の検証
      ```
      検証用：テストtask5796検証用フロー（testTask5796）作成
      flowDifinitionをdeploy資材に入れる
      version１を有効化、他version存在なし
      flowDifinitionの<activeVersionNumber>を除去し
      flowとflowDifinition両方をdeploy
      結果：追い番なし、version１のまま、無効化に成る
      version１を無効化、他version存在なし
      flowDifinitionの<activeVersionNumber>を除去し
      flowとflowDifinition両方をdeploy
      結果：追い番あり、version２追い番、無効化に成る
      version１を有効化、他version２存在あり
      flowDifinitionの<activeVersionNumber>を除去し
      flowとflowDifinition両方をdeploy
      結果：追い番あり、version３追い番、無効化に成る
      version３を有効化、他version１・２存在あり
      flowDifinitionの<activeVersionNumber>を除去し
      flowとflowDifinition両方をdeploy
      結果：追い番あり、version４追い番、無効化に成る
      version全部無効化、flowファイルversion４と同じ中身、他version存在あり
      flowDifinitionの<activeVersionNumber>を除去し
      flowとflowDifinition両方をdeploy
      結果：追い番あり、version５追い番、無効化に成る
      flowDifinitionをdeploy資材に入れない
      version全部無効化、flowファイルversion５と同じ中身、他version存在あり
      flowDifinitionファイルdeployしない
      結果：追い番あり、version６追い番、有効化に成る
      version６を有効化、他version存在あり
      flowDifinitionファイルdeployしない
      結果：追い番あり、version７追い番、有効化に成る 
      ```   


##### 外部オブジェクト項目削除
外部オブジェクト項目を削除の時、sfdxコマンドでdistrictivePackage.xmlをdeployによっての削除方法はできない、環境上手動作業が必要。
そして削除後、該当項目API名後ろに”_del”が追加される。一度削除された項目を復元しても、”_del”のサブフィックスは自動的に消せない、そこも手動修正が必要。

##### apex class管理
apex classのmetadata管理の時、`force-app/main/default/classes`の下に更にフォルダを作って、違う用途のapex　classを分ける。この場合、apex classの名前はグローバルで一意である必要。[参考URL](https://github.com/docusign/sample-app-myfinance-apex/tree/master/force-app/main/default/classes)
フォルダ分けると、こんな感じ：
![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/8bf8485a-e7d0-1777-0ddc-2aab4447dfcb.png)


##### sf環境との接続エラー
sf環境接続後、もし、「org　Browser」をリフレッシュやmetadataダウンロードの時、「Unexpected end of JSON input」のエラーが発生し、接続できなくなったの場合。
- `.sfdx`フォルダーを確認、中には空っぽのjsonファイルがあるばい、それを削除 [参考記事](https://github.com/forcedotcom/cli/issues/226)
- `sfdx force:org:list`コマンドで接続状況を確認、もし接続正常の場合。vscodeを何分放置し、接続が治るはず
![スクリーンショット 2023-05-23 16.43.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/3ed216eb-ccc7-f9a1-a177-5db39df1274a.png)


##### gitブランチ間の差分でmetadataのpackageファイルを作成
以下のコマンドで修正資源のみがリストされる差分package.xmlファイルを生成（修正資源のファイルパスが空白を含む時は(2)で実施）
```
(1) sfdx force:source:manifest:create --sourcepath `git diff 変更前ブランチ名 変更後ブランチ名 --name-only  --diff-filter=MRA | awk '{printf ","$1;} '` --manifestname ./manifest/package.xml

(2) sfdx force:source:manifest:create -p "`git diff 変更前ブランチ名 変更後ブランチ名 --name-only --diff-filter=MRA | awk '{ o = NF > 1 ? "'\''" $0 "'\''" : $0; s = length(s) > 0 ? s "," o : s = o } END{ print s }'`" -n ./manifest/package.xml
```


##### deplpoy検証
manifest/package.xmlの内容は目標組織にdeploy出来るかどうかを検証、`-c`を取り除くと普通のdeployコマンド
```
sfdx force:source:deploy -c --manifest manifest/package.xml  --postdestructivechanges manifest/destructiveChangesPost.xml -u ユーザ名（省略可能）
```
```
// 今回デプロイするApexテスト + デプロイ先に存在するすべてのApexテストを実行する
// 共通クラスの修正をしているような場合はこれで他に影響が出ていないことを確認する
sfdx force:source:deploy -c --manifest manifest/package.xml  --postdestructivechanges manifest/destructiveChangesPost.xml -l RunLocalTests -u ユーザ名（省略可能）

// 指定したApexテストのみを実行する
sfdx force:source:deploy -c --manifest manifest/package.xml  --postdestructivechanges manifest/destructiveChangesPost.xml -l RunSpecifiedTests -r APEXテストクラス名01,APEXテストクラス名02 -u ユーザ名（省略可能）
```


##### vscode上、apex classテストコード走る
vscode上、apex classテストコード走る時、テストコードはリモート組織上で走っている、その為、先ず新しいテストコードを組織へdeployする必要がある


##### apexでレコード大量作成
[chatGPT](https://chat.openai.com/)に実現したいことを詳しく説明すれば、コードを作ってくれる
```apex:「行動」「ToDo」作成と削除の例
public class CreateDeleteClass {
    public static void createEvents() { //「行動」（event）作成用
        DateTime sampleDateTime01 = DateTime.newInstance(2023, 5, 24, 10, 0, 0); // サンプルの日時を設定
		DateTime sampleDateTime02 = DateTime.newInstance(2023, 5, 25, 10, 0, 0); // サンプルの日時を設定
        Id ownerId = '0059D000004SHxdQAG'; // 参照所有者ユーザレコード
        Id whatId = '0069D00000SaodSQAR' ; // 参照商談レコード
        
        List<Event> records = new List<Event>(); // 399個行動レコード作成
        for (Integer i = 0; i < 400; i++) {
            Event obj = new Event(
                Subject = 'Sample Event ' + i,
                StartDateTime = sampleDateTime01,
                EndDateTime = sampleDateTime01.addHours(1),
                OwnerId = ownerId,
                WhatId = whatId                                
                );
            records.add(obj);
        }
 		insert records;
    }


    public static void deleteEvents() { // 　「行動」（event） 削除用
        DateTime sampleDateTime01 = DateTime.newInstance(2023, 5, 24, 10, 0, 0); // 削除したい特定の日付を指定
		List<Event> eventsToDelete = [SELECT Id FROM Event WHERE StartDateTime >= :sampleDateTime01];
		delete eventsToDelete;
    }
     
    
    public static void createTasks() { // 　「Todo」（task） 作成用
        List<Task> tasks = new List<Task>();
        Id ownerId = '0059D000004SHxdQAG';
        Id whatId = '0069D00000SaodSQAR' ;
        Date dateToSet  = Date.newInstance(2023, 5, 24);
        for (Integer i = 0; i < 400; i++) {
            Task task = new Task(
                Subject = 'Sample Task ' + i,
                ActivityDate = dateToSet,
                OwnerId = ownerId,
                WhatId = whatId
            );
            tasks.add(task);
        }
		insert tasks;
    }
        

    public static void deleteTasks() { // 　「Todo」（task） 削除用
        Date dateToSet  = Date.newInstance(2023, 5, 24);// 削除したい特定の日付を指定
		List<Task> tasksToDelete = [SELECT Id FROM Task WHERE ActivityDate >= :dateToSet];
		delete tasksToDelete;
	}
}
```
apex class保存後、メソッドを呼ぶ場合、開発者コンソールを開き、「Debug」タブの「Open Execute Anonymous Window」を開き、`class名.method名`入れ、「execute」でメソッドを実行。
![スクリーンショット 2023-05-22 18.00.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/fc645d15-56b6-3f67-b9b0-35124f78b056.png)
![スクリーンショット 2023-05-22 18.00.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2747384/3bb31d9a-188f-36ec-f666-f4ca935f4de5.png)



##### apex classテストクラス作成時、参照関係の書き方
例：
テスト用contact作成時、参照関係になるaccountも作成され；その参照関係を保つため、
contact.AccountId = account.Id