protip 0.20.7 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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