appsignal 2.11.5-java → 2.11.10-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,6 @@
1
1
  describe Appsignal::Extension, :extension_installation_failure do
2
2
  context "when the extension library cannot be loaded" do
3
- # This test breaks the installation on purpose and is not run by default.
4
- # See `rake test:failure`. If this test was run, run `rake
5
- # extension:install` again to fix the extension installation.
6
3
  it "prints and logs an error" do
7
- # ENV var to make sure installation fails on purpurse
8
- ENV["_TEST_APPSIGNAL_EXTENSION_FAILURE"] = "true"
9
- `rake extension:install` # Run installation
10
-
11
4
  require "open3"
12
5
  _stdout, stderr, _status = Open3.capture3("bin/appsignal --version")
13
6
  expect(stderr).to include("ERROR: AppSignal failed to load extension")
@@ -119,22 +119,56 @@ describe Appsignal::Extension do
119
119
  end
120
120
  end
121
121
 
122
- context "when the extension library cannot be loaded" do
123
- subject { Appsignal::Extension }
122
+ context "when the extension library cannot be loaded", :extension_installation_failure do
123
+ let(:ext) { Appsignal::Extension }
124
124
 
125
- before do
126
- allow(Appsignal).to receive(:extension_loaded).and_return(false)
127
- allow(Appsignal).to receive(:testing?).and_return(false)
125
+ around do |example|
126
+ Appsignal::Testing.without_testing { example.run }
128
127
  end
129
128
 
130
129
  it "should indicate that the extension is not loaded" do
131
130
  expect(Appsignal.extension_loaded?).to be_falsy
132
131
  end
133
132
 
134
- it "should not raise errors when methods are called" do
135
- expect do
136
- subject.something
137
- end.not_to raise_error
133
+ it "does not raise errors when methods are called" do
134
+ ext.appsignal_start
135
+ ext.something
136
+ end
137
+
138
+ describe Appsignal::Extension::MockData do
139
+ it "does not error on missing data_map_new extension method calls" do
140
+ map = ext.data_map_new
141
+ expect(map).to be_kind_of(Appsignal::Extension::MockData)
142
+ # Does not raise errors any arbitrary method call that does not exist
143
+ map.set_string("key", "value")
144
+ map.set_int("key", 123)
145
+ map.something
146
+ end
147
+
148
+ it "does not error on missing data_array_new extension method calls" do
149
+ array = ext.data_array_new
150
+ expect(array).to be_kind_of(Appsignal::Extension::MockData)
151
+ # Does not raise errors any arbitrary method call that does not exist
152
+ array.append_string("value")
153
+ array.append_int(123)
154
+ array.something
155
+ end
156
+ end
157
+
158
+ describe Appsignal::Extension::MockTransaction do
159
+ it "does not error on missing transaction extension method calls" do
160
+ transaction = described_class.new
161
+
162
+ transaction.start_event(0)
163
+ transaction.finish_event(
164
+ "name",
165
+ "title",
166
+ "body",
167
+ Appsignal::EventFormatter::DEFAULT,
168
+ 0
169
+ )
170
+ transaction.something
171
+ end
138
172
  end
139
173
  end
140
174
  end
@@ -2,6 +2,8 @@ describe Appsignal::Hooks::ActionCableHook do
2
2
  if DependencyHelper.action_cable_present?
3
3
  context "with ActionCable" do
4
4
  require "action_cable/engine"
5
+ # Require test helper to test with ConnectionStub
6
+ require "action_cable/channel/test_case" if DependencyHelper.rails6_present?
5
7
 
6
8
  describe ".dependencies_present?" do
7
9
  subject { described_class.new.dependencies_present? }
@@ -262,6 +264,49 @@ describe Appsignal::Hooks::ActionCableHook do
262
264
  )
263
265
  end
264
266
  end
