amq-protocol 2.3.4 → 2.4.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.
@@ -4,10 +4,18 @@ RSpec.describe AMQ::Pack do
4
4
  context "16-bit big-endian packing / unpacking" do
5
5
  let(:examples_16bit) {
6
6
  {
7
- 0x068D => "\x06\x8D" # 1677
7
+ 0x068D => "\x06\x8D", # 1677
8
+ 0x0000 => "\x00\x00", # 0
9
+ 0x7FFF => "\x7F\xFF" # 32767 (max positive signed 16-bit)
8
10
  }
9
11
  }
10
12
 
13
+ it "packs signed integers into a big-endian string" do
14
+ examples_16bit.each do |key, value|
15
+ expect(described_class.pack_int16_big_endian(key)).to eq(value)
16
+ end
17
+ end
18
+
11
19
  it "unpacks signed integers from a string to a number" do
12
20
  examples_16bit.each do |key, value|
13
21
  expect(described_class.unpack_int16_big_endian(value)[0]).to eq(key)
@@ -35,34 +43,16 @@ RSpec.describe AMQ::Pack do
35
43
  end
36
44
  end
37
45
 
38
- it "should unpack string representation into integer" do
46
+ it "unpacks string representation into integer" do
39
47
  examples.each do |key, value|
40
48
  expect(described_class.unpack_uint64_big_endian(value)[0]).to eq(key)
41
49
  end
42
50
  end
51
+ end
43
52
 
44
- if RUBY_VERSION < '1.9'
45
- describe "with utf encoding" do
46
- before do
47
- $KCODE = 'u'
48
- end
49
-
50
- after do
51
- $KCODE = 'NONE'
52
- end
53
-
54
- it "packs integers into big-endian string" do
55
- examples.each do |key, value|
56
- expect(described_class.pack_uint64_big_endian(key)).to eq(value)
57
- end
58
- end
59
-
60
- it "should unpack string representation into integer" do
61
- examples.each do |key, value|
62
- expect(described_class.unpack_uint64_big_endian(value)[0]).to eq(key)
63
- end
64
- end
65
- end
53
+ describe "AMQ::Hacks alias" do
54
+ it "is an alias for AMQ::Pack (backwards compatibility)" do
55
+ expect(AMQ::Hacks).to eq(AMQ::Pack)
66
56
  end
67
57
  end
68
58
  end
