banner

新着情報

Tech Blog

2023.07.31
勤怠管理システム『きんたくん』の単体テストとGitHub Actionsでの機密情報の扱い
pic

きんたくん is 何?


 

おはようございます~!㉔です!
先日の帰社日では漫才風の技術勉強会を実施し、好評を頂きました^^
これからも学びやすい勉強会を企画していきたいと思います!

 

きんたくんはAz One自社開発の勤怠管理システムです。詳しくは以前の記事で紹介しているので前回の技術ブログをご参照ください。

 

きんたくんのテスト環境


テストは堅牢なシステムを実装する上で欠かせません。テストの必要性、粒度に関する議論が尽きることはありませんが、少なくとも自動テストの無い開発環境は避けたいところです。

 

単体テストツール


今回はVitestを採用しています。熟成されてエコシステムの揃っているJestと迷いましたが、きんたくんは社内システムであり新規技術の採用に向いているPJですので知見の少ないVitestを採用しました。

 

 

テストのトリガー


単体テストはリポジトリのpushをトリガーとしてGitHub Actionsを利用して実施しています。きんたくんは当初3人で開発を実施していましたが(現在1人で保守しています)、Pull Requestはテストをパスしないとマージできないように制御していたため、複数人の開発でもデグレもなく安定した開発となりました。

 

テストの方針


今回の単体テストの方針は以下としました。

 

  • DiscordのAPIに依存する部分はテストしない

 

  • ビジネスロジックに関する部分を単体テストを実装する

 

メッセージの送信等、Discord APIを利用する部分をテストするならMock Service Workerを使用してリクエストをインターセプトすることになりますが、きんたくんはDiscord APIのレスポンスはロジックに関わる部分がほぼない状況です。そのため今回はビジネスロジックのみで単体テストを実装するようにしました。

 

Google Apiのテスト

 

前回の記事で触れましたが、きんたくんはデータの永続化にGoogle Sheetを使用しています。そのためCRUDにあたる機能を自前で実装しています。(辛い)データの書き込みやシートの新規作成などはデータの安全な管理に直結するためテスト対象としたいところです。

 

機密情報の扱い

 

Google APIのテストを実行する場合、クライアントIDとクライアントシークレットが保存されたOAuth認証用のファイルを利用することでGitHub Actionsのようなブラウザを持たない仮想マシンでのAPIテストを実施できます。ただし、当然リポジトリに機密情報となるクライアントIDとクライアントシークレットを含めることはできないため、GitHub Actions内でこれら機密情報を安全に利用する方法が必要です。

 

GitHub Actionsのお作法

 

単純に考えればリポジトリのSecretsにクライアントIDとクライアントシークレットを保存し、下記のようにファイルを生成してしまえばいいように思います。

 

echo -n "${PASSWORD}" > password.txt

ただ、これは公式のお墨付きで避けるべき方法の一つです。

Avoid passing secrets between processes from the command line, whenever possible. Command-line processes may be visible to other users (using the ps command)  or  captured by security audit events. To help protect secrets, consider using environment variables, STDIN, or other mechanisms supported by the target process.

 

引数に "${PASSWORD}" などを使うとシェルが展開してからプロセスを開始するので、ps などで見えてしまうからというのが理由のようです。

 

$ echo "${PASSWORD}"
# SDf%@m38x"Tc5N4r
$ echo "sleep 10" > long.sh
$ bash ./long.sh "${PASSWORD}" &
# [1] 2364315
$ pgrep -af long
# 2364315 bash ./long.sh SDf%@m38x"Tc5N4r

GitHub Actionsでの機密情報に関するあれこれはこの記事で詳しく検証されていました。

Actionを利用する

 

今回のようなSecretsに保存した変数を利用して認証用のファイルを生成するために利用できるActionが提供されています。

 

この用に指定することでプロセス内でSecretsに登録したデータを安全に任意のファイル形式に変換することが可能です。

 

# .github/workflows/var-substitution.yml
on: [push]
name: variable substitution in json, xml, and yml files

jobs:
  build:
    runs-on: windows-latest
    steps:
    - uses: actions/checkout@v3

    - uses: keeroll/variable-substitution@v2.0.2
      with:
        files: 'Application/*.json, Application/*.yaml, ./Application/SampleWebApplication/We*.config'
      env:
        Var1: "value1"
        Var2.key1: "value2"
        SECRET: ${{ secrets.SOME_SECRET }}

 

実際のテスト

 

Google APIのテストのサンプルとしては以下のような感じになります。beforeAllafterAllで任意のテストシートの作成、破棄をすることで不要なファイルをDriveに残すことなくテストを実施可能です。

 

// テスト用シート生成
beforeAll(async () => {
  googleAuth = (await authorize()) as OAuth2Client
  spreadSheetId = await createNewSpreadSheet(googleAuth, 'sample')
})

// テスト用シート削除
afterAll(async () => {
  if (!spreadSheetId) return
  await deleteFileOrFolder(googleAuth, spreadSheetId)
})

describe('シート操作', async () => {
  it('データ更新', async () => {
    expect(googleAuth).not.toBeUndefined()
    expect(spreadSheetId).not.toBeUndefined()
    if (!googleAuth || !spreadSheetId) return

    await updateValues(googleAuth, spreadSheetId, 'A1', [['test-data']], 'RAW')
    const values = await collectValues(googleAuth, spreadSheetId, 'A1')
    assert.notEqual(values, undefined)
    assert.notEqual(values, null)
    if (!values) return
    assert.equal(values.length, 1)
    assert.equal(values[0].length, 1)
    assert.equal(values[0][0], 'test-data')
  })
})

 

最後に


今回はテストについて紹介しました。きんたくんでは自動でのe2eテストは実施していません。この記事を書くに当たって多少調査しましたが、Discord Botに関しては正直メジャーなものは無い気がします。きんたくんはSapphireというNode.js環境のFWを採用しているため、採用できる候補としてはcordeというOSSがあるようです。

 

次回はお待ちかね?のCopilotについてです。有名なTech系の企業の方がまとめている資料などを交えてきんたくんの実装で利用した所感をお伝えしたいと思います。

Recruitment information

採用情報

menu