fcm 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +16 -1
- data/.hound.yml +2 -0
- data/.rubocop.yml +67 -0
- data/Gemfile +8 -5
- data/README.md +40 -0
- data/Rakefile +9 -7
- data/fcm.gemspec +10 -8
- data/lib/fcm.rb +125 -57
- data/spec/fcm_spec.rb +247 -161
- data/spec/spec_helper.rb +7 -5
- metadata +33 -5
data/spec/fcm_spec.rb
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "spec_helper"
|
|
2
|
-
require
|
|
4
|
+
require "tempfile"
|
|
3
5
|
|
|
4
6
|
describe FCM do
|
|
5
|
-
let(:project_name) {
|
|
6
|
-
let(:json_key_path) {
|
|
7
|
-
let(:client) {
|
|
7
|
+
let(:project_name) { "test-project" }
|
|
8
|
+
let(:json_key_path) { "path/to/json/key.json" }
|
|
9
|
+
let(:client) { described_class.new(json_key_path) }
|
|
8
10
|
|
|
9
11
|
let(:mock_token) { "access_token" }
|
|
10
12
|
let(:mock_headers) do
|
|
11
13
|
{
|
|
12
14
|
"Content-Type" => "application/json",
|
|
13
|
-
"Authorization" => "Bearer #{mock_token}"
|
|
15
|
+
"Authorization" => "Bearer #{mock_token}"
|
|
14
16
|
}
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
let(:client_email) do
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
"83315528762cf7e0-7bbcc3aad87e0083391bc7f234d487" \
|
|
21
|
+
"c8@developer.gserviceaccount.com"
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
let(:client_x509_cert_url) do
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
"https://www.googleapis.com/robot/v1/metadata/x509/" \
|
|
26
|
+
'fd6b61037dd2bb8585527679" + "-7bbcc3aad87e0083391b' \
|
|
27
|
+
"c7f234d487c8%40developer.gserviceaccount.com"
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
let(:large_file_name) do
|
|
29
|
-
Array.new(1021) {
|
|
31
|
+
"#{Array.new(1021) { "a" }.join}.txt"
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
let(:creds_error) do
|
|
@@ -35,17 +37,17 @@ describe FCM do
|
|
|
35
37
|
|
|
36
38
|
let(:json_credentials) do
|
|
37
39
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
type: "service_account",
|
|
41
|
+
project_id: "example",
|
|
42
|
+
private_key_id: "c09c4593eee53707ca9f4208fbd6fe72b29fc7ab",
|
|
43
|
+
private_key: OpenSSL::PKey::RSA.new(2048).to_s,
|
|
44
|
+
client_email: client_email,
|
|
45
|
+
client_id: "acedc3c0a63b3562376386f0.apps.googleusercontent.com",
|
|
46
|
+
auth_uri: "https://accounts.google.com/o/oauth2/auth",
|
|
47
|
+
token_uri: "https://oauth2.googleapis.com/token",
|
|
48
|
+
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
|
|
49
|
+
client_x509_cert_url: client_x509_cert_url,
|
|
50
|
+
universe_domain: "googleapis.com"
|
|
49
51
|
}.to_json
|
|
50
52
|
end
|
|
51
53
|
|
|
@@ -54,32 +56,32 @@ describe FCM do
|
|
|
54
56
|
|
|
55
57
|
# Mock the Google::Auth::ServiceAccountCredentials
|
|
56
58
|
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds)
|
|
57
|
-
.and_return(double(fetch_access_token!: {
|
|
59
|
+
.and_return(double(fetch_access_token!: { "access_token" => mock_token }))
|
|
58
60
|
end
|
|
59
61
|
|
|
60
|
-
it
|
|
62
|
+
it "initializes" do
|
|
61
63
|
expect { client }.not_to raise_error
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
describe "credentials path" do
|
|
65
|
-
it
|
|
66
|
-
fcm =
|
|
67
|
+
it "can be a path to a file" do
|
|
68
|
+
fcm = described_class.new("README.md")
|
|
67
69
|
expect(fcm.__send__(:json_key).class).to eq(File)
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
it
|
|
72
|
+
it "raises an error when passed a large path" do
|
|
71
73
|
expect do
|
|
72
|
-
|
|
74
|
+
described_class.new(large_file_name).__send__(:json_key)
|
|
73
75
|
end.to raise_error(creds_error)
|
|
74
76
|
end
|
|
75
77
|
|
|
76
|
-
it
|
|
77
|
-
fcm =
|
|
78
|
+
it "can be an IO object" do
|
|
79
|
+
fcm = described_class.new(StringIO.new("hey"))
|
|
78
80
|
expect(fcm.__send__(:json_key).class).to eq(StringIO)
|
|
79
81
|
|
|
80
|
-
temp_file = Tempfile.new(
|
|
82
|
+
temp_file = Tempfile.new("hello_world.json")
|
|
81
83
|
temp_file.write(json_credentials)
|
|
82
|
-
fcm_with_temp_file =
|
|
84
|
+
fcm_with_temp_file = described_class.new(temp_file)
|
|
83
85
|
|
|
84
86
|
expect do
|
|
85
87
|
fcm_with_temp_file
|
|
@@ -88,49 +90,49 @@ describe FCM do
|
|
|
88
90
|
temp_file.unlink
|
|
89
91
|
end
|
|
90
92
|
|
|
91
|
-
it
|
|
93
|
+
it "raises an error when passed a non IO-like object" do
|
|
92
94
|
expect do
|
|
93
|
-
|
|
94
|
-
end.to raise_error(creds_error,
|
|
95
|
-
|
|
95
|
+
described_class.new(nil, "", {}).__send__(:json_key)
|
|
96
|
+
end.to raise_error(creds_error, "credentials must be " \
|
|
97
|
+
"an IO-like object or path. You passed nil.")
|
|
96
98
|
|
|
97
99
|
expect do
|
|
98
|
-
|
|
99
|
-
end.to raise_error(creds_error,
|
|
100
|
-
|
|
100
|
+
described_class.new(json_credentials, "", {}).__send__(:json_key)
|
|
101
|
+
end.to raise_error(creds_error, "credentials must be " \
|
|
102
|
+
"an IO-like object or path. You passed a String.")
|
|
101
103
|
|
|
102
104
|
expect do
|
|
103
|
-
|
|
104
|
-
end.to raise_error(creds_error,
|
|
105
|
-
|
|
105
|
+
described_class.new({}, "", {}).__send__(:json_key)
|
|
106
|
+
end.to raise_error(creds_error, "credentials must be " \
|
|
107
|
+
"an IO-like object or path. You passed a Hash.")
|
|
106
108
|
end
|
|
107
109
|
|
|
108
|
-
it
|
|
109
|
-
fcm =
|
|
110
|
+
it "raises an error when passed a non-existent credentials file path" do
|
|
111
|
+
fcm = described_class.new("spec/fake_credentials.json", "", {})
|
|
110
112
|
expect { fcm.__send__(:json_key) }.to raise_error(creds_error)
|
|
111
113
|
end
|
|
112
114
|
|
|
113
|
-
it
|
|
114
|
-
fcm =
|
|
115
|
+
it "raises an error when passed a string of a file that does not exist" do
|
|
116
|
+
fcm = described_class.new("example.txt", "", {})
|
|
115
117
|
expect { fcm.__send__(:json_key) }.to raise_error(creds_error)
|
|
116
118
|
end
|
|
117
119
|
end
|
|
118
120
|
|
|
119
121
|
describe "#send_v1 or #send_notification_v1" do
|
|
120
|
-
let(:client) {
|
|
122
|
+
let(:client) { described_class.new(json_key_path, project_name) }
|
|
121
123
|
|
|
122
124
|
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
123
125
|
let(:status_code) { 200 }
|
|
124
126
|
|
|
125
127
|
let(:stub_fcm_send_v1_request) do
|
|
126
128
|
stub_request(:post, uri).with(
|
|
127
|
-
body: {
|
|
129
|
+
body: { "message" => send_v1_params }.to_json,
|
|
128
130
|
headers: mock_headers
|
|
129
131
|
).to_return(
|
|
130
132
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
|
131
133
|
body: "{}",
|
|
132
134
|
headers: {},
|
|
133
|
-
status: status_code
|
|
135
|
+
status: status_code
|
|
134
136
|
)
|
|
135
137
|
end
|
|
136
138
|
|
|
@@ -138,222 +140,222 @@ describe FCM do
|
|
|
138
140
|
stub_fcm_send_v1_request
|
|
139
141
|
end
|
|
140
142
|
|
|
141
|
-
shared_examples
|
|
142
|
-
it
|
|
143
|
+
shared_examples "succesfuly send notification" do
|
|
144
|
+
it "sends notification of HTTP V1 using POST to FCM server" do
|
|
143
145
|
client.send_v1(send_v1_params).should eq(
|
|
144
|
-
response:
|
|
146
|
+
response: "success", body: "{}", headers: {}, status_code: 200
|
|
145
147
|
)
|
|
146
148
|
stub_fcm_send_v1_request.should have_been_made.times(1)
|
|
147
149
|
end
|
|
148
150
|
end
|
|
149
151
|
|
|
150
|
-
describe
|
|
151
|
-
let(:token) {
|
|
152
|
+
describe "send to token" do
|
|
153
|
+
let(:token) { "4sdsx" }
|
|
152
154
|
let(:send_v1_params) do
|
|
153
155
|
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
"token" => token,
|
|
157
|
+
"notification" => {
|
|
158
|
+
"title" => "Breaking News",
|
|
159
|
+
"body" => "New news story available."
|
|
158
160
|
},
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
"data" => {
|
|
162
|
+
"story_id" => "story_12345"
|
|
161
163
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
"android" => {
|
|
165
|
+
"notification" => {
|
|
166
|
+
"click_action" => "TOP_STORY_ACTIVITY",
|
|
167
|
+
"body" => "Check out the Top Story"
|
|
166
168
|
}
|
|
167
169
|
},
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
"apns" => {
|
|
171
|
+
"payload" => {
|
|
172
|
+
"aps" => {
|
|
173
|
+
"category" => "NEW_MESSAGE_CATEGORY"
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
end
|
|
177
179
|
|
|
178
|
-
|
|
180
|
+
it_behaves_like "succesfuly send notification"
|
|
179
181
|
|
|
180
|
-
it
|
|
182
|
+
it "includes all the response" do
|
|
181
183
|
response = client.send_v1(send_v1_params)
|
|
182
184
|
expect(response[:status_code]).to eq(status_code)
|
|
183
|
-
expect(response[:response]).to eq(
|
|
184
|
-
expect(response[:body]).to eq(
|
|
185
|
+
expect(response[:response]).to eq("success")
|
|
186
|
+
expect(response[:body]).to eq("{}")
|
|
185
187
|
expect(response[:headers]).to eq({})
|
|
186
188
|
expect(response[:canonical_ids]).to be_nil
|
|
187
189
|
expect(response[:not_registered_ids]).to be_nil
|
|
188
190
|
end
|
|
189
191
|
end
|
|
190
192
|
|
|
191
|
-
describe
|
|
192
|
-
let(:tokens) { [
|
|
193
|
+
describe "send to multiple tokens" do
|
|
194
|
+
let(:tokens) { %w[4sdsx 4sdsy] }
|
|
193
195
|
let(:send_v1_params) do
|
|
194
196
|
{
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
"token" => tokens,
|
|
198
|
+
"notification" => {
|
|
199
|
+
"title" => "Breaking News",
|
|
200
|
+
"body" => "New news story available."
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
end
|
|
202
204
|
|
|
203
|
-
|
|
205
|
+
it_behaves_like "succesfuly send notification"
|
|
204
206
|
end
|
|
205
207
|
|
|
206
|
-
describe
|
|
207
|
-
let(:topic) {
|
|
208
|
+
describe "send to topic" do
|
|
209
|
+
let(:topic) { "news" }
|
|
208
210
|
let(:send_v1_params) do
|
|
209
211
|
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
"topic" => topic,
|
|
213
|
+
"notification" => {
|
|
214
|
+
"title" => "Breaking News",
|
|
215
|
+
"body" => "New news story available."
|
|
214
216
|
}
|
|
215
217
|
}
|
|
216
218
|
end
|
|
217
219
|
|
|
218
|
-
|
|
220
|
+
it_behaves_like "succesfuly send notification"
|
|
219
221
|
|
|
220
|
-
context
|
|
221
|
-
let(:topic) {
|
|
222
|
+
context "when topic is invalid" do
|
|
223
|
+
let(:topic) { "/topics/news$" }
|
|
222
224
|
|
|
223
|
-
it
|
|
225
|
+
it "raises error" do
|
|
224
226
|
stub_fcm_send_v1_request.should_not have_been_requested
|
|
225
227
|
end
|
|
226
228
|
end
|
|
227
229
|
end
|
|
228
230
|
|
|
229
|
-
describe
|
|
231
|
+
describe "send to condition" do
|
|
230
232
|
let(:condition) { "'foo' in topics" }
|
|
231
233
|
let(:send_v1_params) do
|
|
232
234
|
{
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
235
|
+
"condition" => condition,
|
|
236
|
+
"notification" => {
|
|
237
|
+
"title" => "Breaking News",
|
|
238
|
+
"body" => "New news story available."
|
|
239
|
+
}
|
|
238
240
|
}
|
|
239
241
|
end
|
|
240
242
|
|
|
241
|
-
|
|
243
|
+
it_behaves_like "succesfuly send notification"
|
|
242
244
|
end
|
|
243
245
|
|
|
244
|
-
describe
|
|
245
|
-
let(:notification_key) {
|
|
246
|
+
describe "send to notification_key" do
|
|
247
|
+
let(:notification_key) { "notification_key" }
|
|
246
248
|
let(:send_v1_params) do
|
|
247
249
|
{
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
"notification_key" => notification_key,
|
|
251
|
+
"notification" => {
|
|
252
|
+
"title" => "Breaking News",
|
|
253
|
+
"body" => "New news story available."
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
end
|
|
255
257
|
|
|
256
|
-
|
|
258
|
+
it_behaves_like "succesfuly send notification"
|
|
257
259
|
end
|
|
258
260
|
|
|
259
|
-
context
|
|
260
|
-
let(:project_name) {
|
|
261
|
+
context "when project_name is empty" do
|
|
262
|
+
let(:project_name) { "" }
|
|
261
263
|
let(:send_v1_params) do
|
|
262
264
|
{
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
"token" => "4sdsx",
|
|
266
|
+
"notification" => {
|
|
267
|
+
"title" => "Breaking News",
|
|
268
|
+
"body" => "New news story available."
|
|
267
269
|
}
|
|
268
270
|
}
|
|
269
271
|
end
|
|
270
272
|
|
|
271
|
-
it
|
|
273
|
+
it "does not send notification" do
|
|
272
274
|
client.send_v1(send_v1_params)
|
|
273
275
|
stub_fcm_send_v1_request.should_not have_been_requested
|
|
274
276
|
end
|
|
275
277
|
end
|
|
276
278
|
|
|
277
|
-
describe
|
|
279
|
+
describe "error handling" do
|
|
278
280
|
let(:send_v1_params) do
|
|
279
281
|
{
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
"token" => "4sdsx",
|
|
283
|
+
"notification" => {
|
|
284
|
+
"title" => "Breaking News",
|
|
285
|
+
"body" => "New news story available."
|
|
284
286
|
}
|
|
285
287
|
}
|
|
286
288
|
end
|
|
287
289
|
|
|
288
|
-
context
|
|
290
|
+
context "when status_code is 400" do
|
|
289
291
|
let(:status_code) { 400 }
|
|
290
292
|
|
|
291
|
-
it
|
|
293
|
+
it "raises error" do
|
|
292
294
|
response = client.send_v1(send_v1_params)
|
|
293
295
|
expect(response[:status_code]).to eq(status_code)
|
|
294
|
-
expect(response[:response]).to include(
|
|
296
|
+
expect(response[:response]).to include("Only applies for JSON requests")
|
|
295
297
|
end
|
|
296
298
|
end
|
|
297
299
|
|
|
298
|
-
context
|
|
300
|
+
context "when status_code is 401" do
|
|
299
301
|
let(:status_code) { 401 }
|
|
300
302
|
|
|
301
|
-
it
|
|
303
|
+
it "raises error" do
|
|
302
304
|
response = client.send_v1(send_v1_params)
|
|
303
305
|
expect(response[:status_code]).to eq(status_code)
|
|
304
|
-
expect(response[:response]).to include(
|
|
306
|
+
expect(response[:response]).to include("There was an error authenticating")
|
|
305
307
|
end
|
|
306
308
|
end
|
|
307
309
|
|
|
308
|
-
context
|
|
310
|
+
context "when status_code is 500" do
|
|
309
311
|
let(:status_code) { 500 }
|
|
310
312
|
|
|
311
|
-
it
|
|
313
|
+
it "raises error" do
|
|
312
314
|
response = client.send_v1(send_v1_params)
|
|
313
315
|
expect(response[:status_code]).to eq(status_code)
|
|
314
|
-
expect(response[:response]).to include(
|
|
316
|
+
expect(response[:response]).to include("There was an internal error")
|
|
315
317
|
end
|
|
316
318
|
end
|
|
317
319
|
|
|
318
|
-
context
|
|
320
|
+
context "when status_code is 503" do
|
|
319
321
|
let(:status_code) { 503 }
|
|
320
322
|
|
|
321
|
-
it
|
|
323
|
+
it "raises error" do
|
|
322
324
|
response = client.send_v1(send_v1_params)
|
|
323
325
|
expect(response[:status_code]).to eq(status_code)
|
|
324
|
-
expect(response[:response]).to include(
|
|
326
|
+
expect(response[:response]).to include("Server is temporarily unavailable")
|
|
325
327
|
end
|
|
326
328
|
end
|
|
327
329
|
end
|
|
328
330
|
end
|
|
329
331
|
|
|
330
|
-
describe
|
|
331
|
-
let(:client) {
|
|
332
|
+
describe "#send_to_topic" do
|
|
333
|
+
let(:client) { described_class.new(json_key_path, project_name) }
|
|
332
334
|
|
|
333
335
|
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
334
336
|
|
|
335
|
-
let(:topic) {
|
|
337
|
+
let(:topic) { "news" }
|
|
336
338
|
let(:params) do
|
|
337
339
|
{
|
|
338
|
-
|
|
340
|
+
"topic" => topic
|
|
339
341
|
}.merge(options)
|
|
340
342
|
end
|
|
341
343
|
let(:options) do
|
|
342
344
|
{
|
|
343
|
-
|
|
344
|
-
|
|
345
|
+
"data" => {
|
|
346
|
+
"story_id" => "story_12345"
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
349
|
end
|
|
348
350
|
|
|
349
351
|
let(:stub_fcm_send_to_topic_request) do
|
|
350
352
|
stub_request(:post, uri).with(
|
|
351
|
-
body: {
|
|
353
|
+
body: { "message" => params }.to_json,
|
|
352
354
|
headers: mock_headers
|
|
353
355
|
).to_return(
|
|
354
356
|
body: "{}",
|
|
355
357
|
headers: {},
|
|
356
|
-
status: 200
|
|
358
|
+
status: 200
|
|
357
359
|
)
|
|
358
360
|
end
|
|
359
361
|
|
|
@@ -361,17 +363,17 @@ describe FCM do
|
|
|
361
363
|
stub_fcm_send_to_topic_request
|
|
362
364
|
end
|
|
363
365
|
|
|
364
|
-
it
|
|
366
|
+
it "sends notification to topic using POST to FCM server" do
|
|
365
367
|
client.send_to_topic(topic, options).should eq(
|
|
366
|
-
response:
|
|
368
|
+
response: "success", body: "{}", headers: {}, status_code: 200
|
|
367
369
|
)
|
|
368
370
|
stub_fcm_send_to_topic_request.should have_been_made.times(1)
|
|
369
371
|
end
|
|
370
372
|
|
|
371
|
-
context
|
|
372
|
-
let(:topic) {
|
|
373
|
+
context "when topic is invalid" do
|
|
374
|
+
let(:topic) { "/topics/news$" }
|
|
373
375
|
|
|
374
|
-
it
|
|
376
|
+
it "raises error" do
|
|
375
377
|
client.send_to_topic(topic, options)
|
|
376
378
|
stub_fcm_send_to_topic_request.should_not have_been_requested
|
|
377
379
|
end
|
|
@@ -379,32 +381,32 @@ describe FCM do
|
|
|
379
381
|
end
|
|
380
382
|
|
|
381
383
|
describe "#send_to_topic_condition" do
|
|
382
|
-
let(:client) {
|
|
384
|
+
let(:client) { described_class.new(json_key_path, project_name) }
|
|
383
385
|
|
|
384
386
|
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
385
387
|
|
|
386
388
|
let(:topic_condition) { "'foo' in topics" }
|
|
387
389
|
let(:params) do
|
|
388
390
|
{
|
|
389
|
-
|
|
391
|
+
"condition" => topic_condition
|
|
390
392
|
}.merge(options)
|
|
391
393
|
end
|
|
392
394
|
let(:options) do
|
|
393
395
|
{
|
|
394
|
-
|
|
395
|
-
|
|
396
|
+
"data" => {
|
|
397
|
+
"story_id" => "story_12345"
|
|
396
398
|
}
|
|
397
399
|
}
|
|
398
400
|
end
|
|
399
401
|
|
|
400
402
|
let(:stub_fcm_send_to_topic_condition_request) do
|
|
401
403
|
stub_request(:post, uri).with(
|
|
402
|
-
body: {
|
|
404
|
+
body: { "message" => params }.to_json,
|
|
403
405
|
headers: mock_headers
|
|
404
406
|
).to_return(
|
|
405
407
|
body: "{}",
|
|
406
408
|
headers: {},
|
|
407
|
-
status: 200
|
|
409
|
+
status: 200
|
|
408
410
|
)
|
|
409
411
|
end
|
|
410
412
|
|
|
@@ -412,17 +414,17 @@ describe FCM do
|
|
|
412
414
|
stub_fcm_send_to_topic_condition_request
|
|
413
415
|
end
|
|
414
416
|
|
|
415
|
-
it
|
|
417
|
+
it "sends notification to topic_condition using POST to FCM server" do
|
|
416
418
|
client.send_to_topic_condition(topic_condition, options).should eq(
|
|
417
|
-
response:
|
|
419
|
+
response: "success", body: "{}", headers: {}, status_code: 200
|
|
418
420
|
)
|
|
419
421
|
stub_fcm_send_to_topic_condition_request.should have_been_made.times(1)
|
|
420
422
|
end
|
|
421
423
|
|
|
422
|
-
context
|
|
424
|
+
context "when topic_condition is invalid" do
|
|
423
425
|
let(:topic_condition) { "'foo' in topics$" }
|
|
424
426
|
|
|
425
|
-
it
|
|
427
|
+
it "raises error" do
|
|
426
428
|
client.send_to_topic_condition(topic_condition, options)
|
|
427
429
|
stub_fcm_send_to_topic_condition_request.should_not have_been_requested
|
|
428
430
|
end
|
|
@@ -437,19 +439,19 @@ describe FCM do
|
|
|
437
439
|
let(:uri) { "#{base_uri}/#{registration_token}" }
|
|
438
440
|
let(:registration_token) { "42" }
|
|
439
441
|
|
|
440
|
-
context
|
|
441
|
-
it
|
|
442
|
+
context "without options" do
|
|
443
|
+
it "calls info endpoint" do
|
|
442
444
|
endpoint = stub_request(:get, uri).with(headers: mock_headers)
|
|
443
445
|
get_info
|
|
444
446
|
expect(endpoint).to have_been_requested
|
|
445
447
|
end
|
|
446
448
|
end
|
|
447
449
|
|
|
448
|
-
context
|
|
450
|
+
context "with detail option" do
|
|
449
451
|
let(:uri) { "#{base_uri}/#{registration_token}?details=true" }
|
|
450
452
|
let(:options) { { details: true } }
|
|
451
453
|
|
|
452
|
-
it
|
|
454
|
+
it "calls info endpoint" do
|
|
453
455
|
endpoint = stub_request(:get, uri).with(headers: mock_headers)
|
|
454
456
|
get_info
|
|
455
457
|
expect(endpoint).to have_been_requested
|
|
@@ -458,17 +460,17 @@ describe FCM do
|
|
|
458
460
|
end
|
|
459
461
|
|
|
460
462
|
describe "topic subscriptions" do
|
|
461
|
-
let(:topic) {
|
|
463
|
+
let(:topic) { "news" }
|
|
462
464
|
let(:registration_token) { "42" }
|
|
463
|
-
let(:
|
|
464
|
-
let(:registration_tokens) { [registration_token,
|
|
465
|
+
let(:registration_token2) { "43" }
|
|
466
|
+
let(:registration_tokens) { [registration_token, registration_token2] }
|
|
465
467
|
|
|
466
468
|
describe "#topic_subscription" do
|
|
467
469
|
subject(:subscribe) { client.topic_subscription(topic, registration_token) }
|
|
468
470
|
|
|
469
471
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1/#{registration_token}/rel/topics/#{topic}" }
|
|
470
472
|
|
|
471
|
-
it
|
|
473
|
+
it "subscribes to a topic" do
|
|
472
474
|
endpoint = stub_request(:post, uri).with(headers: mock_headers)
|
|
473
475
|
subscribe
|
|
474
476
|
expect(endpoint).to have_been_requested
|
|
@@ -481,7 +483,7 @@ describe FCM do
|
|
|
481
483
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1:batchRemove" }
|
|
482
484
|
let(:params) { { to: "/topics/#{topic}", registration_tokens: [registration_token] } }
|
|
483
485
|
|
|
484
|
-
it
|
|
486
|
+
it "unsubscribes from a topic" do
|
|
485
487
|
endpoint = stub_request(:post, uri).with(body: params.to_json, headers: mock_headers)
|
|
486
488
|
unsubscribe
|
|
487
489
|
expect(endpoint).to have_been_requested
|
|
@@ -494,7 +496,7 @@ describe FCM do
|
|
|
494
496
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1:batchAdd" }
|
|
495
497
|
let(:params) { { to: "/topics/#{topic}", registration_tokens: registration_tokens } }
|
|
496
498
|
|
|
497
|
-
it
|
|
499
|
+
it "subscribes to a topic" do
|
|
498
500
|
endpoint = stub_request(:post, uri).with(body: params.to_json, headers: mock_headers)
|
|
499
501
|
batch_subscribe
|
|
500
502
|
expect(endpoint).to have_been_requested
|
|
@@ -507,11 +509,95 @@ describe FCM do
|
|
|
507
509
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1:batchRemove" }
|
|
508
510
|
let(:params) { { to: "/topics/#{topic}", registration_tokens: registration_tokens } }
|
|
509
511
|
|
|
510
|
-
it
|
|
512
|
+
it "unsubscribes from a topic" do
|
|
511
513
|
endpoint = stub_request(:post, uri).with(body: params.to_json, headers: mock_headers)
|
|
512
514
|
batch_unsubscribe
|
|
513
515
|
expect(endpoint).to have_been_requested
|
|
514
516
|
end
|
|
515
517
|
end
|
|
516
518
|
end
|
|
519
|
+
|
|
520
|
+
describe "keep_alive_connections" do
|
|
521
|
+
let(:client) { described_class.new(json_key_path, project_name, keep_alive_connections: true) }
|
|
522
|
+
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
523
|
+
let(:send_v1_params) { { "token" => "token", "notification" => { "title" => "hi" } } }
|
|
524
|
+
|
|
525
|
+
before do
|
|
526
|
+
stub_request(:post, uri).to_return(body: "{}", headers: {}, status: 200)
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
it "caches a Faraday connection per (thread, uri) and reuses it across calls" do
|
|
530
|
+
client.send_v1(send_v1_params)
|
|
531
|
+
first = client.__send__(:thread_connections)[FCM::BASE_URI_V1]
|
|
532
|
+
|
|
533
|
+
client.send_v1(send_v1_params)
|
|
534
|
+
second = client.__send__(:thread_connections)[FCM::BASE_URI_V1]
|
|
535
|
+
|
|
536
|
+
expect(first).to be_a(Faraday::Connection)
|
|
537
|
+
expect(second).to equal(first)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
it "discards the cached connection when a request raises" do
|
|
541
|
+
client.send_v1(send_v1_params)
|
|
542
|
+
expect(client.__send__(:thread_connections)[FCM::BASE_URI_V1]).to be_a(Faraday::Connection)
|
|
543
|
+
|
|
544
|
+
stub_request(:post, uri).to_raise(Faraday::ConnectionFailed.new("boom"))
|
|
545
|
+
|
|
546
|
+
expect { client.send_v1(send_v1_params) }.to raise_error(Faraday::ConnectionFailed)
|
|
547
|
+
expect(client.__send__(:thread_connections)).not_to have_key(FCM::BASE_URI_V1)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
it "does not share connections across FCM instances" do
|
|
551
|
+
other_client = described_class.new(json_key_path, project_name, keep_alive_connections: true)
|
|
552
|
+
allow(other_client).to receive(:json_key)
|
|
553
|
+
|
|
554
|
+
client.send_v1(send_v1_params)
|
|
555
|
+
other_client.send_v1(send_v1_params)
|
|
556
|
+
|
|
557
|
+
expect(client.__send__(:thread_connections)[FCM::BASE_URI_V1])
|
|
558
|
+
.not_to equal(other_client.__send__(:thread_connections)[FCM::BASE_URI_V1])
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
it "falls back to one-shot connections when disabled" do
|
|
562
|
+
one_shot_client = described_class.new(json_key_path, project_name)
|
|
563
|
+
allow(one_shot_client).to receive(:json_key)
|
|
564
|
+
one_shot_client.send_v1(send_v1_params)
|
|
565
|
+
|
|
566
|
+
expect(one_shot_client.__send__(:thread_connections)).to be_empty
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
describe "request timeouts" do
|
|
571
|
+
it "defaults timeout and open_timeout to DEFAULT_TIMEOUT" do
|
|
572
|
+
fcm = described_class.new(json_key_path, project_name)
|
|
573
|
+
allow(fcm).to receive(:json_key)
|
|
574
|
+
|
|
575
|
+
fcm.__send__(:for_uri, FCM::BASE_URI_V1) do |conn|
|
|
576
|
+
expect(conn.options.timeout).to eq(FCM::DEFAULT_TIMEOUT)
|
|
577
|
+
expect(conn.options.open_timeout).to eq(FCM::DEFAULT_TIMEOUT)
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
it "honours :timeout and :open_timeout from http_options" do
|
|
582
|
+
fcm = described_class.new(json_key_path, project_name, timeout: 7, open_timeout: 3)
|
|
583
|
+
allow(fcm).to receive(:json_key)
|
|
584
|
+
|
|
585
|
+
fcm.__send__(:for_uri, FCM::BASE_URI_V1) do |conn|
|
|
586
|
+
expect(conn.options.timeout).to eq(7)
|
|
587
|
+
expect(conn.options.open_timeout).to eq(3)
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
it "honours :timeout and :open_timeout on keep-alive connections" do
|
|
592
|
+
fcm = described_class.new(
|
|
593
|
+
json_key_path, project_name, keep_alive_connections: true, timeout: 7, open_timeout: 3
|
|
594
|
+
)
|
|
595
|
+
allow(fcm).to receive(:json_key)
|
|
596
|
+
|
|
597
|
+
fcm.__send__(:for_uri, FCM::BASE_URI_V1) do |conn|
|
|
598
|
+
expect(conn.options.timeout).to eq(7)
|
|
599
|
+
expect(conn.options.open_timeout).to eq(3)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
end
|
|
517
603
|
end
|