@@ -0,0 +1,70 @@
1
+ # encoding: binary
2
+
3
+ RSpec.describe AMQ::Protocol::Error do
4
+ describe ".[]" do
5
+ it "looks up exception class by error code" do
6
+ # This only works if subclasses define VALUE constant
7
+ # Default case: no subclass with VALUE defined returns nil
8
+ expect { described_class[999999] }.to raise_error(/No such exception class/)
9
+ end
10
+ end
11
+
12
+ describe ".subclasses_with_values" do
13
+ it "returns subclasses that define VALUE constant" do
14
+ result = described_class.subclasses_with_values
15
+ expect(result).to be_an(Array)
16
+ end
17
+ end
18
+
19
+ describe "#initialize" do
20
+ it "uses default message when none provided" do
21
+ error = described_class.new
22
+ expect(error.message).to eq("AMQP error")
23
+ end
24
+
25
+ it "uses custom message when provided" do
26
+ error = described_class.new("Custom error")
27
+ expect(error.message).to eq("Custom error")
28
+ end
29
+ end
30
+ end
31
+
32
+ RSpec.describe AMQ::Protocol::FrameTypeError do
33
+ it "formats message with valid types" do
34
+ error = described_class.new([:method, :headers])
35
+ expect(error.message).to include("[:method, :headers]")
36
+ end
37
+ end
38
+
39
+ RSpec.describe AMQ::Protocol::EmptyResponseError do
40
+ it "has a default message" do
41
+ error = described_class.new
42
+ expect(error.message).to eq("Empty response received from the server.")
43
+ end
44
+
45
+ it "accepts custom message" do
46
+ error = described_class.new("Custom empty response")
47
+ expect(error.message).to eq("Custom empty response")
48
+ end
49
+ end
50
+
51
+ RSpec.describe AMQ::Protocol::BadResponseError do
52
+ it "formats message with argument, expected, and actual values" do
53
+ error = described_class.new("channel", 1, 2)
54
+ expect(error.message).to include("channel")
55
+ expect(error.message).to include("1")
56
+ expect(error.message).to include("2")
57
+ end
58
+ end
59
+
60
+ RSpec.describe AMQ::Protocol::SoftError do
61
+ it "is a subclass of Protocol::Error" do
62
+ expect(described_class.superclass).to eq(AMQ::Protocol::Error)
63
+ end
64
+ end
65
+
66
+ RSpec.describe AMQ::Protocol::HardError do
67
+ it "is a subclass of Protocol::Error" do
68
+ expect(described_class.superclass).to eq(AMQ::Protocol::Error)
69
+ end
70
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: binary
2
+
3
+ RSpec.describe AMQ::Protocol::Float32Bit do
4
+ describe "#initialize" do
5
+ it "stores the value" do
6
+ f = described_class.new(3.14)
7
+ expect(f.value).to eq(3.14)
8
+ end
9
+ end
10
+
11
+ describe "#value" do
12
+ it "returns the stored value" do
13
+ f = described_class.new(2.718)
14
+ expect(f.value).to eq(2.718)
15
+ end
16
+
17
+ it "works with zero" do
18
+ f = described_class.new(0.0)
19
+ expect(f.value).to eq(0.0)
20
+ end
21
+
22
+ it "works with negative values" do
23
+ f = described_class.new(-1.5)
24
+ expect(f.value).to eq(-1.5)
25
+ end
26
+ end
27
+ end
@@ -86,6 +86,70 @@ module AMQ
86
86
  expect(subject.properties[:delivery_mode]).to eq(2)
87
87
  expect(subject.properties[:priority]).to eq(0)
88
88
  end
89
+
90
+ it "is not final" do
91
+ expect(subject.final?).to eq(false)
92
+ end
93
+ end
94
+
95
+ describe BodyFrame do
96
+ subject { BodyFrame.new("test payload", 1) }
97
+
98
+ it "returns payload as decode_payload" do
99
+ expect(subject.decode_payload).to eq("test payload")
100
+ end
101
+
102
+ it "is not final" do
103
+ expect(subject.final?).to eq(false)
104
+ end
105
+
106
+ it "has correct size" do
107
+ expect(subject.size).to eq(12)
108
+ end
109
+ end
110
+
111
+ describe HeartbeatFrame do
112
+ it "encodes with empty payload on channel 0" do
113
+ encoded = HeartbeatFrame.encode
114
+ expect(encoded.bytes.last).to eq(0xCE)
115
+ end
116
+
117
+ it "is final" do
118
+ frame = HeartbeatFrame.new("", 0)
119
+ expect(frame.final?).to eq(true)
120
+ end
121
+ end
122
+
123
+ describe MethodFrame do
124
+ it "is not final when method has content" do
125
+ # Basic.Publish has content
126
+ payload = "\x00\x3C\x00\x28\x00\x00\x00\x00\x00"
127
+ frame = MethodFrame.new(payload, 1)
128
+ # This will depend on the method class
129
+ expect(frame).to respond_to(:final?)
130
+ end
131
+ end
132
+
133
+ describe FrameSubclass do
134
+ subject { BodyFrame.new("test", 1) }
135
+
136
+ it "has channel accessor" do
137
+ expect(subject.channel).to eq(1)
138
+ subject.channel = 2
139
+ expect(subject.channel).to eq(2)
140
+ end
141
+
142
+ it "encodes to array" do
143
+ result = subject.encode_to_array
144
+ expect(result).to be_an(Array)
145
+ expect(result.size).to eq(3)
146
+ end
147
+
148
+ it "encodes to string" do
149
+ result = subject.encode
150
+ expect(result).to be_a(String)
151
+ expect(result.bytes.last).to eq(0xCE)
152
+ end
89
153
  end
