protip 0.20.7 → 0.30.0

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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/protip.rb +10 -0
  3. data/lib/protip/{wrapper.rb → decorator.rb} +98 -82
  4. data/lib/protip/extensions.rb +0 -1
  5. data/lib/protip/resource.rb +56 -32
  6. data/lib/protip/resource/extra_methods.rb +5 -3
  7. data/lib/protip/tasks/compile.rake +2 -2
  8. data/lib/protip/transformer.rb +14 -0
  9. data/lib/protip/transformers/abstract_transformer.rb +11 -0
  10. data/lib/protip/transformers/active_support/time_with_zone_transformer.rb +35 -0
  11. data/lib/protip/transformers/decorating_transformer.rb +33 -0
  12. data/lib/protip/transformers/default_transformer.rb +22 -0
  13. data/lib/protip/transformers/delegating_transformer.rb +44 -0
  14. data/lib/protip/transformers/deprecated_transformer.rb +90 -0
  15. data/lib/protip/transformers/enum_transformer.rb +93 -0
  16. data/lib/protip/transformers/primitives_transformer.rb +78 -0
  17. data/lib/protip/transformers/timestamp_transformer.rb +31 -0
  18. data/test/functional/protip/decorator_test.rb +204 -0
  19. data/test/unit/protip/decorator_test.rb +665 -0
  20. data/test/unit/protip/resource_test.rb +76 -92
  21. data/test/unit/protip/transformers/decorating_transformer_test.rb +92 -0
  22. data/test/unit/protip/transformers/delegating_transformer_test.rb +83 -0
  23. data/test/unit/protip/transformers/deprecated_transformer_test.rb +139 -0
  24. data/test/unit/protip/transformers/enum_transformer_test.rb +157 -0
  25. data/test/unit/protip/transformers/primitives_transformer_test.rb +139 -0
  26. data/test/unit/protip/transformers/timestamp_transformer_test.rb +46 -0
  27. metadata +23 -12
  28. data/definitions/google/protobuf/wrappers.proto +0 -99
  29. data/lib/google/protobuf/descriptor.rb +0 -1
  30. data/lib/google/protobuf/wrappers.rb +0 -48
  31. data/lib/protip/converter.rb +0 -22
  32. data/lib/protip/standard_converter.rb +0 -185
  33. data/test/unit/protip/standard_converter_test.rb +0 -502
  34. data/test/unit/protip/wrapper_test.rb +0 -646
