protip 0.20.7 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/protip.rb +10 -0
  3. data/lib/protip/{wrapper.rb → decorator.rb} +98 -82
  4. data/lib/protip/extensions.rb +0 -1
  5. data/lib/protip/resource.rb +56 -32
  6. data/lib/protip/resource/extra_methods.rb +5 -3
  7. data/lib/protip/tasks/compile.rake +2 -2
  8. data/lib/protip/transformer.rb +14 -0
  9. data/lib/protip/transformers/abstract_transformer.rb +11 -0
  10. data/lib/protip/transformers/active_support/time_with_zone_transformer.rb +35 -0
  11. data/lib/protip/transformers/decorating_transformer.rb +33 -0
  12. data/lib/protip/transformers/default_transformer.rb +22 -0
  13. data/lib/protip/transformers/delegating_transformer.rb +44 -0
  14. data/lib/protip/transformers/deprecated_transformer.rb +90 -0
  15. data/lib/protip/transformers/enum_transformer.rb +93 -0
  16. data/lib/protip/transformers/primitives_transformer.rb +78 -0
  17. data/lib/protip/transformers/timestamp_transformer.rb +31 -0
  18. data/test/functional/protip/decorator_test.rb +204 -0
  19. data/test/unit/protip/decorator_test.rb +665 -0
  20. data/test/unit/protip/resource_test.rb +76 -92
  21. data/test/unit/protip/transformers/decorating_transformer_test.rb +92 -0
  22. data/test/unit/protip/transformers/delegating_transformer_test.rb +83 -0
  23. data/test/unit/protip/transformers/deprecated_transformer_test.rb +139 -0
  24. data/test/unit/protip/transformers/enum_transformer_test.rb +157 -0
  25. data/test/unit/protip/transformers/primitives_transformer_test.rb +139 -0
  26. data/test/unit/protip/transformers/timestamp_transformer_test.rb +46 -0
  27. metadata +23 -12
  28. data/definitions/google/protobuf/wrappers.proto +0 -99
  29. data/lib/google/protobuf/descriptor.rb +0 -1
  30. data/lib/google/protobuf/wrappers.rb +0 -48
  31. data/lib/protip/converter.rb +0 -22
  32. data/lib/protip/standard_converter.rb +0 -185
  33. data/test/unit/protip/standard_converter_test.rb +0 -502
  34. data/test/unit/protip/wrapper_test.rb +0 -646
