👀

VoIP Push 通知の自動着信テスト始めました

2023/06/30に公開

株式会社 IVRy (アイブリー) 社員番号 7番 エンジニアのボルドーです。

今回は弊社にて実施している VoIP Push 通知 の自動着信テスト方法について紹介したいと思います。

実施に至った経緯

以前から実施したいという話は出ていたのですが、他の業務に追われ優先度を上げられずにいました。
そんな中、先月の日曜日、半日に渡り VoIP Push 通知が届かない障害が発生[1]してしまったため、一刻も早く取り組もうと優先度をぐっと引き上げました。

VoIP Push 通知確認の難しい点

とはいえ、通知が適切に届いているかどうかを機械的に確認することは容易ではありません[2]

一般的に

  1. 自社バックエンドまたは連携サービスから APNs へ通知送信リクエスト
  2. APNs 通知実行
  3. 端末から自社バックエンドに API リクエスト

という流れになると思うので、通知送信時の情報を控えておいてその通知先端末からの API リクエストログを監視することで問題なく通知が届いていそうだということが確認できます。
しかしながら、通知を送信するシステムと Web API を提供するシステムは多くのケースで切り離されているのではないかと思います。その場合データの扱いが難しいログを複数箇所から拾ってきて突合することになります。

方針

綺麗にやろうとすると難しいのですが、今回は検知さえできれば格好などどうでもいいというスタンスで再検討しました。
つまり、不格好でもいいからとにかく「通知が届かない不具合が発生した場合に社内で気付ける仕組みを作ろう」 と元々検討していたような仰々しさを取っ払ってアプリ開発者(私)だけでできることはないか?とシンプルに考えました[5]

その結果、アプリを少し改造して通知が来た時にログを吐くようにしておき、架電スクリプトを組んで発信と受信それぞれのログを突合する方向でいくことにしました。
アプリを改造する場合一番怖いのが誤ってストアに公開してしまうことなので、TestFlight 経由の配信はせず AdHoc 配信する等 誤操作で公開されることがないようにするための方針も決めました[6]

ログを吐く場所については深く考えず Slack が簡単かつセキュアで利便性が高そうだったので採用しました。

実装

最終的にこのような形で 自動架電 → VoIP Push 通知受信 → ログ確認 を1セットとして指定時間間隔で定期実行されるようにしました。

アプリ側

通知を受信したタイミングで Slack に投稿するようにカスタマイズ後[7]、実機にインストールして通知を受け取る設定を済ませておきます。

AdHoc 配信が前提とはいえ、万が一を考慮して Incoming Webhooks を使って投稿します。

const postToSlackIfNeeded = async (message: string) => {
  if (!SLACK_WEBHOOK_URL) return;
  return fetch(SLACK_WEBHOOK_URL, {
    method: 'POST',
    body: JSON.stringify({
      type: 'mrkdwn',
      text: makeMarkdown(message),
    }),
  }).catch((e) => console.error(e));
}

架電 + ログ確認用スクリプト

以下のことを行うスクリプトを組んで crontab で定期実行します。

  1. テストを開始する旨を Slack に投稿
    • この時 timestamp を保持しておく
  2. 通知発火用の処理として Twilio を使って架電
    • 通知を受け取ったアプリに上述の対応が加えられていれば Slack に通知が届いた旨の投稿が行われる
  3. Slack からテスト開始以降の投稿リストを取得
  4. 1つずつ確認してアプリからの投稿を探す
  5. 確認の結果を明示
    • 成功の場合は目視でわかるように確認できた投稿に ✅ リアクションを追加して終了
    • 指定回数リトライしてそれでも失敗の場合は確認できなかった旨の投稿をした後に 🚨 リアクションを追加して終了
      • Reacji Channeler を使用して 🚨 が付与された投稿を障害チャンネルに流す

1つのパッケージで行うため、このスクリプト用の Slack トークンは channels:history, chat:write.customize[8], reactions:write 辺りの権限が必要です。

実行環境について

今回は Amazon Lightsail を利用しています。
デプロイ不要である程度柔軟に対応できるように setting.json を用意してリトライ回数や文言等ちょっとした変更ができるようにしました。