90
154
  end
91
155
  end
@@ -254,6 +254,38 @@ module AMQ
254
254
  end
255
255
 
256
256
  end # describe
257
+
258
+ describe ".length" do
259
+ it "returns the table length from binary data" do
260
+ encoded = Table.encode({"test" => 1})
261
+ expect(Table.length(encoded)).to eq(14)
262
+ end
263
+
264
+ it "returns 0 for empty table" do
265
+ encoded = Table.encode({})
266
+ expect(Table.length(encoded)).to eq(0)
267
+ end
268
+ end
269
+
270
+ describe ".hash_size" do
271
+ it "calculates size for simple hash" do
272
+ size = Table.hash_size({"key" => "value"})
273
+ expect(size).to be > 0
274
+ end
275
+
276
+ it "returns 0 for empty hash" do
277
+ size = Table.hash_size({})
278
+ expect(size).to eq(0)
279
+ end
280
+ end
281
+
282
+ describe "Table::InvalidTableError" do
283
+ it "formats error message with key and value" do
284
+ error = Table::InvalidTableError.new("mykey", Object.new)
285
+ expect(error.message).to include("mykey")
286
+ expect(error.message).to include("Object")
287
+ end
288
+ end
257
289
  end
258
290
  end
259
291
  end
@@ -81,6 +81,103 @@ module AMQ
81
81
  end
82
82
  end
83
83
  end
84
+
85
+ describe ".decode_string" do
86
+ it "decodes string values" do
87
+ # 4 bytes length + string content
88
+ data = "\x00\x00\x00\x05hello"
89
+ value, offset = described_class.decode_string(data, 0)
90
+ expect(value).to eq("hello")
91
+ expect(offset).to eq(9)
92
+ end
93
+ end
94
+
95
+ describe ".decode_integer" do
96
+ it "decodes 32-bit unsigned integers" do
97
+ data = "\x00\x00\x00\x0A"
98
+ value, offset = described_class.decode_integer(data, 0)
99
+ expect(value).to eq(10)
100
+ expect(offset).to eq(4)
101
+ end
102
+ end
103
+
104
+ describe ".decode_long" do
105
+ it "decodes 64-bit signed integers" do
106
+ data = "\x00\x00\x00\x00\x00\x00\x00\x0A"
107
+ value, offset = described_class.decode_long(data, 0)
108
+ expect(value).to eq(10)
109
+ expect(offset).to eq(8)
110
+ end
111
+ end
112
+
113
+ describe ".decode_time" do
114
+ it "decodes timestamp values" do
115
+ timestamp = Time.now.to_i
116
+ data = [timestamp].pack('Q>')
117
+ value, offset = described_class.decode_time(data, 0)
118
+ expect(value.to_i).to eq(timestamp)
119
+ expect(offset).to eq(8)
120
+ end
121
+ end
122
+
123
+ describe ".decode_boolean" do
124
+ it "decodes true" do
125
+ value, offset = described_class.decode_boolean("\x01", 0)
126
+ expect(value).to eq(true)
127
+ expect(offset).to eq(1)
128
+ end
129
+
130
+ it "decodes false" do
131
+ value, offset = described_class.decode_boolean("\x00", 0)
132
+ expect(value).to eq(false)
133
+ expect(offset).to eq(1)
134
+ end
135
+ end
136
+
137
+ describe ".decode_64bit_float" do
138
+ it "decodes 64-bit floats" do
139
+ data = [3.14159].pack('G')
140
+ value, offset = described_class.decode_64bit_float(data, 0)
141
+ expect(value).to be_within(0.00001).of(3.14159)
142
+ expect(offset).to eq(8)
143
+ end
144
+ end
145
+
146
+ describe ".decode_value_type" do
147
+ it "returns the type byte and incremented offset" do
148
+ data = "Shello"
149
+ type, offset = described_class.decode_value_type(data, 0)
150
+ expect(type).to eq("S")
151
+ expect(offset).to eq(1)
152
+ end
153
+ end
154
+
155
+ describe ".decode_short" do
156
+ it "decodes 16-bit signed integers" do
157
+ data = "\x06\x8D"
158
+ value, offset = described_class.decode_short(data, 0)
159
+ expect(value).to eq(1677)
160
+ expect(offset).to eq(2)
161
+ end
162
+ end
163
+
164
+ describe ".decode_big_decimal" do
165
+ it "decodes BigDecimal values" do
166
+ # Scale 2, raw value 100 = 1.00
167
+ data = "\x02\x00\x00\x00\x64"
168
+ value, offset = described_class.decode_big_decimal(data, 0)
169
+ expect(value).to be_a(BigDecimal)
170
+ expect(offset).to eq(5)
171
+ end
172
+ end
173
+
174
+ describe ".decode_hash" do
175
+ it "decodes nested hash values" do
176
+ encoded = AMQ::Protocol::Table.encode({"nested" => "value"})
177
+ value, _offset = described_class.decode_hash(encoded, 0)
178
+ expect(value).to eq({"nested" => "value"})
179
+ end
180
+ end
84
181
  end