@@ -0,0 +1,139 @@
1
+ require 'test_helper'
2
+
3
+ require 'protip/transformers/deprecated_transformer'
4
+
5
+ require 'protip/messages'
6
+
7
+ describe Protip::Transformers::DeprecatedTransformer do
8
+ let(:transformer) { Protip::Transformers::DeprecatedTransformer.new }
9
+ let(:message_class) { raise NotImplementedError } # sub-sections must define
10
+ let(:field) do
11
+ field = mock.responds_like_instance_of ::Google::Protobuf::FieldDescriptor
12
+ field.stubs(:submsg_name).returns(message_class.descriptor.name)
13
+ field.stubs(:subtype).returns(message_class.descriptor)
14
+ field
15
+ end
16
+
17
+ describe '#to_object' do
18
+ describe 'Date' do
19
+ let(:message_class) { Protip::Messages::Date }
20
+ it 'converts dates' do
21
+ date = transformer.to_object(message_class.new(year: 2015, month: 2, day: 9), field)
22
+ assert_instance_of ::Date, date
23
+ assert_equal '2015-02-09', date.strftime
24
+ end
25
+ end
26
+
27
+ describe 'Range' do
28
+ let(:message_class) { Protip::Messages::Range }
29
+ it 'converts ranges' do
30
+ range = transformer.to_object(message_class.new(begin: 1, end: 4), field)
31
+ assert_instance_of ::Range, range
32
+ assert_equal 1..4, range
33
+ end
34
+ end
35
+
36
+ describe 'Currency' do
37
+ let(:message_class) { Protip::Messages::Currency }
38
+ it 'converts currency' do
39
+ currency = transformer.to_object(message_class.new(currency_code: :GBP), field)
40
+ assert_equal :GBP, currency
41
+ end
42
+ end
43
+
44
+ describe 'Money' do
45
+ let(:message_class) { Protip::Messages::Money }
46
+ it 'converts money' do
47
+ message = message_class.new amount_cents: 250,
48
+ currency: (Protip::Messages::Currency.new currency_code: :CAD)
49
+ money = transformer.to_object(message, field)
50
+ assert_instance_of ::Money, money
51
+ assert_equal ::Money::Currency.new(:CAD), money.currency
52
+ assert_equal 250, money.fractional
53
+ assert_equal ::Money.new(250, 'CAD'), money
54
+ end
55
+ end
56
+
57
+ describe 'ActiveSupport::TimeWithZone' do
58
+ let(:message_class) { Protip::Messages::ActiveSupport::TimeWithZone }
59
+ it 'converts times with zones' do
60
+ message = message_class.new utc_timestamp: 1451610000,
61
+ time_zone_name: 'America/Los_Angeles'
62
+ time = transformer.to_object(message, field)
63
+ assert_instance_of ::ActiveSupport::TimeWithZone, time
64
+ assert_equal 1451610000, time.to_i
65
+ assert_equal '2015-12-31T17:00:00-08:00', time.iso8601
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#to_message' do
71
+ describe 'Date' do
72
+ let(:message_class) { Protip::Messages::Date }
73
+ it 'converts dates' do
74
+ date = ::Date.new(2012, 5, 7)
75
+ assert_equal 7, date.day # Sanity check argument order
76
+ assert_equal message_class.new(year: 2012, month: 5, day: 7),
77
+ transformer.to_message(date, field)
78
+ end
79
+ end
80
+
81
+ describe 'Range' do
82
+ let(:message_class) { Protip::Messages::Range }
83
+ it 'converts ranges' do
84
+ range = -1..34
85
+ assert_equal message_class.new(begin: -1, end: 34),
86
+ transformer.to_message(range, field)
87
+ end
88
+ end
89
+
90
+ describe 'Currency' do
91
+ let(:message_class) { Protip::Messages::Currency }
92
+ it 'converts currency' do
93
+ currency = :HKD
94
+ message = transformer.to_message(currency, field)
95
+ assert_equal message_class.new(currency_code: currency), message
96
+ end
97
+ end
98
+
99
+ describe 'Money' do
100
+ let(:message_class) { Protip::Messages::Money }
101
+ it 'converts money' do
102
+ money = ::Money.new(250, 'CAD')
103
+ message = transformer.to_message(money, field)
104
+ assert_instance_of message_class, message
105
+ assert_equal message_class.new(
106
+ amount_cents: money.cents,
107
+ currency: Protip::Messages::Currency.new(
108
+ currency_code: money.currency.iso_code.to_sym
109
+ )),
110
+ message
111
+ end
112
+ end
113
+
114
+ describe 'ActiveSupport::TimeWithZone' do
115
+ let(:message_class) { Protip::Messages::ActiveSupport::TimeWithZone }
116
+ it 'converts times with zones' do
117
+ time_with_zone = ::ActiveSupport::TimeWithZone.new(Time.new(2016, 1, 1, 0, 0, 0, 0),
118
+ ::ActiveSupport::TimeZone.new('America/New_York'))
119
+ message = transformer.to_message(time_with_zone, field)
120
+ assert_equal 1451606400, message.utc_timestamp
121
+ assert_equal 'America/New_York', message.time_zone_name
122
+ end
123
+
124
+ it 'converts times without zones' do
125
+ time = ::Time.new(2016, 1, 1, 0, 0, 0, -3600)
126
+ message = transformer.to_message(time, field)
127
+ assert_equal 1451610000, message.utc_timestamp
128
+ assert_equal 'UTC', message.time_zone_name
129
+ end
130
+
131
+ it 'converts datetimes without zones' do
132
+ datetime = ::DateTime.new(2016, 1, 1, 0, 0, 0, '-1')
133
+ message = transformer.to_message(datetime, field)
134
+ assert_equal 1451610000, message.utc_timestamp
135
+ assert_equal 'UTC', message.time_zone_name
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,157 @@
1
+ require 'test_helper'
2
+
3
+ require 'protip/transformers/enum_transformer'
4
+
5
+ require 'protip/messages'
6
+
7
+ describe Protip::Transformers::EnumTransformer do
8
+ let(:transformer) { Protip::Transformers::EnumTransformer.new }
9
+ let(:pool) do
10
+ # See https://github.com/google/protobuf/blob/master/ruby/tests/generated_code.rb for
11
+ # examples of field types you can add here
12
+ pool = Google::Protobuf::DescriptorPool.new
13
+ pool.build do
14
+ add_enum 'number' do
15
+ value :ZERO, 0
16
+ value :ONE, 1
17
+ end
18
+ end
19
+ pool
20
+ end
21
+ let(:enum) { pool.lookup 'number' }
22
+ let(:message_class) { raise NotImplementedError } # sub-sections must define
23
+ let(:field) do
24
+ field = mock.responds_like_instance_of ::Google::Protobuf::FieldDescriptor
25
+ field.stubs(:submsg_name).returns(message_class.descriptor.name)
26
+ field.stubs(:subtype).returns(message_class.descriptor)
27
+ field
28
+ end
29
+
30
+ before do
31
+ Protip::Transformers::EnumTransformer.stubs(:enum_for_field).
32
+ returns(enum)
33
+ end
34
+
35
+ describe '#to_object' do
36
+ describe 'scalars' do
37
+ let(:message_class) { Protip::Messages::EnumValue }
38
+ it 'transforms enum values in range to symbols' do
39
+ message = message_class.new(value: 1)
40
+ assert_equal :ONE, transformer.to_object(message, field)
41
+ end
42
+ it 'transforms enum values out of range to integers' do
43
+ message = message_class.new(value: 5)
44
+ assert_equal 5, transformer.to_object(message, field)
45
+ end
46
+ end
47
+ describe 'arrays' do
48
+ let(:message_class) { Protip::Messages::RepeatedEnum }
49
+ it 'transforms repeated enum values in range to symbols' do
50
+ message = message_class.new(values: [0, 1])
51
+ assert_equal [:ZERO, :ONE], transformer.to_object(message, field)
52
+ end
53
+ it 'transforms repeated enum values out of range to integers' do
54
+ message = message_class.new(values: [3, 1, 5])
55
+ assert_equal [3, :ONE, 5], transformer.to_object(message, field)
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#to_message' do
61
+ %w(zero one two).each do |number| # values symbolizing as :ZERO, :ONE, :TWO
62
+ let number do
63
+ value = mock
64
+ value.stubs(:to_sym).returns(number.upcase.to_sym)
65
+ value
66
+ end
67
+ end
68
+
69
+ describe 'scalars' do
70
+ let(:message_class) { Protip::Messages::EnumValue }
71
+ it 'transforms integers to messages' do
72
+ assert_equal message_class.new(value: 1), transformer.to_message(1, field)
73
+ end
74
+ it 'transforms non-integers via :to_sym' do
75
+ assert_equal message_class.new(value: 1), transformer.to_message(one, field)
76
+ end
77
+ it 'throws an error when an out-of-range symbol is given' do
78
+ field.stubs(:name).returns('FOO') # The exception message contains this
79
+ exception = assert_raises RangeError do
80
+ transformer.to_message(two, field)
81
+ end
82
+ assert_match(/FOO/, exception.message)
83
+ end
84
+ end
85
+
86
+ describe 'arrays' do
87
+ let(:message_class) { Protip::Messages::RepeatedEnum }
88
+ it 'transforms integers' do
89
+ assert_equal message_class.new(values: [1, 4]),
90
+ transformer.to_message([1, 4], field)
91
+ end
92
+ it 'transforms non-integers via :to_sym' do
93
+ assert_equal message_class.new(values: [0, 2, 1]),
94
+ transformer.to_message([zero, 2, one], field)
95
+ end
96
+ it 'throws an error when an out-of-range symbol is given' do
97
+ field.stubs(:name).returns('FOO') # The exception message contains this
98
+ exception = assert_raises RangeError do
99
+ transformer.to_message([0, two], field)
100
+ end
101
+ assert_match(/FOO/, exception.message)
102
+ end
103
+ it 'allows assigning a scalar value' do
104
+ assert_equal message_class.new(values: [1]), transformer.to_message(one, field)
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ describe '.enum_for_field' do
111
+ # TODO pending https://github.com/google/protobuf/issues/1198
112
+ end
113
+
114
+ if false #describe '(functional)' do # Temp - test an actual compiled file to make sure our options hack is working
115
+ require 'protip/messages/test'
116
+ require 'protip/wrapper'
117
+ let(:wrapped_message) { Protip::Messages::EnumTest.new }
118
+ let(:wrapper) { Protip::Wrapper.new wrapped_message, transformer }
119
+
120
+ let(:value_map) do
121
+ {
122
+ :ONE => :ONE,
123
+ 1 => :ONE,
124
+ 2 => 2,
125
+ }
126
+ end
127
+
128
+ it 'allows setting and getting a scalar field by Ruby value' do
129
+ value_map.each do |value, expected|
130
+ wrapper.enum = value
131
+ assert_equal expected, wrapper.enum
132
+ end
133
+ assert_raises RangeError do
134
+ wrapper.enum = :TWO
135
+ end
136
+ end
137
+ it 'allows setting and getting a scalar field by message' do
138
+ wrapper.enum = ::Protip::Messages::EnumValue.new(value: 1)
139
+ assert_equal :ONE, wrapper.enum
140
+ end
141
+
142
+ it 'allows setting and getting a repeated field by Ruby value' do
143
+ value_map.each do |value, expected|
144
+ wrapper.repeated_enums = [value]
145
+ assert_equal [expected], wrapper.repeated_enums
146
+ end
147
+ assert_raises RangeError do
148
+ wrapper.repeated_enums = [:TWO]
149
+ end
150
+ end
151
+ it 'allows setting and geting a repeated field by message' do
152
+ wrapper.repeated_enums = ::Protip::Messages::RepeatedEnum.new(values: [2])
153
+ assert_equal [2], wrapper.repeated_enums
154
+ end
155
+ end
156
+
157
+ end
@@ -0,0 +1,139 @@
1
+ require 'test_helper'
2
+ require 'base64'
3
+
4
+ require 'protip/transformers/primitives_transformer'
5
+
6
+ require 'google/protobuf/wrappers'
7
+ require 'protip/messages'
8
+
9
+ describe ::Protip::Transformers::PrimitivesTransformer do
10
+ let(:transformer) { ::Protip::Transformers::PrimitivesTransformer.new }
11
+ let(:message_class) { raise NotImplementedError } # sub-sections must define
12
+ let(:field) do
13
+ field = mock.responds_like_instance_of ::Google::Protobuf::FieldDescriptor
14
+ field.stubs(:submsg_name).returns(message_class.descriptor.name)
15
+ field.stubs(:subtype).returns(message_class.descriptor)
16
+ field
17
+ end
18
+
19
+ INTEGER_TYPES = %w(Int64 Int32 UInt64 UInt32)
20
+ FLOAT_TYPES = %w(Float Double)
21
+ STRING_TYPES = %w(String)
22
+ BOOLEAN_TYPES = %w(Bool)
23
+ BYTES_TYPES = %w(Bytes)
24
+
25
+ BYTES_VALUE = Base64.decode64("U2VuZCByZWluZm9yY2VtZW50cw==\n")
26
+
27
+ {
28
+ 6 => INTEGER_TYPES,
29
+ 5.5 => FLOAT_TYPES,
30
+ 'foo' => STRING_TYPES,
31
+ true => BOOLEAN_TYPES,
32
+ BYTES_VALUE => BYTES_TYPES,
33
+ }.each do |value, types|
34
+ types.each do |type|
35
+ describe '#to_object' do
36
+ describe "google.protobuf.#{type}Value" do
37
+ let(:message_class) { Google::Protobuf.const_get("#{type}Value") }
38
+ it 'converts scalar messages' do
39
+ assert_equal value, transformer.to_object(message_class.new(value: value), field)
40
+ end
41
+ end
42
+
43
+ describe "protip.messages.Repeated#{type}" do
44
+ let(:message_class) { Protip::Messages.const_get("Repeated#{type}") }
45
+ it 'converts repeated mesages to an immutable array' do
46
+ result = transformer.to_object(message_class.new(values: [value, value]), field)
47
+ assert_equal [value, value], result
48
+
49
+ exception = assert_raises RuntimeError do
50
+ result << value
51
+ end
52
+ assert_equal 'can\'t modify frozen Array', exception.message
53
+ end
54
+ end
55
+
56
+ describe '#to_message' do
57
+ # Keys are the actual value that should be set on the message,
58
+ # values are an object of a different type that should be converted.
59
+ # Used for testing that e.g. integer types can be set using numeric strings.
60
+ if INTEGER_TYPES.include?(type) || FLOAT_TYPES.include?(type)
61
+ let(:native_to_non_native) { {value => value.to_s} }
62
+ elsif STRING_TYPES.include?(type)
63
+ let(:native_to_non_native) { {'3' => 3} }
64
+ elsif BOOLEAN_TYPES.include?(type)
65
+ let(:native_to_non_native) { {true => 'on', false => 'off'} }
66
+ else
67
+ let(:native_to_non_native) { {} }
68
+ end
69
+
70
+ describe "google.protobuf.#{type}Value" do
71
+ let(:message_class) { Google::Protobuf.const_get("#{type}Value") }
72
+ it 'converts scalar types to a message' do
73
+ assert_equal message_class.new(value: value), transformer.to_message(value, field)
74
+ end
75
+
76
+ it "converts non-#{type} types to a #{type} message" do
77
+ native_to_non_native.each do |native, non_native|
78
+ assert_equal message_class.new(value: native),
79
+ transformer.to_message(non_native, field)
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "protip.messages.Repeated#{type}" do
85
+ let(:message_class) { Protip::Messages.const_get("Repeated#{type}") }
86
+
87
+ it 'converts repeated types when given a scalar' do
88
+ assert_equal message_class.new(values: [value]),
89
+ transformer.to_message(value, field)
90
+ end
91
+
92
+ it 'converts repeated types when given an array' do
93
+ assert_equal message_class.new(values: [value, value]),
94
+ transformer.to_message([value, value], field)
95
+ end
96
+
97
+ it "converts non-#{type} scalar types to a repeated #{type} message" do
98
+ native_to_non_native.each do |native, non_native|
99
+ assert_equal message_class.new(values: [native]),
100
+ transformer.to_message(non_native, field)
101
+ end
102
+ end
103
+
104
+ it "converts non-#{type} array types to a repeated #{type} message" do
105
+ native_to_non_native.each do |native, non_native|
106
+ assert_equal message_class.new(values: [native, native]),
107
+ transformer.to_message([non_native, non_native], field)
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '#to_message' do
117
+ let(:message_class) { Google::Protobuf::BoolValue }
118
+ it 'converts all truthy values to booleans' do
119
+ [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].each do |truth_value|
120
+ assert_equal Google::Protobuf::BoolValue.new(value: true),
121
+ transformer.to_message(truth_value, field)
122
+ end
123
+ end
124
+ it 'converts all falsey values to booleans' do
125
+ [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].each do |false_value|
126
+ assert_equal Google::Protobuf::BoolValue.new(value: false),
127
+ transformer.to_message(false_value, field)
128
+ end
129
+ end
130
+
131
+ it 'raises an exception if non-boolean values passed to a boolean field' do
132
+ [nil, 'test', Object.new, 2, {}, []].each do |bad_value|
133
+ assert_raises TypeError do
134
+ transformer.to_message(bad_value, field)
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+ require 'protip/transformers/timestamp_transformer'
3
+ require 'google/protobuf/timestamp'
4
+
5
+ describe Protip::Transformers::TimestampTransformer do
6
+ let(:transformer) { Protip::Transformers::TimestampTransformer.new }
7
+ let(:field) do
8
+ field = mock.responds_like_instance_of Google::Protobuf::FieldDescriptor
9
+ descriptor = Google::Protobuf::Timestamp.descriptor
10
+ field.stubs(:submsg_name).returns(descriptor.name)
11
+ field.stubs(:subtype).returns(descriptor)
12
+ field
13
+ end
14
+
15
+ describe '#to_object' do
16
+ it 'creates a timestamp' do
17
+ message = Google::Protobuf::Timestamp.new(seconds: 1415, nanos: 12345678)
18
+ result = transformer.to_object(message, field)
19
+ assert_instance_of ::Time, result
20
+ assert_equal 1415, result.to_i
21
+ assert_equal 12345678, result.nsec
22
+ end
23
+ end
24
+
25
+ describe '#to_message' do
26
+ let(:timestamp) do
27
+ ::Time.at(601, 1)
28
+ end
29
+ let(:expected_message) do
30
+ Google::Protobuf::Timestamp.new(
31
+ seconds: 601,
32
+ nanos: 1000,
33
+ )
34
+ end
35
+ it 'converts times directly' do
36
+ assert_equal expected_message,
37
+ transformer.to_message(timestamp, field)
38
+ end
39
+ it 'converts non-times via :to_time' do
40
+ object = mock 'object'
41
+ object.expects(:to_time).once.returns(timestamp)
42
+ assert_equal expected_message,
43
+ transformer.to_message(object, field)
44
+ end
45
+ end
46
+ end