Salesforce CLI で CircleCIの自動テストを実行する

こんにちは、こちらは[CircleCI Advent Calendar 2023](https://qiita.com/advent-calendar/2023/circleci)の23日目、Perfumeはかしゆかの生誕祭をお祝いする記事になります。
悲しいことに1日遅れになってしまいました。よよよ。それでもゆかちゃん誕生日おめでとう🎉良い35歳を過ごしてください。

ということでSalesforceの開発をCircleCIで自動化していきます。

# なぜ今更?

CircleCI + Salesforce の自動化記事は世に沢山あります。しかし残念ながら、どれもこれも古い`sfdx`コマンドを使ったもので、やがてなくなっていくコマンドを使って自動化しています。
ここはやはり、Salesforceの中の人として、`salesforce cli`を使った自動化を実現しなければならないと今回の記事を生成しました。が、結構大変でゆかちゃんの誕生日に間に合いませんでした。ホント残念です。

が、Salesforce CLIを使った自動化方法は完全にできあがりましたので、1日遅れで公開していきます。

# 事前準備

Salesforce CLIを使って自動化したバッチ処理を行う場合には、JWTを使った認証が必要となります。
まず、こちらの記事を元にして証明書の作成と、Salesforce組織側の設定を行っておく必要があります。

[Salesforce CLIをJWTで使う場合の設定](https://qiita.com/sho7650/items/88bd8e1ae43a6d7bea81)

# 暗号化した証明書の準備

下地ができあがりましたら、今度は外部に公開してぶちまけてしまっても安全な証明書を準備して、CircleCIを設定していきます。

事前に準備した`server.key`が必要です。

## パスフレーズを暗号化する

まず証明書を暗号化するために必要なパスフレーズを準備します。そのパスフレーズでしか復号化できないようにするためです。とは言え、パスフレーズをそのままどこかに記してしまったら不安全で仕方ありません。念には念を入れて、このパスフレーズをまず暗号化していきます。

`<パスフレーズ>`には、好きなパスフレーズを指定してください。なんでも良いです。
実行すると`key`と`iv`が出力されます。このパスフレーズを暗号化した情報源です。やったぜ。

```bash:パスフレーズを元に暗号化情報を取得する
> openssl enc -aes-256-cbc -k <パスフレーズ> -P -md sha1 -nosalt -iter 10000
key=7F045E41781E5E49A48AE86716375E4ED5FB9D4325F39CB3D2385C007755F945
iv =11650FF4873F3A391729991E53FD588C
```

この暗号化されたパスフレーズ情報を元にして、`server.key`を暗号化します。

```bash:暗号化コマンド
> openssl enc -nosalt -aes-256-cbc -in ./foo/server.key -out server.key.enc -base64 -K 7F045E41781E5E49A48AE86716375E4ED5FB9D4325F39CB3D2385C007755F945 -iv 11650FF4873F3A391729991E53FD588C
```

これで、先ほどの暗号化されたパスフレーズ情報を元に、証明書を暗号化した外部に公開されちゃっても安全な証明書ファイルができました。実際に利用するときは、この暗号化された証明書を復号化して利用します。このままでは利用できません。

参考: [forcedotcom/sfdx-circleci](https://github.com/forcedotcom/sfdx-circleci?tab=readme-ov-file)

# Salesforce DXプロジェクトに、暗号化された証明書を配置する

今回は[YUKAちゃん誕生日おめでとうFY23ソースコードサンプル](https://github.com/sho7650/YUKA)をベースに進めていきます。

今回は、Salesforce DXプロジェクトのルートディレクトリに `assets`というフォルダを準備して進めます。その配下に、先ほど準備した暗号化された証明書`server.key.enc`を配置します。それ以外の証明書関係のファイルはプロジェクトディレクトリには配置しないでください。ヤバイです。情報漏洩です。

暗号化されたパスフレーズなどもファイルに残したりしないでください。なお、上記の`key`と`iv`情報は私は使っていないので、これを使ってもなりすましは不可能です。フフフ。

と言うことで、ファイルを配置できたら、準備は完了です。あとはCircleCI側の設定です。

# CircleCIを設定する

該当のGitHubリポジトリとCircleCIを連携してプロジェクトを準備してください。ここでは割愛します

## 環境変数を設定する

プロジェクトを準備したら、次の4項目の環境変数を作成してください。`Project Settings`から`Environment Variables`を辿ってください。
そこから`Add Environment Variable`で4つの環境変数を作成します。

1. CONSUMER_KEY ... Salesforce組織の接続アプリケーションで作成された「コンシューマー鍵」を定義します
1. DECRYPTION_IV ... 先ほど作成した暗号化されたパスフレーズの`iv`情報をここに記します
1. DECRYPTION_KEY ... 先ほど作成した暗号化されたパスフレーズの`key`情報をここに記します
1. USERNAME ... Salesforce組織へ接続するときのユーザー名(メールアドレス形式のアレ)を指定します

![CleanShot 2023-12-24 at 11.48.54@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37263/8721ac9e-fc44-a50c-0cdf-3ece3512502c.png)

これらを環境変数として持つことで、外部に公開されているソースコードも安全に運用が可能というわけです。

## config.yml を準備する

全体像はこちらです。ゆかちゃんの誕生日にあわせて`yuka-birthday`というジョブとワークフローを設定しました。名前は好きにしてください。

特徴としては、Salesforceが提供するSalesforce CLIおよびnpmパッケージを含むDockerイメージを利用しています。これにより古い`sfdx`コマンドを使わずに、常に最新の`salesforce cli`が利用できます。

参考:[Docker イメージを使用した Salesforce CLI の実行 | Salesforce CLI 設定ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_setup.meta/sfdx_setup/sfdx_setup_docker.htm)

また、先ほど設定した「暗号化された証明書」を復号化してJWTの鍵として利用しています。

```yml:./circleci/config.yml
version: 2.1

jobs:
  yuka-birthday:
    docker:
      - image: salesforce/cli:latest-full
    steps:
      - checkout
      - run:
          name: "Create a server.key"
          command: |
            openssl enc -nosalt -aes-256-cbc -d -in assets/server.key.enc -out assets/server.key -base64 -K $DECRYPTION_KEY -iv $DECRYPTION_IV
      - run:
          name: "Authenticate and create Scratch org to Salesforce DevHub"
          command: |
            sf force auth jwt grant -i $CONSUMER_KEY -f assets/server.key --username $USERNAME -d
            sf org create scratch -f config/project-scratch-def.json -a circle_build_$CIRCLE_BUILD_NUM
            sf project deploy start -o circle_build_$CIRCLE_BUILD_NUM

      - run:
          name: "Run LWC tests"
          command: |
            npm install
            npm test
      - run:
          name: "Run Apex tests"
          command: |
            sf force apex test run --result-format human --code-coverage -o circle_build_$CIRCLE_BUILD_NUM

      - run:
          name: "Delete useless Scratch org"
          command: |
            sf org delete scratch -p -o circle_build_$CIRCLE_BUILD_NUM

workflows:
  version: 2
  yuka-birthday:
    jobs:
      - yuka-birthday

```

各所のポイントを説明します。

### 証明書の復号化

暗号化された証明書`server.key.enc`と`DECRYPTION_KEY`、`DECRYPTION_IV`を利用して、証明書を復号化しています。複合に必要な情報を環境変数として定義していることで、ソースコード内に漏洩したら困るような情報の排除が可能となっています。やるじゃん。

`openssl enc -nosalt -aes-256-cbc -d -in assets/server.key.enc -out assets/server.key -base64 -K $DECRYPTION_KEY -iv $DECRYPTION_IV`

base64化した秘密鍵を環境変数に入れるという方法もとれるだろうと思いつつも、公式に従ってこの方法をとりました。興味がある方は次の記事を参考に頑張ってみてください。できたら教えてほしい。

[[RS256] JWTでRSA秘密鍵を環境変数で処理したい [Javascript] #JavaScript - Qiita](https://qiita.com/sho7650/items/1dd65a1db785f902a2d6)

### JWT認証とスクラッチ組織作成とデプロイ

`sf force auth jwt grant`コマンドで`CONSUMER_KEY`と、復号化した`server.key`を使ってDevHub組織の認証を行っています。

`sf org create scratch`コマンドでスクラッチ組織を作成します。ここで`-a circle_build_$CIRCLE_BUILD_NUM`で別名を割り当てています。最後にスクラッチ組織を削除するために別名を割り当てています。

`sf project deploy start`で該当のスクラッチ組織へデプロイしています。

```bash:認証と組織の作成
sf force auth jwt grant -i $CONSUMER_KEY -f assets/server.key --username $USERNAME -d
sf org create scratch -f config/project-scratch-def.json -a circle_build_$CIRCLE_BUILD_NUM
sf project deploy start -o circle_build_$CIRCLE_BUILD_NUM
```

参考:[CircleCI を DevHub に接続 | Salesforce DX 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/atlas.ja-jp.sfdx_dev.meta/sfdx_dev/sfdx_dev_ci_circle_connect.htm)

### Lightning Web Componentのテスト

Lightning Web Component(LWC)は、Jestを使ってテストをすることが推奨されています。[Jest のインストール](https://developer.salesforce.com/docs/platform/ja-jp/lwc/guide/unit-testing-using-jest-installation.html)を参考にしてJestでテスト実行できる環境を設定しておく必要があります。

LWCのテストは、通常のJavaScriptのテストと同じです。`npm install`で必要なパッケージをインストールして`npm test`でテスト実行です。

```bash:LWCのテスト
npm install
npm test
```

参考:[Lightning Web コンポーネントのテスト | Lightning Web Components 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/platform/ja-jp/lwc/guide/testing.html)
参考: [Jest のインストール | Lightning Web Components 開発者ガイド | Salesforce Developers](https://developer.salesforce.com/docs/platform/ja-jp/lwc/guide/unit-testing-using-jest-installation.html)

### Apexのテスト

Salesforce Apexのテストも勿論実行可能です。
`--result-format human`や`--code-coverage`を付けていますが、成功したときは何も表示されないので付ける意味はあまり感じていません。実際のテスト結果の詳細は、実行結果内で示唆される`sf apex get test -i 707H3000000xVjq -o test-xxxxxxxxxxx@example.com`で確認が可能なので、これをうまく拾えたら良いんでしょうね。どうやったらいいんだろう。

`sf force apex test run --result-format human --code-coverage -o circle_build_$CIRCLE_BUILD_NUM`

### スクラッチ組織の削除

最後にテストを実行したスクラッチ組織を削除します。作成可能な下図などに上限がありますので、一般的には削除運用が望ましいです。

`sf org delete scratch -p -o circle_build_$CIRCLE_BUILD_NUM`

スクラッチ組織を使い回すという考え方もあるでしょう。使い回しても良いかどうかは開発する内容にもよりますので一概には言えませんが、考慮しても良いかもしれません。

参考:[sfdx-circleci/.circleci/config.yml at master · forcedotcom/sfdx-circleci](https://github.com/forcedotcom/sfdx-circleci/blob/master/.circleci/config.yml)

# CircleCIで実行してみる。

[YUKAちゃん誕生日おめでとうFY23ソースコードサンプル](https://github.com/sho7650/YUKA)を元にCircleCIでテストを実行すると、次のような結果となります。ご参考に。

![download.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/37263/57eeafce-7705-feff-bf68-c57c155a871c.jpeg)

今年もお世話になりました🙏