267
+
268
+ if DependencyHelper.rails6_present?
269
+ context "with ConnectionStub" do
270
+ let(:connection) { ActionCable::Channel::ConnectionStub.new }
271
+ let(:transaction_id) { "Stubbed transaction id" }
272
+ before do
273
+ # Stub future (private AppSignal) transaction id generated by the hook.
274
+ expect(SecureRandom).to receive(:uuid).and_return(transaction_id)
275
+ end
276
+
277
+ it "does not fail on missing `#env` method on `ConnectionStub`" do
278
+ instance.subscribe_to_channel
279
+
280
+ expect(subject).to include(
281
+ "action" => "MyChannel#subscribed",
282
+ "error" => nil,
283
+ "id" => transaction_id,
284
+ "namespace" => Appsignal::Transaction::ACTION_CABLE,
285
+ "metadata" => {
286
+ "method" => "websocket",
287
+ "path" => "" # No path as the ConnectionStub doesn't have the real request env
288
+ }
289
+ )
290
+ expect(subject["events"].first).to include(
291
+ "allocation_count" => kind_of(Integer),
292
+ "body" => "",
293
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
294
+ "child_allocation_count" => kind_of(Integer),
295
+ "child_duration" => kind_of(Float),
296
+ "child_gc_duration" => kind_of(Float),
297
+ "count" => 1,
298
+ "gc_duration" => kind_of(Float),
299
+ "start" => kind_of(Float),
300
+ "duration" => kind_of(Float),
301
+ "name" => "subscribed.action_cable",
302
+ "title" => ""
303
+ )
304
+ expect(subject["sample_data"]).to include(
305
+ "params" => { "internal" => "true" }
306
+ )
307
+ end
308
+ end
309
+ end
265
310
  end
266
311
 
267
312
  describe "unsubscribe callback" do
@@ -349,6 +394,49 @@ describe Appsignal::Hooks::ActionCableHook do
349
394
  )
350
395
  end
351
396
  end
397
+
398
+ if DependencyHelper.rails6_present?
399
+ context "with ConnectionStub" do
400
+ let(:connection) { ActionCable::Channel::ConnectionStub.new }
401
+ let(:transaction_id) { "Stubbed transaction id" }
402
+ before do
403
+ # Stub future (private AppSignal) transaction id generated by the hook.
404
+ expect(SecureRandom).to receive(:uuid).and_return(transaction_id)
405
+ end
406
+
407
+ it "does not fail on missing `#env` method on `ConnectionStub`" do
408
+ instance.unsubscribe_from_channel
409
+
410
+ expect(subject).to include(
411
+ "action" => "MyChannel#unsubscribed",
412
+ "error" => nil,
413
+ "id" => transaction_id,
414
+ "namespace" => Appsignal::Transaction::ACTION_CABLE,
415
+ "metadata" => {
416
+ "method" => "websocket",
417
+ "path" => "" # No path as the ConnectionStub doesn't have the real request env
418
+ }
419
+ )
420
+ expect(subject["events"].first).to include(
421
+ "allocation_count" => kind_of(Integer),
422
+ "body" => "",
423
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
424
+ "child_allocation_count" => kind_of(Integer),
425
+ "child_duration" => kind_of(Float),
426
+ "child_gc_duration" => kind_of(Float),
427
+ "count" => 1,
428
+ "gc_duration" => kind_of(Float),
429
+ "start" => kind_of(Float),
430
+ "duration" => kind_of(Float),
431
+ "name" => "unsubscribed.action_cable",
432
+ "title" => ""
433
+ )
434
+ expect(subject["sample_data"]).to include(
435
+ "params" => { "internal" => "true" }
436
+ )
437
+ end
438
+ end
439
+ end
352
440
  end
353
441
  end
354
442
  end
@@ -16,14 +16,31 @@ describe Appsignal::Hooks::SidekiqHook do
16
16
  end
17
17
 
18
18
  describe "#install" do
