fcm 2.0.1 → 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 +43 -0
- data/Rakefile +9 -7
- data/fcm.gemspec +10 -8
- data/lib/fcm.rb +145 -55
- data/spec/fcm_spec.rb +289 -124
- data/spec/spec_helper.rb +7 -5
- metadata +33 -5
data/spec/fcm_spec.rb
CHANGED
|
@@ -1,57 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "spec_helper"
|
|
4
|
+
require "tempfile"
|
|
2
5
|
|
|
3
6
|
describe FCM do
|
|
4
|
-
let(:project_name) {
|
|
5
|
-
let(:json_key_path) {
|
|
6
|
-
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) }
|
|
7
10
|
|
|
8
11
|
let(:mock_token) { "access_token" }
|
|
9
12
|
let(:mock_headers) do
|
|
10
13
|
{
|
|
11
14
|
"Content-Type" => "application/json",
|
|
12
|
-
"Authorization" => "Bearer #{mock_token}"
|
|
15
|
+
"Authorization" => "Bearer #{mock_token}"
|
|
13
16
|
}
|
|
14
17
|
end
|
|
15
18
|
|
|
19
|
+
let(:client_email) do
|
|
20
|
+
"83315528762cf7e0-7bbcc3aad87e0083391bc7f234d487" \
|
|
21
|
+
"c8@developer.gserviceaccount.com"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
let(:client_x509_cert_url) do
|
|
25
|
+
"https://www.googleapis.com/robot/v1/metadata/x509/" \
|
|
26
|
+
'fd6b61037dd2bb8585527679" + "-7bbcc3aad87e0083391b' \
|
|
27
|
+
"c7f234d487c8%40developer.gserviceaccount.com"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
let(:large_file_name) do
|
|
31
|
+
"#{Array.new(1021) { "a" }.join}.txt"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
let(:creds_error) do
|
|
35
|
+
FCM::InvalidCredentialError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
let(:json_credentials) do
|
|
39
|
+
{
|
|
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"
|
|
51
|
+
}.to_json
|
|
52
|
+
end
|
|
53
|
+
|
|
16
54
|
before do
|
|
17
55
|
allow(client).to receive(:json_key)
|
|
18
56
|
|
|
19
57
|
# Mock the Google::Auth::ServiceAccountCredentials
|
|
20
|
-
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds)
|
|
21
|
-
and_return(double(fetch_access_token!: {
|
|
58
|
+
allow(Google::Auth::ServiceAccountCredentials).to receive(:make_creds)
|
|
59
|
+
.and_return(double(fetch_access_token!: { "access_token" => mock_token }))
|
|
22
60
|
end
|
|
23
61
|
|
|
24
|
-
it "
|
|
62
|
+
it "initializes" do
|
|
25
63
|
expect { client }.not_to raise_error
|
|
26
64
|
end
|
|
27
65
|
|
|
28
66
|
describe "credentials path" do
|
|
29
67
|
it "can be a path to a file" do
|
|
30
|
-
fcm =
|
|
68
|
+
fcm = described_class.new("README.md")
|
|
31
69
|
expect(fcm.__send__(:json_key).class).to eq(File)
|
|
32
70
|
end
|
|
33
71
|
|
|
72
|
+
it "raises an error when passed a large path" do
|
|
73
|
+
expect do
|
|
74
|
+
described_class.new(large_file_name).__send__(:json_key)
|
|
75
|
+
end.to raise_error(creds_error)
|
|
76
|
+
end
|
|
77
|
+
|
|
34
78
|
it "can be an IO object" do
|
|
35
|
-
fcm =
|
|
79
|
+
fcm = described_class.new(StringIO.new("hey"))
|
|
36
80
|
expect(fcm.__send__(:json_key).class).to eq(StringIO)
|
|
81
|
+
|
|
82
|
+
temp_file = Tempfile.new("hello_world.json")
|
|
83
|
+
temp_file.write(json_credentials)
|
|
84
|
+
fcm_with_temp_file = described_class.new(temp_file)
|
|
85
|
+
|
|
86
|
+
expect do
|
|
87
|
+
fcm_with_temp_file
|
|
88
|
+
end.not_to raise_error
|
|
89
|
+
temp_file.close
|
|
90
|
+
temp_file.unlink
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it "raises an error when passed a non IO-like object" do
|
|
94
|
+
expect do
|
|
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.")
|
|
98
|
+
|
|
99
|
+
expect do
|
|
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.")
|
|
103
|
+
|
|
104
|
+
expect do
|
|
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.")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "raises an error when passed a non-existent credentials file path" do
|
|
111
|
+
fcm = described_class.new("spec/fake_credentials.json", "", {})
|
|
112
|
+
expect { fcm.__send__(:json_key) }.to raise_error(creds_error)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "raises an error when passed a string of a file that does not exist" do
|
|
116
|
+
fcm = described_class.new("example.txt", "", {})
|
|
117
|
+
expect { fcm.__send__(:json_key) }.to raise_error(creds_error)
|
|
37
118
|
end
|
|
38
119
|
end
|
|
39
120
|
|
|
40
121
|
describe "#send_v1 or #send_notification_v1" do
|
|
41
|
-
let(:client) {
|
|
122
|
+
let(:client) { described_class.new(json_key_path, project_name) }
|
|
42
123
|
|
|
43
124
|
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
44
125
|
let(:status_code) { 200 }
|
|
45
126
|
|
|
46
127
|
let(:stub_fcm_send_v1_request) do
|
|
47
128
|
stub_request(:post, uri).with(
|
|
48
|
-
body: {
|
|
129
|
+
body: { "message" => send_v1_params }.to_json,
|
|
49
130
|
headers: mock_headers
|
|
50
131
|
).to_return(
|
|
51
132
|
# ref: https://firebase.google.com/docs/cloud-messaging/http-server-ref#interpret-downstream
|
|
52
133
|
body: "{}",
|
|
53
134
|
headers: {},
|
|
54
|
-
status: status_code
|
|
135
|
+
status: status_code
|
|
55
136
|
)
|
|
56
137
|
end
|
|
57
138
|
|
|
@@ -59,222 +140,222 @@ describe FCM do
|
|
|
59
140
|
stub_fcm_send_v1_request
|
|
60
141
|
end
|
|
61
142
|
|
|
62
|
-
shared_examples
|
|
63
|
-
it
|
|
143
|
+
shared_examples "succesfuly send notification" do
|
|
144
|
+
it "sends notification of HTTP V1 using POST to FCM server" do
|
|
64
145
|
client.send_v1(send_v1_params).should eq(
|
|
65
|
-
response:
|
|
146
|
+
response: "success", body: "{}", headers: {}, status_code: 200
|
|
66
147
|
)
|
|
67
148
|
stub_fcm_send_v1_request.should have_been_made.times(1)
|
|
68
149
|
end
|
|
69
150
|
end
|
|
70
151
|
|
|
71
|
-
describe
|
|
72
|
-
let(:token) {
|
|
152
|
+
describe "send to token" do
|
|
153
|
+
let(:token) { "4sdsx" }
|
|
73
154
|
let(:send_v1_params) do
|
|
74
155
|
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
156
|
+
"token" => token,
|
|
157
|
+
"notification" => {
|
|
158
|
+
"title" => "Breaking News",
|
|
159
|
+
"body" => "New news story available."
|
|
79
160
|
},
|
|
80
|
-
|
|
81
|
-
|
|
161
|
+
"data" => {
|
|
162
|
+
"story_id" => "story_12345"
|
|
82
163
|
},
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
164
|
+
"android" => {
|
|
165
|
+
"notification" => {
|
|
166
|
+
"click_action" => "TOP_STORY_ACTIVITY",
|
|
167
|
+
"body" => "Check out the Top Story"
|
|
87
168
|
}
|
|
88
169
|
},
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
170
|
+
"apns" => {
|
|
171
|
+
"payload" => {
|
|
172
|
+
"aps" => {
|
|
173
|
+
"category" => "NEW_MESSAGE_CATEGORY"
|
|
93
174
|
}
|
|
94
175
|
}
|
|
95
176
|
}
|
|
96
177
|
}
|
|
97
178
|
end
|
|
98
179
|
|
|
99
|
-
|
|
180
|
+
it_behaves_like "succesfuly send notification"
|
|
100
181
|
|
|
101
|
-
it
|
|
182
|
+
it "includes all the response" do
|
|
102
183
|
response = client.send_v1(send_v1_params)
|
|
103
184
|
expect(response[:status_code]).to eq(status_code)
|
|
104
|
-
expect(response[:response]).to eq(
|
|
105
|
-
expect(response[:body]).to eq(
|
|
185
|
+
expect(response[:response]).to eq("success")
|
|
186
|
+
expect(response[:body]).to eq("{}")
|
|
106
187
|
expect(response[:headers]).to eq({})
|
|
107
188
|
expect(response[:canonical_ids]).to be_nil
|
|
108
189
|
expect(response[:not_registered_ids]).to be_nil
|
|
109
190
|
end
|
|
110
191
|
end
|
|
111
192
|
|
|
112
|
-
describe
|
|
113
|
-
let(:tokens) { [
|
|
193
|
+
describe "send to multiple tokens" do
|
|
194
|
+
let(:tokens) { %w[4sdsx 4sdsy] }
|
|
114
195
|
let(:send_v1_params) do
|
|
115
196
|
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
197
|
+
"token" => tokens,
|
|
198
|
+
"notification" => {
|
|
199
|
+
"title" => "Breaking News",
|
|
200
|
+
"body" => "New news story available."
|
|
120
201
|
}
|
|
121
202
|
}
|
|
122
203
|
end
|
|
123
204
|
|
|
124
|
-
|
|
205
|
+
it_behaves_like "succesfuly send notification"
|
|
125
206
|
end
|
|
126
207
|
|
|
127
|
-
describe
|
|
128
|
-
let(:topic) {
|
|
208
|
+
describe "send to topic" do
|
|
209
|
+
let(:topic) { "news" }
|
|
129
210
|
let(:send_v1_params) do
|
|
130
211
|
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
212
|
+
"topic" => topic,
|
|
213
|
+
"notification" => {
|
|
214
|
+
"title" => "Breaking News",
|
|
215
|
+
"body" => "New news story available."
|
|
135
216
|
}
|
|
136
217
|
}
|
|
137
218
|
end
|
|
138
219
|
|
|
139
|
-
|
|
220
|
+
it_behaves_like "succesfuly send notification"
|
|
140
221
|
|
|
141
|
-
context
|
|
142
|
-
let(:topic) {
|
|
222
|
+
context "when topic is invalid" do
|
|
223
|
+
let(:topic) { "/topics/news$" }
|
|
143
224
|
|
|
144
|
-
it
|
|
225
|
+
it "raises error" do
|
|
145
226
|
stub_fcm_send_v1_request.should_not have_been_requested
|
|
146
227
|
end
|
|
147
228
|
end
|
|
148
229
|
end
|
|
149
230
|
|
|
150
|
-
describe
|
|
231
|
+
describe "send to condition" do
|
|
151
232
|
let(:condition) { "'foo' in topics" }
|
|
152
233
|
let(:send_v1_params) do
|
|
153
234
|
{
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
235
|
+
"condition" => condition,
|
|
236
|
+
"notification" => {
|
|
237
|
+
"title" => "Breaking News",
|
|
238
|
+
"body" => "New news story available."
|
|
239
|
+
}
|
|
159
240
|
}
|
|
160
241
|
end
|
|
161
242
|
|
|
162
|
-
|
|
243
|
+
it_behaves_like "succesfuly send notification"
|
|
163
244
|
end
|
|
164
245
|
|
|
165
|
-
describe
|
|
166
|
-
let(:notification_key) {
|
|
246
|
+
describe "send to notification_key" do
|
|
247
|
+
let(:notification_key) { "notification_key" }
|
|
167
248
|
let(:send_v1_params) do
|
|
168
249
|
{
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
250
|
+
"notification_key" => notification_key,
|
|
251
|
+
"notification" => {
|
|
252
|
+
"title" => "Breaking News",
|
|
253
|
+
"body" => "New news story available."
|
|
173
254
|
}
|
|
174
255
|
}
|
|
175
256
|
end
|
|
176
257
|
|
|
177
|
-
|
|
258
|
+
it_behaves_like "succesfuly send notification"
|
|
178
259
|
end
|
|
179
260
|
|
|
180
|
-
context
|
|
181
|
-
let(:project_name) {
|
|
261
|
+
context "when project_name is empty" do
|
|
262
|
+
let(:project_name) { "" }
|
|
182
263
|
let(:send_v1_params) do
|
|
183
264
|
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
265
|
+
"token" => "4sdsx",
|
|
266
|
+
"notification" => {
|
|
267
|
+
"title" => "Breaking News",
|
|
268
|
+
"body" => "New news story available."
|
|
188
269
|
}
|
|
189
270
|
}
|
|
190
271
|
end
|
|
191
272
|
|
|
192
|
-
it
|
|
273
|
+
it "does not send notification" do
|
|
193
274
|
client.send_v1(send_v1_params)
|
|
194
275
|
stub_fcm_send_v1_request.should_not have_been_requested
|
|
195
276
|
end
|
|
196
277
|
end
|
|
197
278
|
|
|
198
|
-
describe
|
|
279
|
+
describe "error handling" do
|
|
199
280
|
let(:send_v1_params) do
|
|
200
281
|
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
282
|
+
"token" => "4sdsx",
|
|
283
|
+
"notification" => {
|
|
284
|
+
"title" => "Breaking News",
|
|
285
|
+
"body" => "New news story available."
|
|
205
286
|
}
|
|
206
287
|
}
|
|
207
288
|
end
|
|
208
289
|
|
|
209
|
-
context
|
|
290
|
+
context "when status_code is 400" do
|
|
210
291
|
let(:status_code) { 400 }
|
|
211
292
|
|
|
212
|
-
it
|
|
293
|
+
it "raises error" do
|
|
213
294
|
response = client.send_v1(send_v1_params)
|
|
214
295
|
expect(response[:status_code]).to eq(status_code)
|
|
215
|
-
expect(response[:response]).to include(
|
|
296
|
+
expect(response[:response]).to include("Only applies for JSON requests")
|
|
216
297
|
end
|
|
217
298
|
end
|
|
218
299
|
|
|
219
|
-
context
|
|
300
|
+
context "when status_code is 401" do
|
|
220
301
|
let(:status_code) { 401 }
|
|
221
302
|
|
|
222
|
-
it
|
|
303
|
+
it "raises error" do
|
|
223
304
|
response = client.send_v1(send_v1_params)
|
|
224
305
|
expect(response[:status_code]).to eq(status_code)
|
|
225
|
-
expect(response[:response]).to include(
|
|
306
|
+
expect(response[:response]).to include("There was an error authenticating")
|
|
226
307
|
end
|
|
227
308
|
end
|
|
228
309
|
|
|
229
|
-
context
|
|
310
|
+
context "when status_code is 500" do
|
|
230
311
|
let(:status_code) { 500 }
|
|
231
312
|
|
|
232
|
-
it
|
|
313
|
+
it "raises error" do
|
|
233
314
|
response = client.send_v1(send_v1_params)
|
|
234
315
|
expect(response[:status_code]).to eq(status_code)
|
|
235
|
-
expect(response[:response]).to include(
|
|
316
|
+
expect(response[:response]).to include("There was an internal error")
|
|
236
317
|
end
|
|
237
318
|
end
|
|
238
319
|
|
|
239
|
-
context
|
|
320
|
+
context "when status_code is 503" do
|
|
240
321
|
let(:status_code) { 503 }
|
|
241
322
|
|
|
242
|
-
it
|
|
323
|
+
it "raises error" do
|
|
243
324
|
response = client.send_v1(send_v1_params)
|
|
244
325
|
expect(response[:status_code]).to eq(status_code)
|
|
245
|
-
expect(response[:response]).to include(
|
|
326
|
+
expect(response[:response]).to include("Server is temporarily unavailable")
|
|
246
327
|
end
|
|
247
328
|
end
|
|
248
329
|
end
|
|
249
330
|
end
|
|
250
331
|
|
|
251
|
-
describe
|
|
252
|
-
let(:client) {
|
|
332
|
+
describe "#send_to_topic" do
|
|
333
|
+
let(:client) { described_class.new(json_key_path, project_name) }
|
|
253
334
|
|
|
254
335
|
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
255
336
|
|
|
256
|
-
let(:topic) {
|
|
337
|
+
let(:topic) { "news" }
|
|
257
338
|
let(:params) do
|
|
258
339
|
{
|
|
259
|
-
|
|
340
|
+
"topic" => topic
|
|
260
341
|
}.merge(options)
|
|
261
342
|
end
|
|
262
343
|
let(:options) do
|
|
263
344
|
{
|
|
264
|
-
|
|
265
|
-
|
|
345
|
+
"data" => {
|
|
346
|
+
"story_id" => "story_12345"
|
|
266
347
|
}
|
|
267
348
|
}
|
|
268
349
|
end
|
|
269
350
|
|
|
270
351
|
let(:stub_fcm_send_to_topic_request) do
|
|
271
352
|
stub_request(:post, uri).with(
|
|
272
|
-
body: {
|
|
353
|
+
body: { "message" => params }.to_json,
|
|
273
354
|
headers: mock_headers
|
|
274
355
|
).to_return(
|
|
275
356
|
body: "{}",
|
|
276
357
|
headers: {},
|
|
277
|
-
status: 200
|
|
358
|
+
status: 200
|
|
278
359
|
)
|
|
279
360
|
end
|
|
280
361
|
|
|
@@ -282,17 +363,17 @@ describe FCM do
|
|
|
282
363
|
stub_fcm_send_to_topic_request
|
|
283
364
|
end
|
|
284
365
|
|
|
285
|
-
it
|
|
366
|
+
it "sends notification to topic using POST to FCM server" do
|
|
286
367
|
client.send_to_topic(topic, options).should eq(
|
|
287
|
-
response:
|
|
368
|
+
response: "success", body: "{}", headers: {}, status_code: 200
|
|
288
369
|
)
|
|
289
370
|
stub_fcm_send_to_topic_request.should have_been_made.times(1)
|
|
290
371
|
end
|
|
291
372
|
|
|
292
|
-
context
|
|
293
|
-
let(:topic) {
|
|
373
|
+
context "when topic is invalid" do
|
|
374
|
+
let(:topic) { "/topics/news$" }
|
|
294
375
|
|
|
295
|
-
it
|
|
376
|
+
it "raises error" do
|
|
296
377
|
client.send_to_topic(topic, options)
|
|
297
378
|
stub_fcm_send_to_topic_request.should_not have_been_requested
|
|
298
379
|
end
|
|
@@ -300,32 +381,32 @@ describe FCM do
|
|
|
300
381
|
end
|
|
301
382
|
|
|
302
383
|
describe "#send_to_topic_condition" do
|
|
303
|
-
let(:client) {
|
|
384
|
+
let(:client) { described_class.new(json_key_path, project_name) }
|
|
304
385
|
|
|
305
386
|
let(:uri) { "#{FCM::BASE_URI_V1}#{project_name}/messages:send" }
|
|
306
387
|
|
|
307
388
|
let(:topic_condition) { "'foo' in topics" }
|
|
308
389
|
let(:params) do
|
|
309
390
|
{
|
|
310
|
-
|
|
391
|
+
"condition" => topic_condition
|
|
311
392
|
}.merge(options)
|
|
312
393
|
end
|
|
313
394
|
let(:options) do
|
|
314
395
|
{
|
|
315
|
-
|
|
316
|
-
|
|
396
|
+
"data" => {
|
|
397
|
+
"story_id" => "story_12345"
|
|
317
398
|
}
|
|
318
399
|
}
|
|
319
400
|
end
|
|
320
401
|
|
|
321
402
|
let(:stub_fcm_send_to_topic_condition_request) do
|
|
322
403
|
stub_request(:post, uri).with(
|
|
323
|
-
body: {
|
|
404
|
+
body: { "message" => params }.to_json,
|
|
324
405
|
headers: mock_headers
|
|
325
406
|
).to_return(
|
|
326
407
|
body: "{}",
|
|
327
408
|
headers: {},
|
|
328
|
-
status: 200
|
|
409
|
+
status: 200
|
|
329
410
|
)
|
|
330
411
|
end
|
|
331
412
|
|
|
@@ -333,17 +414,17 @@ describe FCM do
|
|
|
333
414
|
stub_fcm_send_to_topic_condition_request
|
|
334
415
|
end
|
|
335
416
|
|
|
336
|
-
it
|
|
417
|
+
it "sends notification to topic_condition using POST to FCM server" do
|
|
337
418
|
client.send_to_topic_condition(topic_condition, options).should eq(
|
|
338
|
-
response:
|
|
419
|
+
response: "success", body: "{}", headers: {}, status_code: 200
|
|
339
420
|
)
|
|
340
421
|
stub_fcm_send_to_topic_condition_request.should have_been_made.times(1)
|
|
341
422
|
end
|
|
342
423
|
|
|
343
|
-
context
|
|
424
|
+
context "when topic_condition is invalid" do
|
|
344
425
|
let(:topic_condition) { "'foo' in topics$" }
|
|
345
426
|
|
|
346
|
-
it
|
|
427
|
+
it "raises error" do
|
|
347
428
|
client.send_to_topic_condition(topic_condition, options)
|
|
348
429
|
stub_fcm_send_to_topic_condition_request.should_not have_been_requested
|
|
349
430
|
end
|
|
@@ -358,19 +439,19 @@ describe FCM do
|
|
|
358
439
|
let(:uri) { "#{base_uri}/#{registration_token}" }
|
|
359
440
|
let(:registration_token) { "42" }
|
|
360
441
|
|
|
361
|
-
context
|
|
362
|
-
it
|
|
442
|
+
context "without options" do
|
|
443
|
+
it "calls info endpoint" do
|
|
363
444
|
endpoint = stub_request(:get, uri).with(headers: mock_headers)
|
|
364
445
|
get_info
|
|
365
446
|
expect(endpoint).to have_been_requested
|
|
366
447
|
end
|
|
367
448
|
end
|
|
368
449
|
|
|
369
|
-
context
|
|
450
|
+
context "with detail option" do
|
|
370
451
|
let(:uri) { "#{base_uri}/#{registration_token}?details=true" }
|
|
371
452
|
let(:options) { { details: true } }
|
|
372
453
|
|
|
373
|
-
it
|
|
454
|
+
it "calls info endpoint" do
|
|
374
455
|
endpoint = stub_request(:get, uri).with(headers: mock_headers)
|
|
375
456
|
get_info
|
|
376
457
|
expect(endpoint).to have_been_requested
|
|
@@ -379,17 +460,17 @@ describe FCM do
|
|
|
379
460
|
end
|
|
380
461
|
|
|
381
462
|
describe "topic subscriptions" do
|
|
382
|
-
let(:topic) {
|
|
463
|
+
let(:topic) { "news" }
|
|
383
464
|
let(:registration_token) { "42" }
|
|
384
|
-
let(:
|
|
385
|
-
let(:registration_tokens) { [registration_token,
|
|
465
|
+
let(:registration_token2) { "43" }
|
|
466
|
+
let(:registration_tokens) { [registration_token, registration_token2] }
|
|
386
467
|
|
|
387
468
|
describe "#topic_subscription" do
|
|
388
469
|
subject(:subscribe) { client.topic_subscription(topic, registration_token) }
|
|
389
470
|
|
|
390
471
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1/#{registration_token}/rel/topics/#{topic}" }
|
|
391
472
|
|
|
392
|
-
it
|
|
473
|
+
it "subscribes to a topic" do
|
|
393
474
|
endpoint = stub_request(:post, uri).with(headers: mock_headers)
|
|
394
475
|
subscribe
|
|
395
476
|
expect(endpoint).to have_been_requested
|
|
@@ -402,7 +483,7 @@ describe FCM do
|
|
|
402
483
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1:batchRemove" }
|
|
403
484
|
let(:params) { { to: "/topics/#{topic}", registration_tokens: [registration_token] } }
|
|
404
485
|
|
|
405
|
-
it
|
|
486
|
+
it "unsubscribes from a topic" do
|
|
406
487
|
endpoint = stub_request(:post, uri).with(body: params.to_json, headers: mock_headers)
|
|
407
488
|
unsubscribe
|
|
408
489
|
expect(endpoint).to have_been_requested
|
|
@@ -415,7 +496,7 @@ describe FCM do
|
|
|
415
496
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1:batchAdd" }
|
|
416
497
|
let(:params) { { to: "/topics/#{topic}", registration_tokens: registration_tokens } }
|
|
417
498
|
|
|
418
|
-
it
|
|
499
|
+
it "subscribes to a topic" do
|
|
419
500
|
endpoint = stub_request(:post, uri).with(body: params.to_json, headers: mock_headers)
|
|
420
501
|
batch_subscribe
|
|
421
502
|
expect(endpoint).to have_been_requested
|
|
@@ -428,11 +509,95 @@ describe FCM do
|
|
|
428
509
|
let(:uri) { "#{FCM::INSTANCE_ID_API}/iid/v1:batchRemove" }
|
|
429
510
|
let(:params) { { to: "/topics/#{topic}", registration_tokens: registration_tokens } }
|
|
430
511
|
|
|
431
|
-
it
|
|
512
|
+
it "unsubscribes from a topic" do
|
|
432
513
|
endpoint = stub_request(:post, uri).with(body: params.to_json, headers: mock_headers)
|
|
433
514
|
batch_unsubscribe
|
|
434
515
|
expect(endpoint).to have_been_requested
|
|
435
516
|
end
|
|
436
517
|
end
|
|
437
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
|
|
438
603
|
end
|