85
182
  end
86
183
  end
@@ -134,6 +134,27 @@ module AMQ
134
134
  expect(described_class.encode(input4).bytesize).to eq(69)
135
135
  end
136
136
 
137
+ it "encodes symbols as strings" do
138
+ encoded = described_class.encode(:test_symbol)
139
+ expect(encoded).to start_with("S")
140
+ expect(encoded).to include("test_symbol")
141
+ end
142
+
143
+ it "encodes BigDecimal values" do
144
+ bd = BigDecimal("123.45")
145
+ encoded = described_class.encode(bd)
146
+ expect(encoded).to start_with("D")
147
+ end
148
+
149
+ it "encodes BigDecimal with zero exponent" do
150
+ bd = BigDecimal("100")
151
+ encoded = described_class.encode(bd)
152
+ expect(encoded).to start_with("D")
153
+ end
154
+
155
+ it "raises ArgumentError for unsupported types" do
156
+ expect { described_class.encode(Object.new) }.to raise_error(ArgumentError)
157
+ end
137
158
 
138
159
  end # TableValueEncoder
139
160
  end # Protocol
@@ -6,9 +6,18 @@ RSpec.describe AMQ::Settings do
6
6
  expect(AMQ::Settings.default).to_not be_nil
7
7
  expect(AMQ::Settings.default[:host]).to_not be_nil
8
8
  end
9
+
10
+ it "includes default port" do
11
+ expect(AMQ::Settings.default[:port]).to eq(5672)
12
+ end
13
+
14
+ it "includes default credentials" do
15
+ expect(AMQ::Settings.default[:user]).to eq("guest")
16
+ expect(AMQ::Settings.default[:pass]).to eq("guest")
17
+ end
9
18
  end
10
19
 
11
- describe ".configure(&block)" do
20
+ describe ".configure" do
12
21
  it "should merge custom settings with default settings" do
13
22
  settings = AMQ::Settings.configure(:host => "tagadab")
14
23
  expect(settings[:host]).to eql("tagadab")
@@ -18,5 +27,32 @@ RSpec.describe AMQ::Settings do
18
27
  settings = AMQ::Settings.configure("amqp://tagadab")
19
28
  expect(settings[:host]).to eql("tagadab")
20
29
  end