19
- class SidekiqMiddlewareMock < Set
20
- def exists?(middleware)
21
- include?(middleware)
19
+ class SidekiqMiddlewareMockWithPrepend < Array
20
+ alias add <<
21
+ alias exists? include?
22
+
23
+ unless method_defined? :prepend
24
+ def prepend(middleware) # For Ruby < 2.5
25
+ insert(0, middleware)
26
+ end
22
27
  end
23
28
  end
29
+
30
+ class SidekiqMiddlewareMockWithoutPrepend < Array
31
+ alias add <<
32
+ alias exists? include?
33
+
34
+ undef_method :prepend if method_defined? :prepend # For Ruby >= 2.5
35
+ end
36
+
24
37
  module SidekiqMock
38
+ def self.middleware_mock=(mock)
39
+ @middlewares = mock.new
40
+ end
41
+
25
42
  def self.middlewares
26
- @middlewares ||= SidekiqMiddlewareMock.new
43
+ @middlewares
27
44
  end
28
45
 
29
46
  def self.configure_server
@@ -36,15 +53,52 @@ describe Appsignal::Hooks::SidekiqHook do
36
53
  end
37
54
  end
38
55
 
56
+ def add_middleware(middleware)
57
+ Sidekiq.configure_server do |sidekiq_config|
58
+ sidekiq_config.middlewares.add(middleware)
59
+ end
60
+ end
61
+
39
62
  before do
40
63
  Appsignal.config = project_fixture_config
41
64
  stub_const "Sidekiq", SidekiqMock
42
65
  end
43
66
 
44
- it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
45
- described_class.new.install
67
+ context "when Sidekiq middleware responds to prepend method" do # Sidekiq 3.3.0 and newer
68
+ before { Sidekiq.middleware_mock = SidekiqMiddlewareMockWithPrepend }
69
+
70
+ it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain in the first position" do
71
+ user_middleware1 = proc {}
72
+ add_middleware(user_middleware1)
73
+ described_class.new.install
74
+ user_middleware2 = proc {}
75
+ add_middleware(user_middleware2)
46
76
 
47
- expect(Sidekiq.server_middleware.exists?(Appsignal::Hooks::SidekiqPlugin)).to be(true)
77
+ expect(Sidekiq.server_middleware).to eql([
78
+ Appsignal::Hooks::SidekiqPlugin, # Prepend makes it the first entry
79
+ user_middleware1,
80
+ user_middleware2
81
+ ])
82
+ end
83
+ end
84
+
85
+ context "when Sidekiq middleware does not respond to prepend method" do
86
+ before { Sidekiq.middleware_mock = SidekiqMiddlewareMockWithoutPrepend }
87
+
88
+ it "adds the AppSignal SidekiqPlugin to the Sidekiq middleware chain" do
89
+ user_middleware1 = proc {}
90
+ add_middleware(user_middleware1)
91
+ described_class.new.install
92
+ user_middleware2 = proc {}
93
+ add_middleware(user_middleware2)
94
+
95
+ # Add middlewares in whatever order they were added
96
+ expect(Sidekiq.server_middleware).to eql([
97
+ user_middleware1,
98
+ Appsignal::Hooks::SidekiqPlugin,
99
+ user_middleware2
100
+ ])
101
+ end
48
102
  end
49
103
  end
50
104
  end
@@ -30,12 +30,57 @@ describe Object do
30
30
  before do
31
31
  Appsignal.config = project_fixture_config
32
32
  expect(Appsignal::Transaction).to receive(:current).at_least(:once).and_return(transaction)
33
+ expect(Appsignal.active?).to be_truthy
33
34
  end
34
35
  after { Appsignal.config = nil }
35
36
 
