|
38 | 38 | let(:submitter) { instance_double(Subscriptions::PardotFormHandlerSubmitter) } |
39 | 39 |
|
40 | 40 | before do |
| 41 | + allow(Rails.configuration.x.cloudflare_turnstile).to receive(:enabled).and_return(false) |
41 | 42 | allow(Subscriptions::PardotFormHandlerSubmitter).to receive(:new).and_return(submitter) |
42 | 43 | allow(submitter).to receive(:call).and_return(submitter_result_success) |
| 44 | + allow(Sentry).to receive(:capture_exception) |
43 | 45 | end |
44 | 46 |
|
45 | 47 | it 'returns success for a valid payload' do |
|
113 | 115 | end |
114 | 116 |
|
115 | 117 | describe 'Cloudflare Turnstile integration' do |
116 | | - let(:request_url) { 'https://challenges.cloudflare.com/turnstile/v0/siteverify' } |
| 118 | + let(:request_url) { Api::SubscriptionsController::API_URL } |
| 119 | + let(:turnstile_request_body) { { 'secret' => 'test-secret', 'response' => 'test-token', 'remoteip' => '127.0.0.1' } } |
| 120 | + let(:post_params) { payload } |
117 | 121 |
|
118 | 122 | before do |
119 | 123 | allow(Rails.configuration.x.cloudflare_turnstile).to receive_messages( |
|
122 | 126 | ) |
123 | 127 | end |
124 | 128 |
|
125 | | - it 'returns 422 when turnstile token is missing' do |
126 | | - post(path, params: payload.deep_merge(subscription: { turnstile_token: '' }), as: :json) |
| 129 | + shared_examples 'turnstile verification failure' do |
| 130 | + it 'returns 422 with turnstile_verification_failed error code' do |
| 131 | + post(path, params: post_params, as: :json) |
127 | 132 |
|
128 | | - expect(response).to have_http_status(:unprocessable_content) |
129 | | - expect(response.parsed_body['error_code']).to eq('turnstile_verification_failed') |
| 133 | + expect(response).to have_http_status(:unprocessable_content) |
| 134 | + expect(response.parsed_body['error_code']).to eq('turnstile_verification_failed') |
| 135 | + end |
130 | 136 | end |
131 | 137 |
|
132 | | - it 'returns 422 when turnstile verification fails' do |
133 | | - stub_request(:post, request_url) |
134 | | - .with( |
135 | | - body: hash_including( |
136 | | - secret: 'test-secret', |
137 | | - response: 'test-token' |
138 | | - ) |
139 | | - ) |
140 | | - .to_return(status: 200, body: { success: false }.to_json) |
| 138 | + shared_examples 'fail-open turnstile response' do |
| 139 | + it 'allows the request through' do |
| 140 | + post(path, params: payload, as: :json) |
141 | 141 |
|
142 | | - post(path, params: payload, as: :json) |
| 142 | + expect(response).to have_http_status(:ok) |
| 143 | + expect(response.parsed_body['ok']).to be(true) |
| 144 | + end |
| 145 | + end |
| 146 | + |
| 147 | + context 'when turnstile token is missing' do |
| 148 | + let(:post_params) { payload.deep_merge(subscription: { turnstile_token: '' }) } |
| 149 | + |
| 150 | + it_behaves_like 'turnstile verification failure' |
| 151 | + end |
| 152 | + |
| 153 | + context 'when turnstile verification fails' do |
| 154 | + before do |
| 155 | + stub_request(:post, request_url) |
| 156 | + .with(body: turnstile_request_body) |
| 157 | + .to_return(status: 200, body: { success: false }.to_json) |
| 158 | + end |
| 159 | + |
| 160 | + it_behaves_like 'turnstile verification failure' |
| 161 | + end |
143 | 162 |
|
144 | | - expect(response).to have_http_status(:unprocessable_content) |
145 | | - expect(response.parsed_body['error_code']).to eq('turnstile_verification_failed') |
| 163 | + context 'when turnstile verification times out' do |
| 164 | + before do |
| 165 | + stub_request(:post, request_url) |
| 166 | + .with(body: turnstile_request_body) |
| 167 | + .to_timeout |
| 168 | + end |
| 169 | + |
| 170 | + it 'allows the request through and reports to Sentry' do |
| 171 | + post(path, params: payload, as: :json) |
| 172 | + |
| 173 | + expect(response).to have_http_status(:ok) |
| 174 | + expect(response.parsed_body['ok']).to be(true) |
| 175 | + expect(Sentry).to have_received(:capture_exception).with(be_a(Faraday::Error)) |
| 176 | + end |
146 | 177 | end |
147 | 178 |
|
148 | | - it 'allows request through if turnstile verification is unavailable' do |
149 | | - stub_request(:post, request_url) |
150 | | - .with( |
151 | | - body: hash_including( |
152 | | - secret: 'test-secret', |
153 | | - response: 'test-token' |
154 | | - ) |
155 | | - ) |
156 | | - .to_timeout |
| 179 | + context 'when Cloudflare returns a server error' do |
| 180 | + before do |
| 181 | + stub_request(:post, request_url) |
| 182 | + .with(body: turnstile_request_body) |
| 183 | + .to_return(status: 500, body: 'Internal Server Error') |
| 184 | + end |
| 185 | + |
| 186 | + it_behaves_like 'fail-open turnstile response' |
| 187 | + end |
| 188 | + |
| 189 | + context 'when Cloudflare returns malformed JSON' do |
| 190 | + before do |
| 191 | + stub_request(:post, request_url) |
| 192 | + .with(body: turnstile_request_body) |
| 193 | + .to_return(status: 200, body: 'not-json') |
| 194 | + end |
| 195 | + |
| 196 | + it_behaves_like 'fail-open turnstile response' |
| 197 | + end |
157 | 198 |
|
158 | | - post(path, params: payload, as: :json) |
| 199 | + context 'when turnstile token is valid' do |
| 200 | + before do |
| 201 | + stub_request(:post, request_url) |
| 202 | + .with(body: turnstile_request_body) |
| 203 | + .to_return(status: 200, body: { success: true }.to_json) |
| 204 | + end |
159 | 205 |
|
160 | | - expect(response).to have_http_status(:ok) |
161 | | - expect(response.parsed_body['ok']).to be(true) |
| 206 | + it_behaves_like 'fail-open turnstile response' |
162 | 207 | end |
163 | 208 | end |
164 | 209 | end |
|
0 commit comments