Rspec の Request Spec で Stripe-Signature ヘッダを含めたテストをする


Stripe を利用していてwebhookを使ったStripe 上で発生したイベントに対して処理を行うというのはよくあると思います。

Stripe ではイベントが第三者ではなく Stripe によって送信されたリクエストであることを確認するために Signature の検証ができるようになっています。
Check the webhook signatures

基本的にはwebhook でイベントを受け取るエンドポイントで Signature の確認処理を実行すると思いますが、テストでは自前で Signature を作成する必要があります。

ここでは公式のStripe API の Ruby Gems の stripe-ruby と Rspec でテストする前提での例を説明します。

Stripe-Signature の作成

stripe-ruby では Signature の計算とStripe-Signature ヘッダの文字列を作成するメソッドが提供されています。
これらを使ってStripe-Signature ヘッダを含む POST リクエストをするメソッドを用意します。

spec/support/stripe_webhook_helpers.rb
# frozen_string_literal: true

module StripeEventHelpers
  def post_with_stripe_signature(path, **options)
    post(
      path,
      headers: {
        'Stripe-Signature': generate_stripe_event_signature(options[:params])
      },
      **options
    )
  end

  private

  def generate_stripe_event_signature(payload)
    time = Time.now
    secret = ENV['STRIPE_WEBHOOK_SECRET']
    signature = Stripe::Webhook::Signature.compute_signature(time, payload, secret)
    Stripe::Webhook::Signature.generate_header(
      time,
      signature
    )
  end
end

Stripe の イベントを受け取るエンドポイントのテスト内での post は基本的に Stripe-Signature ヘッダを含めることになるので post をオーバーライドするのもありだと思います。

spec/support/stripe_webhook_helpers.rb
-  def post_with_stripe_signature(path, **options)
+  def post(path, **options)
-    post(
+    super(
      path,
      headers: {
        'Stripe-Signature': generate_stripe_event_signature(options[:params])
      },
      **options
    )
  end

ヘルパーをインポートしてヘッダーを含むリクエストをする

テスト用の Stripe::Event オブジェクトを作成するのに stripe-ruby-mock を使います。
Ref: stripe-ruby-mock/stripe-ruby-mock: A mocking library for testing stripe ruby

spec/requests/stripe_events_spec.rb
describe StripeEventsController do
  include StripeEventHelpers

  before { StripeMock.start }
  after { StripeMock.stop }

  let(:event) { StripeMock.mock_webhook_event('customer.created') }

  it 'returns ok' do 
    post_with_stripe_signature stripe_events_path, params: event.to_json
    expect(response).to have_http_status(200)
  end
end

Refs