37
+ context "with different kind of arguments" do
38
+ let(:klass) do
39
+ Class.new do
40
+ def positional_arguments(param1, param2)
41
+ [param1, param2]
42
+ end
43
+ appsignal_instrument_method :positional_arguments
44
+
45
+ def positional_arguments_splat(*params)
46
+ params
47
+ end
48
+ appsignal_instrument_method :positional_arguments_splat
49
+
50
+ def keyword_arguments(a: nil, b: nil)
51
+ [a, b]
52
+ end
53
+ appsignal_instrument_method :keyword_arguments
54
+
55
+ def keyword_arguments_splat(**kwargs)
56
+ kwargs
57
+ end
58
+ appsignal_instrument_method :keyword_arguments_splat
59
+
60
+ def splat(*args, **kwargs)
61
+ [args, kwargs]
62
+ end
63
+ appsignal_instrument_method :splat
64
+ end
65
+ end
66
+
67
+ it "instruments the method and calls it" do
68
+ expect(instance.positional_arguments("abc", "def")).to eq(["abc", "def"])
69
+ expect(instance.positional_arguments_splat("abc", "def")).to eq(["abc", "def"])
70
+ expect(instance.keyword_arguments(:a => "a", :b => "b")).to eq(["a", "b"])
71
+ expect(instance.keyword_arguments_splat(:a => "a", :b => "b"))
72
+ .to eq(:a => "a", :b => "b")
73
+
74
+ expect(instance.splat).to eq([[], {}])
75
+ expect(instance.splat(:a => "a", :b => "b")).to eq([[], { :a => "a", :b => "b" }])
76
+ expect(instance.splat("abc", "def")).to eq([["abc", "def"], {}])
77
+ expect(instance.splat("abc", "def", :a => "a", :b => "b"))
78
+ .to eq([["abc", "def"], { :a => "a", :b => "b" }])
79
+ end
80
+ end
81
+
36
82
  context "with anonymous class" do
37
83
  it "instruments the method and calls it" do
38
- expect(Appsignal.active?).to be_truthy
39
84
  expect(transaction).to receive(:start_event)
40
85
  expect(transaction).to receive(:finish_event).with \
41
86
  "foo.AnonymousClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
@@ -56,7 +101,6 @@ describe Object do
56
101
  let(:klass) { NamedClass }
57
102
 
58
103
  it "instruments the method and calls it" do
59
- expect(Appsignal.active?).to be_truthy
60
104
  expect(transaction).to receive(:start_event)
61
105
  expect(transaction).to receive(:finish_event).with \
62
106
  "foo.NamedClass.other", nil, nil, Appsignal::EventFormatter::DEFAULT
@@ -81,7 +125,6 @@ describe Object do
81
125
  let(:klass) { MyModule::NestedModule::NamedClass }
82
126
 
83
127
  it "instruments the method and calls it" do
84
- expect(Appsignal.active?).to be_truthy
85
128
  expect(transaction).to receive(:start_event)
86
129
  expect(transaction).to receive(:finish_event).with \
87
130
  "bar.NamedClass.NestedModule.MyModule.other", nil, nil,
@@ -101,7 +144,6 @@ describe Object do
101
144
  end
102
145
 
103
146
  it "instruments with custom name" do
104
- expect(Appsignal.active?).to be_truthy
105
147
  expect(transaction).to receive(:start_event)
106
148
  expect(transaction).to receive(:finish_event).with \
107
149
  "my_method.group", nil, nil, Appsignal::EventFormatter::DEFAULT
@@ -162,6 +204,51 @@ describe Object do
162
204
  end
163
205
  after { Appsignal.config = nil }
164
206
 
