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.
- checksums.yaml +4 -4
- data/lib/protip.rb +10 -0
- data/lib/protip/{wrapper.rb → decorator.rb} +98 -82
- data/lib/protip/extensions.rb +0 -1
- data/lib/protip/resource.rb +56 -32
- data/lib/protip/resource/extra_methods.rb +5 -3
- data/lib/protip/tasks/compile.rake +2 -2
- data/lib/protip/transformer.rb +14 -0
- data/lib/protip/transformers/abstract_transformer.rb +11 -0
- data/lib/protip/transformers/active_support/time_with_zone_transformer.rb +35 -0
- data/lib/protip/transformers/decorating_transformer.rb +33 -0
- data/lib/protip/transformers/default_transformer.rb +22 -0
- data/lib/protip/transformers/delegating_transformer.rb +44 -0
- data/lib/protip/transformers/deprecated_transformer.rb +90 -0
- data/lib/protip/transformers/enum_transformer.rb +93 -0
- data/lib/protip/transformers/primitives_transformer.rb +78 -0
- data/lib/protip/transformers/timestamp_transformer.rb +31 -0
- data/test/functional/protip/decorator_test.rb +204 -0
- data/test/unit/protip/decorator_test.rb +665 -0
- data/test/unit/protip/resource_test.rb +76 -92
- data/test/unit/protip/transformers/decorating_transformer_test.rb +92 -0
- data/test/unit/protip/transformers/delegating_transformer_test.rb +83 -0
- data/test/unit/protip/transformers/deprecated_transformer_test.rb +139 -0
- data/test/unit/protip/transformers/enum_transformer_test.rb +157 -0
- data/test/unit/protip/transformers/primitives_transformer_test.rb +139 -0
- data/test/unit/protip/transformers/timestamp_transformer_test.rb +46 -0
- metadata +23 -12
- data/definitions/google/protobuf/wrappers.proto +0 -99
- data/lib/google/protobuf/descriptor.rb +0 -1
- data/lib/google/protobuf/wrappers.rb +0 -48
- data/lib/protip/converter.rb +0 -22
- data/lib/protip/standard_converter.rb +0 -185
- data/test/unit/protip/standard_converter_test.rb +0 -502
- 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
|