30
+
31
+ it "returns default when passed nil" do
32
+ settings = AMQ::Settings.configure(nil)
33
+ expect(settings).to eq(AMQ::Settings.default)
34
+ end
35
+
36
+ it "normalizes username to user" do
37
+ settings = AMQ::Settings.configure(:username => "admin")
38
+ expect(settings[:user]).to eq("admin")
39
+ end
40
+
41
+ it "normalizes password to pass" do
42
+ settings = AMQ::Settings.configure(:password => "secret")
43
+ expect(settings[:pass]).to eq("secret")
44
+ end
45
+
46
+ it "prefers user over username when both provided" do
47
+ settings = AMQ::Settings.configure(:user => "admin", :username => "other")
48
+ expect(settings[:user]).to eq("admin")
49
+ end
50
+ end
51
+
52
+ describe ".parse_amqp_url" do
53
+ it "delegates to AMQ::URI.parse" do
54
+ result = AMQ::Settings.parse_amqp_url("amqp://localhost")
55
+ expect(result[:host]).to eq("localhost")
56
+ end
21
57
  end
22
58
  end
@@ -277,4 +277,11 @@ RSpec.describe AMQ::URI do
277
277
  end
278
278
  end
279
279
  end
280
+
281
+ describe ".parse_amqp_url" do
282
+ it "is an alias for .parse" do
283
+ result = described_class.parse_amqp_url("amqp://localhost")
284
+ expect(result[:host]).to eq("localhost")
285
+ end
286
+ end
280
287
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: amq-protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.4
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jakub Stastny
@@ -10,7 +10,7 @@ authors:
10
10
  - Mark Abramov
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-05-12 00:00:00.000000000 Z
13
+ date: 1980-01-02 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: |2
16
16
  amq-protocol is an AMQP 0.9.1 serialization library for Ruby. It is not a
@@ -34,9 +34,14 @@ files:
34
34
  - README.md
35
35
  - Rakefile
36
36
  - amq-protocol.gemspec
37
+ - benchmarks/frame_encoding.rb
37
38
  - benchmarks/int_allocator.rb
39
+ - benchmarks/method_encoding.rb
40
+ - benchmarks/pack_unpack.rb
38
41
  - benchmarks/pure/body_framing_with_256k_payload.rb
39
42
  - benchmarks/pure/body_framing_with_2k_payload.rb
43
+ - benchmarks/run_all.rb
44
+ - benchmarks/table_encoding.rb
40
45
  - codegen/__init__.py
41
46
  - codegen/amqp_0.9.1_changes.json
42
47
  - codegen/codegen.py
@@ -63,6 +68,7 @@ files:
63
68
  - profiling/README.md
64
69
  - profiling/stackprof/body_framing_with_2k_payload.rb
65
70
  - spec/amq/bit_set_spec.rb
71
+ - spec/amq/endianness_spec.rb
66
72
  - spec/amq/int_allocator_spec.rb
67
73
  - spec/amq/pack_spec.rb
68
74
  - spec/amq/protocol/basic_spec.rb
@@ -71,7 +77,9 @@ files:
71
77
  - spec/amq/protocol/confirm_spec.rb
72
78
  - spec/amq/protocol/connection_spec.rb
73
79
  - spec/amq/protocol/constants_spec.rb
80
+ - spec/amq/protocol/exceptions_spec.rb
74
81
  - spec/amq/protocol/exchange_spec.rb
82
+ - spec/amq/protocol/float_32bit_spec.rb
75
83
  - spec/amq/protocol/frame_spec.rb
76
84
  - spec/amq/protocol/method_spec.rb
77
85
  - spec/amq/protocol/queue_spec.rb
@@ -101,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
109
  - !ruby/object:Gem::Version
102
110
  version: '0'
103
111
  requirements: []
104
- rubygems_version: 3.6.2
112
+ rubygems_version: 3.6.9
105
113
  specification_version: 4
106
114
  summary: AMQP 0.9.1 encoding & decoding library.
107
115
  test_files: []