207
+ context "with different kind of arguments" do
208
+ let(:klass) do
209
+ Class.new do
210
+ def self.positional_arguments(param1, param2)
211
+ [param1, param2]
212
+ end
213
+ appsignal_instrument_class_method :positional_arguments
214
+
215
+ def self.positional_arguments_splat(*params)
216
+ params
217
+ end
218
+ appsignal_instrument_class_method :positional_arguments_splat
219
+
220
+ def self.keyword_arguments(a: nil, b: nil)
221
+ [a, b]
222
+ end
223
+ appsignal_instrument_class_method :keyword_arguments
224
+
225
+ def self.keyword_arguments_splat(**kwargs)
226
+ kwargs
227
+ end
228
+ appsignal_instrument_class_method :keyword_arguments_splat
229
+
230
+ def self.splat(*args, **kwargs)
231
+ [args, kwargs]
232
+ end
233
+ appsignal_instrument_class_method :splat
234
+ end
235
+ end
236
+
237
+ it "instruments the method and calls it" do
238
+ expect(klass.positional_arguments("abc", "def")).to eq(["abc", "def"])
239
+ expect(klass.positional_arguments_splat("abc", "def")).to eq(["abc", "def"])
240
+ expect(klass.keyword_arguments(:a => "a", :b => "b")).to eq(["a", "b"])
241
+ expect(klass.keyword_arguments_splat(:a => "a", :b => "b"))
242
+ .to eq(:a => "a", :b => "b")
243
+
244
+ expect(klass.splat).to eq([[], {}])
245
+ expect(klass.splat(:a => "a", :b => "b")).to eq([[], { :a => "a", :b => "b" }])
246
+ expect(klass.splat("abc", "def")).to eq([["abc", "def"], {}])
247
+ expect(klass.splat("abc", "def", :a => "a", :b => "b"))
248
+ .to eq([["abc", "def"], { :a => "a", :b => "b" }])
249
+ end
250
+ end
251
+
165
252
  context "with anonymous class" do
166
253
  it "instruments the method and calls it" do
167
254
  expect(Appsignal.active?).to be_truthy
@@ -246,6 +246,23 @@ describe Appsignal::Transaction do
246
246
  expect(transaction.ext).to_not be_nil
247
247
  end
248
248
 
249
+ context "when extension is not loaded", :extension_installation_failure do
250
+ around do |example|
251
+ Appsignal::Testing.without_testing { example.run }
252
+ end
253
+
254
+ it "does not error on missing extension method calls" do
255
+ expect(transaction.ext).to be_kind_of(Appsignal::Extension::MockTransaction)
256
+ transaction.start_event
257
+ transaction.finish_event(
258
+ "name",
259
+ "title",
260
+ "body",
261
+ Appsignal::EventFormatter::DEFAULT
262
+ )
263
+ end
264
+ end
265
+
249
266
  it "sets the transaction id" do
250
267
  expect(transaction.transaction_id).to eq "1"
251
268
  end
@@ -4,110 +4,156 @@ describe Appsignal::Utils::Data do
4
4
  describe ".generate" do
5
5
  subject { Appsignal::Utils::Data.generate(body) }
6
6
 
7
- context "with a valid hash body" do
8
- let(:body) do
9
- {
10
- "the" => "payload",
11
- "int" => 1, # Fixnum
12
- "int61" => 1 << 61, # Fixnum
13
- "int62" => 1 << 62, # Bignum, this one still works
14
- "int63" => 1 << 63, # Bignum, turnover point for C, too big for long
15
- "int64" => 1 << 64, # Bignum
16
- "float" => 1.0,
17
- 1 => true,
18
- nil => "test",
19
- :foo => [1, 2, "three", { "foo" => "bar" }],
20
- "bar" => nil,
21
- "baz" => { "foo" => "bʊr", "arr" => [1, 2] }
22
- }
7
+ context "when extension is not loaded", :extension_installation_failure do
8
+ around do |example|
9
+ Appsignal::Testing.without_testing { example.run }
23
10
  end
24
11
 