@@ -0,0 +1,31 @@
1
+ require 'protip/transformer'
2
+ require 'protip/transformers/delegating_transformer'
3
+
4
+ module Protip
5
+ module Transformers
6
+ class TimestampTransformer < DelegatingTransformer
7
+ def initialize
8
+ super
9
+ # TODO: single-message transformers are awkward to define
10
+ transformer = Class.new do
11
+ include Protip::Transformer
12
+
13
+ def to_object(message, field)
14
+ # Using a Rational prevents rounding errors, see
15
+ # http://stackoverflow.com/questions/16326008/accuracy-of-nanosecond-component-in-ruby-time
16
+ ::Time.at(message.seconds, Rational(message.nanos, 1000))
17
+ end
18
+
19
+ def to_message(object, field)
20
+ object = object.to_time # No-op for ::Time objects
21
+ field.subtype.msgclass.new(
22
+ seconds: object.to_i,
23
+ nanos: object.nsec,
24
+ )
25
+ end
26
+ end.new
27
+ self['google.protobuf.Timestamp'] = transformer
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,204 @@
1
+ require 'test_helper'
2
+
3
+ require 'protip/decorator'
4
+ require 'protip/transformers/default_transformer'
5
+
6
+ require 'google/protobuf'
7
+ require 'google/protobuf/wrappers'
8
+
9
+ require 'protip/messages/test' # For the enum hack
10
+
11
+ # Tests the whole decoration/transformation process with the default
12
+ # transformer, using well-known types and other transformable message
13
+ # types.
14
+ describe Protip::Decorator do
15
+ let(:decorated_message) { raise NotImplementedError }
16
+ let(:transformer) { Protip::Transformers::DefaultTransformer.new }
17
+ let(:decorator) { Protip::Decorator.new decorated_message, transformer }
18
+
19
+ let(:pool) do
20
+ pool = ::Google::Protobuf::DescriptorPool.new
21
+ pool.build do
22
+ add_enum 'number' do
23
+ value :ZERO, 0
24
+ value :ONE, 1
25
+ value :TWO, 2
26
+ end
27
+ add_message 'inner_message' do
28
+ optional :value, :int64, 1
29
+ optional :note, :string, 2
30
+ end
31
+ add_message 'google.protobuf.StringValue' do
32
+ optional :value, :string, 1
33
+ end
34
+ add_message 'protip.messages.EnumValue' do
35
+ optional :value, :int32, 1
36
+ end
37
+ add_message 'message' do
38
+ optional :inner, :message, 1, 'inner_message'
39
+ optional :string_value, :message, 2, 'google.protobuf.StringValue'
40
+ optional :enum_value, :message, 3, 'protip.messages.EnumValue'
41
+ optional :string, :string, 4
42
+
43
+ optional :inner_alternate, :message, 5, 'inner_message'
44
+ end
45
+ end
46
+ pool
47
+ end
48
+ let(:message_class) { pool.lookup('message').msgclass }
49
+ let(:inner_message_class) { pool.lookup('inner_message').msgclass }
50
+ let(:string_value_class) { pool.lookup('google.protobuf.StringValue').msgclass }
51
+ let(:enum_value_class) { pool.lookup('protip.messages.EnumValue').msgclass }
52
+
53
+ describe 'getters' do
54
+ # Temporary while our hacky enum detection is still necessary
55
+ before do
56
+ Protip::Transformers::EnumTransformer.stubs(:enum_for_field).
57
+ with(message_class.descriptor.lookup('enum_value')).
58
+ returns(pool.lookup('number'))
59
+ end
60
+
61
+ let(:decorated_message) do
62
+ message_class.new(
63
+ inner: inner_message_class.new(value: 50),
64
+ string_value: string_value_class.new(value: 'salt'),
65
+ enum_value: enum_value_class.new(value: 1),
66
+ string: 'peppa',
67
+ )
68
+ end
69
+ it 'returns a decorated version of the inner message' do
70
+ result = decorator.inner
71
+ assert_instance_of Protip::Decorator, result
72
+ assert_equal 50, result.value
73
+ end
74
+ it 'returns nil for nil messages' do
75
+ assert_nil decorator.inner_alternate
76
+ end
77
+ it 'returns a string for the StringValue message' do
78
+ assert_equal 'salt', decorator.string_value
79
+ end
80
+ it 'returns a symbol for the EnumValue message' do
81
+ assert_equal :ONE, decorator.enum_value
82
+ end
83
+ it 'returns an integer for the EnumValue message if the value is out of range' do
84
+ decorated_message.enum_value.value = 4
85
+ assert_equal 4, decorator.enum_value
86
+ end
87
+ it 'returns primitives directly' do
88
+ assert_equal 'peppa', decorator.string
89
+ end
90
+ end
91
+
92
+ describe 'setters' do
93
+ # Temporary while our hacky enum detection is still necessary
94
+ before do
95
+ Protip::Transformers::EnumTransformer.stubs(:enum_for_field).
96
+ with(message_class.descriptor.lookup('enum_value')).
97
+ returns(pool.lookup('number'))
98
+ end
99
+
100
+ let(:decorated_message) do
101
+ message_class.new
102
+ end
103
+
104
+ it 'allows setting the inner message directly' do
105
+ inner_message = inner_message_class.new(value: 70)
106
+ decorator.inner = inner_message
107
+ assert_equal inner_message, decorated_message.inner
108
+ end
109
+ it 'allows setting the inner message from another decorator' do
110
+ inner_message = inner_message_class.new(value: 80)
111
+ decorator.inner = Protip::Decorator.new(inner_message, transformer)
112
+ assert_equal inner_message, decorated_message.inner
113
+ end
114
+ it 'allows setting the inner message by hash' do
115
+ decorator.inner = {value: 90}
116
+ assert_equal inner_message_class.new(value: 90),
117
+ decorated_message.inner
118
+ end
119
+
120
+ it 'allows setting the StringValue by message' do
121
+ string_value_message = string_value_class.new(value: 'Tool')
122
+ decorator.string_value = string_value_message
123
+ assert_equal string_value_message, decorated_message.string_value
124
+ end
125
+ it 'allows setting the StringValue by string' do
126
+ decorator.string_value = 'TMV'
127
+ assert_equal string_value_class.new(value: 'TMV'), decorated_message.string_value
128
+ end
129
+
130
+ it 'allows setting the EnumValue by symbol' do
131
+ decorator.enum_value = :ONE
132
+ assert_equal enum_value_class.new(value: 1), decorated_message.enum_value
133
+ end
134
+ it 'allows setting the EnumValue by string' do
135
+ decorator.enum_value = 'TWO'
136
+ assert_equal enum_value_class.new(value: 2), decorated_message.enum_value
137
+ end
138
+ it 'allows setting the EnumValue by number' do
139
+ decorator.enum_value = 4
140
+ assert_equal enum_value_class.new(value: 4), decorated_message.enum_value
141
+ end
142
+ it 'raises an error when setting the EnumValue by an undefined symbol' do
143
+ assert_raises RangeError do
144
+ decorator.enum_value = :BLUE
145
+ end
146
+ end
147
+
148
+ it 'allows setting primitive fields directly' do
149
+ decorator.string = 'hai'
150
+ assert_equal 'hai', decorated_message.string
151
+ end
152
+
153
+ it 'allows nulling message fields' do
154
+ decorator.string_value = nil
155
+ decorator.enum_value = nil
156
+ decorator.inner = nil
157
+
158
+ assert_nil decorated_message.string_value
159
+ assert_nil decorated_message.enum_value
160
+ assert_nil decorated_message.inner
161
+ end
162
+ end
163
+
164
+ describe 'enum hacks' do # Temp - test an actual compiled file to make sure our options hack is working
165
+ let(:decorated_message) { Protip::Messages::EnumTest.new }
166
+ let(:decorator) { Protip::Decorator.new decorated_message, transformer }
167
+
168
+ let(:value_map) do
169
+ {
170
+ :ONE => :ONE,
171
+ 1 => :ONE,
172
+ 2 => 2,
173
+ }
174
+ end
175
+
176
+ it 'allows setting and getting a scalar field by Ruby value' do
177
+ value_map.each do |value, expected|
178
+ decorator.enum = value
179
+ assert_equal expected, decorator.enum
180
+ end
181
+ assert_raises RangeError do
182
+ decorator.enum = :TWO
183
+ end
184
+ end
185
+ it 'allows setting and getting a scalar field by message' do
186
+ decorator.enum = Protip::Messages::EnumValue.new(value: 1)
187
+ assert_equal :ONE, decorator.enum
188
+ end
189
+
190
+ it 'allows setting and getting a repeated field by Ruby value' do
191
+ value_map.each do |value, expected|
192
+ decorator.repeated_enums = [value]
193
+ assert_equal [expected], decorator.repeated_enums
194
+ end
195
+ assert_raises RangeError do
196
+ decorator.repeated_enums = [:TWO]
197
+ end
198
+ end
199
+ it 'allows setting and geting a repeated field by message' do
200
+ decorator.repeated_enums = Protip::Messages::RepeatedEnum.new(values: [2])
201
+ assert_equal [2], decorator.repeated_enums
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,665 @@
1
+ require 'test_helper'
2
+
3
+ require 'google/protobuf'
4
+
5
+ require 'protip/decorator'
6
+ require 'protip/resource'
7
+ require 'protip/transformer'
8
+
9
+
10
+ describe Protip::Decorator do
11
+ let(:transformer) do
12
+ Class.new do
13
+ include Protip::Transformer
14
+ end.new
15
+ end
16
+
17
+ let(:pool) do
18
+ pool = Google::Protobuf::DescriptorPool.new
19
+ pool.build do
20
+ add_enum 'number' do
21
+ value :ZERO, 0
22
+ value :ONE, 1
23
+ value :TWO, 2
24
+ end
25
+ add_message 'inner_message' do
26
+ optional :value, :int64, 1
27
+ optional :note, :string, 2
28
+ end
29
+ add_message 'google.protobuf.BoolValue' do
30
+ optional :value, :bool, 1
31
+ end
32
+ add_message 'protip.messages.EnumValue' do
33
+ optional :value, :int32, 1
34
+ end
35
+
36
+ add_message 'message' do
37
+ optional :inner, :message, 1, 'inner_message'
38
+ optional :string, :string, 2
39
+
40
+ repeated :inners, :message, 3, 'inner_message'
41
+ repeated :strings, :string, 4
42
+
43
+ optional :inner_blank, :message, 5, 'inner_message'
44
+
45
+ optional :number, :enum, 6, 'number'
46
+ repeated :numbers, :enum, 7, 'number'
47
+ optional :number_message, :message, 8, 'protip.messages.EnumValue'
48
+
49
+ optional :boolean, :bool, 9
50
+ repeated :booleans, :bool, 10
51
+
52
+ optional :google_bool_value, :message, 11, 'google.protobuf.BoolValue'
53
+ repeated :google_bool_values, :message, 12, 'google.protobuf.BoolValue'
54
+
55
+ oneof :oneof_group do
56
+ optional :oneof_string1, :string, 13
57
+ optional :oneof_string2, :string, 14
58
+ end
59
+ end
60
+ end
61
+ pool
62
+ end
63
+
64
+ %w(inner_message message).each do |name|
65
+ let(:"#{name}_class") do
66
+ pool.lookup(name).msgclass
67
+ end
68
+ end
69
+ let(:enum_message_class) do
70
+ pool.lookup('protip.messages.EnumValue').msgclass
71
+ end
72
+ let (:inner_message_field) { message_class.descriptor.lookup('inner') }
73
+
74
+ # Stubbed API client
75
+ let :client do
76
+ mock.responds_like_instance_of(Class.new { include Protip::Client })
77
+ end
78
+
79
+ # Call `resource_class` to get an empty resource type.
80
+ let :resource_class do
81
+ resource_class = Class.new do
82
+ include Protip::Resource
83
+ self.base_path = 'base_path'
84
+ class << self
85
+ attr_accessor :client
86
+ end
87
+ end
88
+ resource_class.client = client
89
+ resource_class
90
+ end
91
+
92
+ # An actual protobuf message, which is used when building the decorator below
93
+ let(:decorated_message) do
94
+ message_class.new(inner: inner_message_class.new(value: 25), string: 'test')
95
+ end
96
+
97
+ let(:decorator) do
98
+ Protip::Decorator.new(decorated_message, transformer)
99
+ end
100
+
101
+ # Stub the wrapped-enum fetcher method - probably rethink this once the
102
+ # instance variable hack is no longer necessary
103
+ before do
104
+ Protip::Transformers::EnumTransformer.stubs(:enum_for_field)
105
+ .returns(pool.lookup('number') || raise('unexpected - no enum field found'))
106
+ end
107
+
108
+ describe '#respond_to?' do
109
+ it 'adds setters for message fields' do
110
+ assert_respond_to decorator, :string=
111
+ assert_respond_to decorator, :inner=
112
+ assert_respond_to decorator, :inner_blank=
113
+ end
114
+ it 'adds getters for message fields' do
115
+ assert_respond_to decorator, :string
116
+ assert_respond_to decorator, :inner
117
+ assert_respond_to decorator, :inner_blank
118
+ end
119
+ it 'adds accessors for oneof groups' do
120
+ assert_respond_to decorator, :oneof_group
121
+ end
122
+ it 'adds queries for scalar fields' do
123
+ assert_respond_to decorator, :number?, 'enum field should respond to query'
124
+ assert_respond_to decorator, :number_message?, 'enum message field should respond to query'
125
+ assert_respond_to decorator, :boolean?, 'bool field should respond to query'
126
+ assert_respond_to decorator, :google_bool_value?, 'google.protobuf.BoolValue field should respond to query'
127
+ assert_respond_to decorator, :inner?, 'non-bool message field should respond to query'
128
+ end
129
+ it 'adds queries for repeated fields' do
130
+ assert_respond_to decorator, :numbers?, 'repeated enum field should respond to query'
131
+ assert_respond_to decorator, :booleans?, 'repeated bool field should respond to query'
132
+ assert_respond_to decorator, :google_bool_values?, 'repeated google.protobuf.BoolValue field should respond to query'
133
+ end
134
+ it 'responds to standard defined methods' do
135
+ assert_respond_to decorator, :as_json
136
+ end
137
+ it 'does not add other setters/getters/queries' do
138
+ refute_respond_to decorator, :foo=
139
+ refute_respond_to decorator, :foo
140
+ refute_respond_to decorator, :foo?
141
+ end
142
+ it 'does not add methods which partially match message fields' do
143
+ refute_respond_to decorator, :xinner
144
+ refute_respond_to decorator, :xinner=
145
+ refute_respond_to decorator, :xnumber?
146
+ refute_respond_to decorator, :innerx
147
+ refute_respond_to decorator, :innerx=
148
+ refute_respond_to decorator, :'inner=x'
149
+ refute_respond_to decorator, :numberx?
150
+ refute_respond_to decorator, :'number?x'
151
+ end
152
+ end
153
+
154
+ describe '#build' do
155
+ let(:decorated_message) { message_class.new }
156
+ before do
157
+ decorator.stubs(:get).returns(:opeth)
158
+ end
159
+
160
+ it 'raises an error when building a primitive field' do
161
+ assert_raises RuntimeError do
162
+ decorator.build(:string)
163
+ end
164
+ end
165
+
166
+ it 'raises an error when building a repeated primitive field' do
167
+ assert_raises RuntimeError do
168
+ decorator.build(:strings)
169
+ end
170
+ end
171
+
172
+ it 'builds the message when no attributes are provided' do
173
+ assert_nil decorated_message.inner # Sanity check
174
+ decorator.build(:inner)
175
+ assert_equal inner_message_class.new, decorated_message.inner
176
+ end
177
+
178
+ it 'overwrites the message if it exists' do
179
+ decorated_message.inner = inner_message_class.new(value: 4)
180
+ decorator.build(:inner)
181
+ assert_equal inner_message_class.new, decorated_message.inner
182
+ end
183
+
184
+ it 'delegates to #assign_attributes if attributes are provided' do
185
+ # A decorator should be created with a new instance of the
186
+ # message, and the same transformer as the main decorator.
187
+ inner_decorator = mock('inner decorator')
188
+ decorator.class.expects(:new).
189
+ once.
190
+ with(inner_message_class.new, transformer).
191
+ returns(inner_decorator)
192
+
193
+ # That decorator should be used to assign the given attributes
194
+ assignment = sequence('assignment')
195
+ inner_decorator.expects(:assign_attributes).
196
+ in_sequence(assignment).
197
+ once.
198
+ with(value: 40)
199
+
200
+ # And then return a message, which should be assigned to the
201
+ # field being built (we return a mock with a different value
202
+ # so we can check it in the next step).
203
+ built_message = inner_message_class.new(value: 15)
204
+ inner_decorator.expects(:message).
205
+ in_sequence(assignment).
206
+ once.
207
+ returns(built_message)
208
+
209
+ decorator.build(:inner, value: 40)
210
+ assert_equal built_message, decorated_message.inner
211
+ end
212
+
213
+ it 'returns the built field' do
214
+ built = decorator.build(:inner)
215
+ assert_equal :opeth, built
216
+ end
217
+ end
218
+
219
+ describe '#assign_attributes' do
220
+
221
+ it 'assigns primitive fields directly' do
222
+ decorator.assign_attributes string: 'another thing'
223
+ assert_equal 'another thing', decorated_message.string
224
+ end
225
+
226
+ it 'assigns repeated primitive fields from an enumerator' do
227
+ decorator.assign_attributes strings: ['one', 'two']
228
+ assert_equal ['one', 'two'], decorated_message.strings
229
+ end
230
+
231
+ it 'assigns multiple attributes' do
232
+ decorator.assign_attributes string: 'foo', strings: ['one', 'two']
233
+ assert_equal 'foo', decorated_message.string
234
+ assert_equal ['one', 'two'], decorated_message.strings
235
+ end
236
+
237
+ describe 'when assigning message fields with a non-hash' do
238
+
239
+ it 'converts scalar Ruby values to protobuf messages' do
240
+ transformer.expects(:to_message).
241
+ once.
242
+ with(45, inner_message_field).
243
+ returns(inner_message_class.new(value: 43))
244
+
245
+ decorator.assign_attributes inner: 45
246
+ assert_equal inner_message_class.new(value: 43),
247
+ decorated_message.inner
248
+ end
249
+
250
+ it 'converts repeated Ruby values to protobuf messages' do
251
+ invocation = 0
252
+ transformer.expects(:to_message).twice.with do |value|
253
+ invocation += 1
254
+ value == invocation
255
+ end.returns(inner_message_class.new(value: 43), inner_message_class.new(value: 44))
256
+ decorator.assign_attributes inners: [1, 2]
257
+ assert_equal [inner_message_class.new(value: 43), inner_message_class.new(value: 44)],
258
+ decorated_message.inners
259
+ end
260
+
261
+ it 'allows messages to be assigned directly' do
262
+ message = inner_message_class.new
263
+ decorator.assign_attributes inner: message
264
+ assert_same message, decorated_message.inner
265
+ end
266
+
267
+ it "sets fields to nil when they're assigned nil" do
268
+ decorated_message.inner = inner_message_class.new(value: 60)
269
+ refute_nil decorated_message.inner
270
+ decorator.assign_attributes inner: nil
271
+ assert_nil decorated_message.inner
272
+ end
273
+ end
274
+
275
+ it 'returns nil' do
276
+ assert_nil decorator.assign_attributes({})
277
+ end
278
+
279
+ describe 'when assigning message fields with a hash' do
280
+ it 'builds nil message fields and assigns attributes to them' do
281
+ # We expect to transform an empty message, and then assign
282
+ # attributes on it.
283
+ transformed_inner_message = mock 'transfomred inner message'
284
+ transformer.expects(:to_object).once.with(
285
+ inner_message_class.new,
286
+ instance_of(::Google::Protobuf::FieldDescriptor)
287
+ ).returns(transformed_inner_message)
288
+ transformed_inner_message.expects(:assign_attributes).
289
+ once.
290
+ with(note: 'created')
291
+
292
+ decorated_message.inner = nil
293
+ decorator.assign_attributes inner: {note: 'created'}
294
+ end
295
+
296
+ it 'updates message fields which are already present' do
297
+ # We expect to transform the existing message, and then
298
+ # assign attributes on it.
299
+ transformed_inner_message = mock 'transformed inner message'
300
+ inner_message = inner_message_class.new(value: 60)
301
+ transformer.expects(:to_object).once.with(
302
+ inner_message,
303
+ instance_of(::Google::Protobuf::FieldDescriptor)
304
+ ).returns(transformed_inner_message)
305
+
306
+ transformed_inner_message.expects(:assign_attributes).
307
+ once.
308
+ with(note: 'updated')
309
+
310
+ decorated_message.inner = inner_message
311
+ decorator.assign_attributes inner: {note: 'updated'}
312
+ end
313
+ end
314
+ end
315
+
316
+ describe '#==' do
317
+ it 'returns false for non-wrapper objects' do
318
+ refute_equal 1, decorator
319
+ refute_equal decorator, 1 # Sanity check, make sure we're testing both sides of equality
320
+ end
321
+
322
+ it 'returns false when messages are not equal' do
323
+ alternate_message = message_class.new
324
+ refute_equal alternate_message, decorator.message # Sanity check
325
+ refute_equal decorator, Protip::Decorator.new(alternate_message, decorator.transformer)
326
+ end
327
+
328
+ it 'returns false when transformer are not equal' do
329
+ alternate_transformer = Class.new do
330
+ include Protip::Transformer
331
+ end.new
332
+ refute_equal alternate_transformer, transformer # Sanity check
333
+ refute_equal transformer, Protip::Decorator.new(decorated_message, alternate_transformer)
334
+ end
335
+
336
+ it 'returns true when the message and transformer are equal' do
337
+ # Stub converter equality so we aren't relying on actual equality behavior there
338
+ alternate_transformer = transformer.clone
339
+ transformer.expects(:==).at_least_once.with(alternate_transformer).returns(true)
340
+ assert_equal decorator, Protip::Decorator.new(decorated_message.clone, transformer)
341
+ end
342
+ end
343
+
344
+ describe '#to_h' do
345
+ let(:transformed_value) { mock 'transformed value' }
346
+ let(:decorated_message) do
347
+ m = message_class.new({
348
+ string: 'test',
349
+ inner: inner_message_class.new(value: 1),
350
+ })
351
+ m.strings += %w(test1 test2)
352
+ [2, 3].each do |i|
353
+ m.inners.push inner_message_class.new(value: i)
354
+ end
355
+ m
356
+ end
357
+
358
+ before do
359
+ transformer.stubs(:to_object).returns(transformed_value)
360
+ end
361
+
362
+ it 'contains keys for all fields of the parent message' do
363
+ keys = %i(
364
+ string strings inner inners inner_blank number numbers
365
+ number_message boolean booleans google_bool_value google_bool_values
366
+ oneof_string1 oneof_string2)
367
+ assert_equal keys.sort, decorator.to_h.keys.sort
368
+ end
369
+
370
+ it 'passes along nil values' do
371
+ hash = decorator.to_h
372
+ assert hash.has_key?(:inner_blank)
373
+ assert_nil hash[:inner_blank]
374
+ end
375
+
376
+ it 'transforms scalar messages' do
377
+ assert_equal transformed_value, decorator.to_h[:inner]
378
+ end
379
+
380
+ it 'transforms repeated messages' do
381
+ assert_equal [transformed_value, transformed_value],
382
+ decorator.to_h[:inners]
383
+ end
384
+
385
+ it 'returns scalar primitives directly' do
386
+ assert_equal 'test', decorator.to_h[:string]
387
+ end
388
+
389
+ it 'returns repeated primitives directly' do
390
+ assert_equal ['test1', 'test2'], decorator.to_h[:strings]
391
+ end
392
+
393
+ describe 'for fields which transorm to an instance of Protip::Decorator' do
394
+ let(:transformed_value) { Protip::Decorator.new(inner_message_class.new, transformer) }
395
+ let(:transformed_value_to_h) { {foo: 'bar'} }
396
+ before do
397
+ transformed_value.stubs(:to_h).returns(transformed_value_to_h)
398
+ end
399
+ it 'trickles down the :to_h call on scalar messages' do
400
+ assert_equal transformed_value_to_h, decorator.to_h[:inner]
401
+ end
402
+ it 'trickles down the :to_h call on repeated messages' do
403
+ assert_equal [transformed_value_to_h, transformed_value_to_h],
404
+ decorator.to_h[:inners]
405
+ end
406
+ end
407
+ end
408
+
409
+ describe 'getters' do
410
+ before do
411
+ resource_class.class_exec(transformer, inner_message_class) do |transformer, message|
412
+ resource actions: [], message: message
413
+ self.transformer = transformer
414
+ end
415
+ end
416
+
417
+ it 'does not transform simple fields' do
418
+ transformer.expects(:to_object).never
419
+ assert_equal 'test', decorator.string
420
+ end
421
+
422
+ it 'transforms messages' do
423
+ transformer.expects(:to_object).once.with(
424
+ inner_message_class.new(value: 25),
425
+ inner_message_field
426
+ ).returns 40
427
+ assert_equal 40, decorator.inner
428
+ end
429
+
430
+ it 'wraps nested resource messages in their defined resource' do
431
+ message = decorated_message
432
+ klass = resource_class
433
+ decorator = Protip::Decorator.new(message, transformer, {inner: klass})
434
+ assert_equal klass, decorator.inner.class
435
+ assert_equal message.inner, decorator.inner.message
436
+ end
437
+
438
+ it 'returns nil for messages that have not been set' do
439
+ transformer.expects(:to_object).never
440
+ assert_equal nil, decorator.inner_blank
441
+ end
442
+
443
+ it 'returns the underlying assigned value for oneof fields' do
444
+ decorated_message.oneof_string1 = 'foo'
445
+ assert_equal 'foo', decorator.oneof_group
446
+ decorated_message.oneof_string2 = 'bar'
447
+ assert_equal 'bar', decorator.oneof_group
448
+ decorated_message.oneof_string2 = 'bar'
449
+ decorated_message.oneof_string1 = 'foo'
450
+ assert_equal 'foo', decorator.oneof_group
451
+ end
452
+
453
+ it 'returns nil for oneof fields that have not been set' do
454
+ assert_nil decorator.oneof_group
455
+ end
456
+ end
457
+
458
+ describe 'attribute writer' do # generated via method_missing?
459
+
460
+ before do
461
+ resource_class.class_exec(transformer, inner_message_class) do |transformer, message|
462
+ resource actions: [], message: message
463
+ self.transformer = transformer
464
+ end
465
+ end
466
+
467
+ it 'does not transform simple fields' do
468
+ transformer.expects(:to_message).never
469
+
470
+ decorator.string = 'test2'
471
+ assert_equal 'test2', decorator.message.string
472
+ end
473
+
474
+ it 'transforms messages' do
475
+ transformer.expects(:to_message).
476
+ with(40, inner_message_field).
477
+ returns(inner_message_class.new(value: 30))
478
+
479
+ decorator.inner = 40
480
+ assert_equal inner_message_class.new(value: 30), decorated_message.inner
481
+ end
482
+
483
+ it 'removes message fields when assigning nil, without transforming them' do
484
+ transformer.expects(:to_message).never
485
+ decorator.inner = nil
486
+ assert_nil decorated_message.inner
487
+ end
488
+
489
+ it 'passes through messages without transforming them' do
490
+ message = inner_message_class.new(value: 50)
491
+
492
+ transformer.expects(:to_message).never
493
+ decorator.inner = message
494
+ assert_equal inner_message_class.new(value: 50), decorated_message.inner
495
+ end
496
+
497
+ it "for nested resources, sets the resource's message" do
498
+ message = message_class.new
499
+ klass = resource_class
500
+ new_inner_message = inner_message_class.new(value: 50)
501
+
502
+ resource = klass.new new_inner_message
503
+ decorator = Protip::Decorator.new(message, transformer, {inner: klass})
504
+
505
+ resource.expects(:message).once.returns(new_inner_message)
506
+ decorator.inner = resource
507
+
508
+ assert_equal new_inner_message,
509
+ decorator.message.inner,
510
+ 'Decorator did not set its message\'s inner message value to the value of the '\
511
+ 'given resource\'s message'
512
+ end
513
+
514
+ it 'raises an error when setting an enum field to an undefined value' do
515
+ assert_raises RangeError do
516
+ decorator.number = :CHEERIOS
517
+ end
518
+ end
519
+
520
+ it 'allows strings to be set for enum fields' do
521
+ decorator.number = 'ONE'
522
+ assert_equal :ONE, decorator.number
523
+ end
524
+
525
+ it 'allows symbols to be set for enum fields' do
526
+ decorator.number = :ONE
527
+ assert_equal :ONE, decorated_message.number
528
+ end
529
+
530
+ it 'allows numbers to be set for enum fields' do
531
+ decorator.number = 1
532
+ assert_equal :ONE, decorated_message.number
533
+ end
534
+
535
+ it 'allows symbolizable values to be set for enum fields' do
536
+ m = mock
537
+ m.stubs(:to_sym).returns(:ONE)
538
+
539
+ decorator.number = m
540
+ assert_equal :ONE, decorated_message.number
541
+ end
542
+
543
+ it 'returns the input value' do
544
+ input_value = 'str'
545
+ assert_equal input_value, (decorator.string = input_value)
546
+ end
547
+ end
548
+
549
+ describe 'queries' do
550
+ it 'returns the presence of scalar fields' do
551
+ raise 'unexpected' unless decorated_message.string == 'test'
552
+ raise 'unexpected' unless decorated_message.boolean == false
553
+ raise 'unexpected' unless decorated_message.google_bool_value == nil
554
+
555
+ assert_equal true, decorator.string?
556
+ assert_equal false, decorator.boolean?
557
+ assert_equal false, decorator.google_bool_value?
558
+
559
+ decorated_message.string = ''
560
+ assert_equal false, decorator.string?
561
+ end
562
+
563
+ it 'returns the presence of repeated fields' do
564
+ raise 'unexpected' if decorated_message.strings.length > 0
565
+ assert_equal false, decorator.strings?
566
+
567
+ decorated_message.strings << 'test'
568
+ assert_equal true, decorator.strings?
569
+ end
570
+
571
+ it 'returns the presence of transformed message fields' do
572
+ raise 'unexpected' if nil == decorated_message.inner
573
+ transformer.stubs(:to_object).returns('not empty string')
574
+ assert_equal true, decorator.inner?
575
+
576
+ transformer.stubs(:to_object).returns('')
577
+ assert_equal false, decorator.inner?
578
+ end
579
+
580
+ end
581
+
582
+ describe '#matches?' do
583
+ it 'raises an error for non-enum fields' do
584
+ assert_raises ArgumentError do
585
+ decorator.inner?(:test)
586
+ end
587
+ end
588
+
589
+ it 'raises an error for repeated enum fields' do
590
+ assert_raises ArgumentError do
591
+ decorator.numbers?(:test)
592
+ end
593
+ end
594
+
595
+ describe 'when given a Fixnum' do
596
+ before do
597
+ decorator.number = :ONE
598
+ end
599
+ it 'returns true when the number matches the value' do
600
+ assert decorator.number?(1)
601
+ end
602
+ it 'returns false when the number does not match the value' do
603
+ refute decorator.number?(0)
604
+ end
605
+ it 'raises an error when the number is not a valid value for the enum' do
606
+ assert_raises RangeError do
607
+ decorator.number?(3)
608
+ end
609
+ end
610
+ end
611
+
612
+ describe 'when given a non-Fixnum' do
613
+ before do
614
+ decorator.number = :TWO
615
+ end
616
+ it 'returns true when its symbolized argument matches the value' do
617
+ m = mock
618
+ m.expects(:to_sym).returns :TWO
619
+ assert decorator.number?(m)
620
+ end
621
+ it 'returns false when its symbolized argument does not match the value' do
622
+ m = mock
623
+ m.expects(:to_sym).returns :ONE
624
+ refute decorator.number?(m)
625
+ end
626
+ it 'raises an error when its symbolized argument is not a valid value for the enum' do
627
+ m = mock
628
+ m.expects(:to_sym).returns :NAN
629
+ assert_raises RangeError do
630
+ decorator.number?(m)
631
+ end
632
+ end
633
+ end
634
+
635
+ describe 'for a wrapped enum' do
636
+ let :enum_message do
637
+ enum_message_class.new value: 1
638
+ end
639
+
640
+ before do
641
+ transformer.stubs(:to_object).
642
+ with(enum_message, anything).
643
+ returns :ONE
644
+ decorated_message.number_message = enum_message
645
+ end
646
+
647
+ it 'returns true when its symbolized argument matches the value' do
648
+ m = mock
649
+ m.expects(:to_sym).returns :ONE
650
+ assert decorator.number_message?(m)
651
+ end
652
+
653
+ it 'returns false when its symbolized argument does not match the value' do
654
+ m = mock
655
+ m.expects(:to_sym).returns :TWO
656
+ refute decorator.number_message?(m)
657
+ end
658
+
659
+ it 'returns true when a Fixnum argument matches the value' do
660
+ assert decorator.number_message?(1)
661
+ end
662
+
663
+ end
664
+ end
665
+ end