setting.json
{
  "appName": "AppAutoTest",
  "checker": "slack",
  "maxRetries": 1,
  "retryInterval": 60,
  "retryMessage": "⚠️ ログが確認できなかったため、{retryInterval}秒後に再実行します({retryCount}/{maxRetries})",
  "callTimeout": 20,
  "confirmLogRegex": "【AutoTest for (ios|android)】([0-9]{3,4}-?){3} ?から着信がありました。",
  "startMessage": "=== 自動架電を開始します ===",
  "endMessage": "=== 自動架電を終了します ===",
  "alertMessage": "🚨 アプリからの着電ログが確認できませんでした。状況を確認してください。",
  "slack": {
    "loggingChannelId": "XXXX",
    "goodReaction": "white_check_mark",
    "alertReaction": "alert"
  }
}

1. テスト開始通知

テストが行われていることを眺めつつログを取得する時のカーソルとして使用したいので開始通知を Slack に投げます。
投稿するだけなので省略しますが、chat.postMessage を利用してレスポンスから ts を保持しておきます。

2. 通知発火用の処理として Twilio を使って架電

弊社の場合はエンドユーザー様からの電話を弊社サービス IVRy が受けて、システムの案内に基づいてエンドユーザー様がプッシュ分岐を行った末のアクションが「アプリへの電話転送」だった場合にアプリへと VoIP Push 通知が送信されます。そのため、コードから架電できるサービスとして Twilio を利用して架電します。

Twilio Node.js SDK を用いる場合のドキュメントはこちら にありまして、このサンプルコード の通りに記述するだけで簡単に架電することができます。

3. テスト開始以降の投稿リストを取得

テスト開始時の timestamp を conversations.history の oldest に指定することで確実にテスト開始以降に投稿されたログだけを取得することができます。

oldest string·Optional
Only messages after this Unix timestamp will be included in results.

Default 0
Example 1234567890.123456

4. 3で取得した投稿リストからアプリからの投稿を検索

アプリからの投稿で使用している Incomning Webhook が専用の BOT として用意されている場合は bot_id または user で探す方法が簡単です。そうでない場合でも簡単な正規表現等を用いて判定ロジックを組んでおけば問題ありません。

5. 結果をリアクションで明示

確認ができた時 / できなかった時 状況に応じて投稿に対してリアクションを追加します。リアクションの追加は reactions.add で行うことができます。
問題がありそうな際のエスカレーションに関して、BOT の参加チャンネルを 1チャンネルだけにしたかったため Reacji Channeler を使用して障害用チャンネルに流すようにしています。

場合によっては chat:write.public 権限を付与して参加していないパブリックチャンネルに投稿するようにしても良いかもしれません。


VoIP Push 通知の自動着信テスト方法の紹介は以上となります。
ご覧いただきありがとうございました。

We are hiring!!

最後に、IVRy では一緒に働く仲間を絶賛募集中です。
今の所 順調に成長してきていますが、今後の更なる成長のためには圧倒的に仲間が不足しています。皆さまのご応募お待ちしております!

カジュアルに話を聞きたいという方は私の Meety から面談を申し込んでいただければ色々お話します。

代表の奥西とも話せます!

脚注
  1. IVRy のシステム上の問題ではなく証明書の期限切れが原因だったのですが、自動検知の対応を先延ばしにした結果 社内で検知できずお客様からの問い合わせで発覚するという由々しき事態でした ↩︎

  2. 難しさをうまく説明できる自信がないです。フィードバックをいただけると嬉しいです。 ↩︎

  3. Apple 製品のケースを記載していますが Android 端末でも同様かと思います。 ↩︎

  4. APNs のドキュメント を見た限りではエラー毎にハンドリングできそうです。ただし、配信システムを自前で用意していない場合は検知に限界がありそうです ↩︎

  5. 私は IoT に興味がある ので E2Eテスト として実機を Webカメラ でキャプチャして画像認識する等も楽しそうだなとは思ったのですが、ちょっと工数が膨らみそうで諦めました ↩︎

  6. AdHoc 配信でも Production 環境と同じ証明書を使用できるため、環境差異はありません ↩︎

  7. 細かいことですが、アプリ名を変える、ビルド後開発環境をクリーンする等した方が良いと思います。また、通知が受信できたという時点でネットワーク環境はあるものの、Webhook URL を無効にした等の理由で POST に失敗することは考えられます。そうした場合の検知については今後の課題としています。 ↩︎

  8. chat:write でも良いのですが、開発環境で同じチャンネルに投稿して確認する場合投稿者の名前を変更できた方が良いため customize を使用しています。 ↩︎

IVRyテックブログ

Discussion