25
- it { is_expected.to eq Appsignal::Utils::Data.generate(body) }
26
- it { is_expected.to_not eq Appsignal::Utils::Data.generate({}) }
27
-
28
- describe "#to_s" do
29
- it "returns a serialized hash" do
30
- expect(subject.to_s).to eq %({"":"test",) +
31
- %("1":true,) +
32
- %("bar":null,) +
33
- %("baz":{"arr":[1,2],"foo":"bʊr"},) +
34
- %("float":1.0,) +
35
- %("foo":[1,2,"three",{"foo":"bar"}],) +
36
- %("int":1,) +
37
- %("int61":#{1 << 61},) +
38
- %("int62":#{1 << 62},) +
39
- %("int63":"bigint:#{1 << 63}",) +
40
- %("int64":"bigint:#{1 << 64}",) +
41
- %("the":"payload"})
12
+ context "with valid hash body" do
13
+ let(:body) { hash_body }
14
+
15
+ it "does not error and returns MockData class" do
16
+ expect(subject).to be_kind_of(Appsignal::Extension::MockData)
17
+ expect(subject.to_s).to eql("{}")
42
18
  end
43
19
  end
44
- end
45
20
 
46
- context "with a valid array body" do
47
- let(:body) do
48
- [
49
- nil,
50
- true,
51
- false,
52
- "string",
53
- 1, # Fixnum
54
- 1.0, # Float
55
- 1 << 61, # Fixnum
56
- 1 << 62, # Bignum, this one still works
57
- 1 << 63, # Bignum, turnover point for C, too big for long
58
- 1 << 64, # Bignum
59
- { "arr" => [1, 2, "three"], "foo" => "bʊr" }
60
- ]
21
+ context "with valid array body" do
22
+ let(:body) { array_body }
23
+
24
+ it "does not error and returns MockData class" do
25
+ expect(subject).to be_kind_of(Appsignal::Extension::MockData)
26
+ expect(subject.to_s).to eql("{}")
27
+ end
61
28
  end
62
29
 
63
- it { is_expected.to eq Appsignal::Utils::Data.generate(body) }
64
- it { is_expected.to_not eq Appsignal::Utils::Data.generate({}) }
65
-
66
- describe "#to_s" do
67
- it "returns a serialized array" do
68
- expect(subject.to_s).to eq %([null,) +
69
- %(true,) +
70
- %(false,) +
71
- %(\"string\",) +
72
- %(1,) +
73
- %(1.0,) +
74
- %(#{1 << 61},) +
75
- %(#{1 << 62},) +
76
- %("bigint:#{1 << 63}",) +
77
- %("bigint:#{1 << 64}",) +
78
- %({\"arr\":[1,2,\"three\"],\"foo\":\"bʊr\"}])
30
+ context "with an invalid body" do
31
+ let(:body) { "body" }
32
+
33
+ it "raise a type error" do
34
+ expect do
35
+ subject
36
+ end.to raise_error TypeError
79
37
  end
80
38
  end
81
39
  end
82
40
 
83
- context "with a body that contains strings with invalid utf-8 content" do
84
- let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack("c*") }
85
- let(:body) do
86
- {
87
- "field_one" => [0x61, 0x61].pack("c*"),
88
- :field_two => string_with_invalid_utf8,
89
- "field_three" => [
90
- "one", string_with_invalid_utf8
91
- ],
92
- "field_four" => {
93
- "one" => string_with_invalid_utf8
94
- }
95
- }
41
+ context "when extension is loaded" do
42
+ context "with a valid hash body" do
43
+ let(:body) { hash_body }
44
+
45
+ it "returns a valid Data object" do
46
+ is_expected.to eq Appsignal::Utils::Data.generate(body)
47
+ is_expected.to_not eq Appsignal::Utils::Data.generate({})
48
+ end
49
+
50
+ describe "#to_s" do
51
+ it "returns a serialized hash" do
52
+ expect(subject.to_s).to eq %({"":"test",) +
53
+ %("1":true,) +
54
+ %("bar":null,) +
55
+ %("baz":{"arr":[1,2],"foo":"bʊr"},) +
56
+ %("float":1.0,) +
57
+ %("foo":[1,2,"three",{"foo":"bar"}],) +
58
+ %("int":1,) +
59
+ %("int61":#{1 << 61},) +
60
+ %("int62":#{1 << 62},) +
61
+ %("int63":"bigint:#{1 << 63}",) +
62
+ %("int64":"bigint:#{1 << 64}",) +
63
+ %("the":"payload"})
64
+ end
65
+ end
66
+ end
67
+
68
+ context "with a valid array body" do
69
+ let(:body) { array_body }
70
+
71
+ it "returns a valid Data object" do
72
+ is_expected.to eq Appsignal::Utils::Data.generate(body)
73
+ is_expected.to_not eq Appsignal::Utils::Data.generate({})
74
+ end
75
+
76
+ describe "#to_s" do
77
+ it "returns a serialized array" do
78
+ expect(subject.to_s).to eq %([null,) +
79
+ %(true,) +
80
+ %(false,) +
81
+ %(\"string\",) +
82
+ %(1,) +
83
+ %(1.0,) +
84
+ %(#{1 << 61},) +
85
+ %(#{1 << 62},) +
86
+ %("bigint:#{1 << 63}",) +
87
+ %("bigint:#{1 << 64}",) +
88
+ %({\"arr\":[1,2,\"three\"],\"foo\":\"bʊr\"}])
89
+ end
90
+ end
96
91
  end
97
92
 
98
- describe "#to_s" do
99
- it { expect(subject.to_s).to eq %({"field_four":{"one":"aa�"},"field_one":"aa","field_three":["one","aa�"],"field_two":"aa�"}) }
93
+ context "with a body that contains strings with invalid utf-8 content" do
94
+ let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack("c*") }
95
+ let(:body) do
96
+ {
97
+ "field_one" => [0x61, 0x61].pack("c*"),
98
+ :field_two => string_with_invalid_utf8,
99
+ "field_three" => [
100
+ "one", string_with_invalid_utf8
101
+ ],
102
+ "field_four" => {
103
+ "one" => string_with_invalid_utf8
104
+ }
105
+ }
106
+ end
107
+
108
+ describe "#to_s" do
109
+ it "returns a JSON representation in a String" do
110
+ expect(subject.to_s).to eq %({"field_four":{"one":"aa�"},"field_one":"aa","field_three":["one","aa�"],"field_two":"aa�"})
111
+ end
112
+ end
100
113
  end
101
- end
102
114
 
103
- context "with an invalid body" do
104
- let(:body) { "body" }
115
+ context "with an invalid body" do
116
+ let(:body) { "body" }
105
117
 
106
- it "should raise a type error" do
107
- expect do
108
- subject
109
- end.to raise_error TypeError
118
+ it "raises a type error" do
119
+ expect do
120
+ subject
121
+ end.to raise_error TypeError
122
+ end
110
123
  end
111
124
  end
112
125
  end
126
+
127
+ def hash_body
128
+ {
129
+ "the" => "payload",
130
+ "int" => 1, # Fixnum
131
+ "int61" => 1 << 61, # Fixnum
132
+ "int62" => 1 << 62, # Bignum, this one still works
133
+ "int63" => 1 << 63, # Bignum, turnover point for C, too big for long
134
+ "int64" => 1 << 64, # Bignum
135
+ "float" => 1.0,
136
+ 1 => true,
137
+ nil => "test",
138
+ :foo => [1, 2, "three", { "foo" => "bar" }],
139
+ "bar" => nil,
140
+ "baz" => { "foo" => "bʊr", "arr" => [1, 2] }
141
+ }
142
+ end
143
+
144
+ def array_body
145
+ [
146
+ nil,
147
+ true,
148
+ false,
149
+ "string",
150
+ 1, # Fixnum
151
+ 1.0, # Float
152
+ 1 << 61, # Fixnum
153
+ 1 << 62, # Bignum, this one still works
154
+ 1 << 63, # Bignum, turnover point for C, too big for long
155
+ 1 << 64, # Bignum
156
+ { "arr" => [1, 2, "three"], "foo" => "bʊr" }
157
+ ]
158
+ end
113
159
  end