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