wsdirector-core 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f1fd8da87e97c9a82a6f0505a8d4a7ef28a9dabb0a75809ee60b1739481e08c
4
- data.tar.gz: 77f4c9ab2836f8f1e407c6376ee1b2d73b07ed1963770f5d38837bd24c5a598c
3
+ metadata.gz: e8f7c80b878147f8ceba30cff3c94398e7cf8196794d3b4b857ade3702b8a97f
4
+ data.tar.gz: dc767a46dc089ee5cbe7ff90227ddaa2bb23eee835264a436084dd5cf910e77a
5
5
  SHA512:
6
- metadata.gz: 935e69a837cf42cb0bb3121a8f3f9b415534c492245b350549cf2f0f6a839f382a9e8167179c7db00c0157e4312d0a7efc6480fa9e5bac65076a3a20d9a26cd9
7
- data.tar.gz: 61b7ee833e4a236167ef4ede04354b46d44bbc29cdc56b6caa05d3d28f5f0588131c933f3f5ab59833b1d74dfaca509b6a30f1b0bc3280591784b7cd211a448e
6
+ metadata.gz: c2a1d4aa0e1134344f2e4d94fb132153d90f071e48a541bef57e4ab26bf9d49227a9b646a6e3a226ff86d84995e54297933c9aa755db6374c5a8b107d741d4b8
7
+ data.tar.gz: cd9e6f0d9f2804fca5c3fff3dbccbcd6cc7cc3f1f4eafb9eba7846bf4c8b81cc96246ed1c0fc9be4721267406a97e6bb3e3173d6f7b0b8995f88a30b21dcb4ee
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.1.0 (2024-10-04)
6
+
7
+ - Support `stream_id`, `offset`, and `epoch` fields in Action Cable `receive`. ([@palkan][])
8
+
9
+ - Add `print` option to `receive` action to print the received message. ([@palkan][])
10
+
11
+ ## 1.0.3 (2023-10-03)
12
+
13
+ - Add `timeout` option to receive to limit the amount of time we wait for the expected message. ([@palkan][])
14
+
15
+ - Add `loop` action to multiply several actions. ([@palkan][])
16
+
5
17
  ## 1.0.2 (2023-01-12)
6
18
 
7
19
  - Fix adding transpiled files to releases. ([@palkan][])
@@ -94,11 +94,14 @@ module WSDirector
94
94
  "an object including #{obj.inspect}"
95
95
  end
96
96
 
97
- def truncate(*__rest__, &__block__) ; obj.truncate(*__rest__, &__block__); end
97
+ def truncate(*__rest__, &__block__) ; obj.truncate(*__rest__, &__block__); end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :truncate)
98
98
  end
99
99
 
100
100
  # Base protocol describes basic actions
101
101
  class Base
102
+ class ReceiveTimeoutError < StandardError
103
+ end
104
+
102
105
  include WSDirector::Utils
103
106
 
104
107
  def initialize(task, scale: 1, logger: nil, id: nil, color: nil)
@@ -115,7 +118,7 @@ module WSDirector
115
118
  @client = build_client(*__rest__, &__block__)
116
119
 
117
120
  log(:done) { "Connected" }
118
- end
121
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :init_client)
119
122
 
120
123
  def handle_step(step)
121
124
  type = step.delete("type")
@@ -155,9 +158,11 @@ module WSDirector
155
158
  def receive(step)
156
159
  expected = step["data"] || PartialMatcher.new(step["data>"])
157
160
  ordered = step["ordered"]
161
+ timeout = step.fetch("timeout", 5).to_f
158
162
 
159
- log { "Receive a message: #{expected.truncate(50)}" }
163
+ log { "Receive a message in #{timeout}s: #{expected.inspect.truncate(100)}" }
160
164
 
165
+ start = Time.now.to_f
161
166
  received = nil
162
167
 
163
168
  client.each_message do |msg, id|
@@ -170,14 +175,22 @@ module WSDirector
170
175
  if ordered
171
176
  raise UnmatchedExpectationError, prepare_receive_error(expected, received)
172
177
  end
178
+
179
+ if Time.now.to_f - start > timeout
180
+ raise ReceiveTimeoutError
181
+ end
173
182
  end
174
183
 
175
- log(:done) { "Received a message: #{received&.truncate(50)}" }
176
- rescue ThreadError
184
+ if step["print"]
185
+ debug({"message" => received})
186
+ end
187
+
188
+ log(:done) { "Received a message: #{received&.truncate(100)}" }
189
+ rescue ThreadError, ReceiveTimeoutError
177
190
  if received
178
191
  raise UnmatchedExpectationError, prepare_receive_error(expected, received)
179
192
  else
180
- raise NoMessageError, "Expected to receive #{expected} but nothing has been received"
193
+ raise NoMessageError, "Expected to receive #{expected.inspect} but nothing has been received"
181
194
  end
182
195
  end
183
196
 
@@ -244,7 +257,7 @@ module WSDirector
244
257
 
245
258
  def build_client(*__rest__, &__block__)
246
259
  Client.new(*__rest__, &__block__)
247
- end
260
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :build_client)
248
261
 
249
262
  def prepare_receive_error(expected, received)
250
263
  <<~MSG
@@ -14,14 +14,14 @@ module WSDirector
14
14
  @refs_counter = 3
15
15
  @join_refs_counter = 3
16
16
  @topics_to_join_ref = {}
17
- end
17
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :initialize)
18
18
 
19
19
  def init_client(**options)
20
20
  options[:query] ||= {}
21
21
  # Make sure we use the v2 of the protocol
22
22
  options[:query][:vsn] = "2.0.0"
23
23
 
24
- super(**options)
24
+ super
25
25
  end
26
26
 
27
27
  def join(step)
@@ -92,7 +92,12 @@ module WSDirector
92
92
  data["sample"] = [1, parse_multiplier(data["sample"])].max if data["sample"]
93
93
 
94
94
  multiplier = parse_multiplier(data.delete("multiplier") || "1")
95
- Array.new(multiplier) { {"type" => type, "id" => id}.merge(data) }
95
+
96
+ if type == "loop"
97
+ handle_steps(data.fetch("actions")) * multiplier
98
+ else
99
+ Array.new(multiplier) { {"type" => type, "id" => id}.merge(data) }
100
+ end
96
101
  else
97
102
  {"type" => step, "id" => id}
98
103
  end
@@ -5,7 +5,7 @@ module WSDirector
5
5
  # Extend Object through refinements
6
6
  module Formatting
7
7
  refine ::Object do
8
- def truncate(*) ; itself; end
8
+ def truncate(*__rest__) ; itself; end
9
9
  end
10
10
 
11
11
  refine ::String do
@@ -94,11 +94,14 @@ module WSDirector
94
94
  "an object including #{obj.inspect}"
95
95
  end
96
96
 
97
- def truncate(...) ; obj.truncate(...); end
97
+ def truncate(...) ; obj.truncate(...); end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :truncate)
98
98
  end
99
99
 
100
100
  # Base protocol describes basic actions
101
101
  class Base
102
+ class ReceiveTimeoutError < StandardError
103
+ end
104
+
102
105
  include WSDirector::Utils
103
106
 
104
107
  def initialize(task, scale: 1, logger: nil, id: nil, color: nil)
@@ -115,7 +118,7 @@ module WSDirector
115
118
  @client = build_client(...)
116
119
 
117
120
  log(:done) { "Connected" }
118
- end
121
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :init_client)
119
122
 
120
123
  def handle_step(step)
121
124
  type = step.delete("type")
@@ -155,9 +158,11 @@ module WSDirector
155
158
  def receive(step)
156
159
  expected = step["data"] || PartialMatcher.new(step["data>"])
157
160
  ordered = step["ordered"]
161
+ timeout = step.fetch("timeout", 5).to_f
158
162
 
159
- log { "Receive a message: #{expected.truncate(50)}" }
163
+ log { "Receive a message in #{timeout}s: #{expected.inspect.truncate(100)}" }
160
164
 
165
+ start = Time.now.to_f
161
166
  received = nil
162
167
 
163
168
  client.each_message do |msg, id|
@@ -170,14 +175,22 @@ module WSDirector
170
175
  if ordered
171
176
  raise UnmatchedExpectationError, prepare_receive_error(expected, received)
172
177
  end
178
+
179
+ if Time.now.to_f - start > timeout
180
+ raise ReceiveTimeoutError
181
+ end
173
182
  end
174
183
 
175
- log(:done) { "Received a message: #{received&.truncate(50)}" }
176
- rescue ThreadError
184
+ if step["print"]
185
+ debug({"message" => received})
186
+ end
187
+
188
+ log(:done) { "Received a message: #{received&.truncate(100)}" }
189
+ rescue ThreadError, ReceiveTimeoutError
177
190
  if received
178
191
  raise UnmatchedExpectationError, prepare_receive_error(expected, received)
179
192
  else
180
- raise NoMessageError, "Expected to receive #{expected} but nothing has been received"
193
+ raise NoMessageError, "Expected to receive #{expected.inspect} but nothing has been received"
181
194
  end
182
195
  end
183
196
 
@@ -244,7 +257,7 @@ module WSDirector
244
257
 
245
258
  def build_client(...)
246
259
  Client.new(...)
247
- end
260
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :build_client)
248
261
 
249
262
  def prepare_receive_error(expected, received)
250
263
  <<~MSG
@@ -11,7 +11,7 @@ module WSDirector
11
11
  options[:ignore] ||= [PING_IGNORE]
12
12
  options[:subprotocol] ||= "actioncable-v1-json"
13
13
 
14
- super(**options)
14
+ super
15
15
 
16
16
  receive("data>" => {"type" => "welcome"})
17
17
  log(:done) { "Welcomed" }
@@ -64,7 +64,11 @@ module WSDirector
64
64
  key = step.key?("data") ? "data" : "data>"
65
65
 
66
66
  message = step.fetch(key, {})
67
- super(key => {"identifier" => identifier, "message" => message})
67
+
68
+ # Move all protocol-level fields to data
69
+ step[key] = {"identifier" => identifier, "message" => message}.merge(step.slice("offset", "stream_id", "epoch"))
70
+
71
+ super
68
72
  end
69
73
 
70
74
  def receive_all(step)
@@ -78,7 +82,7 @@ module WSDirector
78
82
 
79
83
  key = msg.key?("data") ? "data" : "data>"
80
84
 
81
- msg[key] = {"identifier" => identifier, "message" => msg[key]}
85
+ msg[key] = {"identifier" => identifier, "message" => msg[key]}.merge(step.slice("offset", "stream_id", "epoch"))
82
86
  end
83
87
 
84
88
  super
@@ -88,7 +92,7 @@ module WSDirector
88
92
 
89
93
  def extract_identifier(step)
90
94
  channel = step.delete("channel")
91
- step.fetch("params", {}).merge(channel: channel).to_json
95
+ (step.delete("params") || {}).merge(channel: channel).to_json
92
96
  end
93
97
  end
94
98
  end
@@ -14,14 +14,14 @@ module WSDirector
14
14
  @refs_counter = 3
15
15
  @join_refs_counter = 3
16
16
  @topics_to_join_ref = {}
17
- end
17
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :initialize)
18
18
 
19
19
  def init_client(**options)
20
20
  options[:query] ||= {}
21
21
  # Make sure we use the v2 of the protocol
22
22
  options[:query][:vsn] = "2.0.0"
23
23
 
24
- super(**options)
24
+ super
25
25
  end
26
26
 
27
27
  def join(step)
@@ -92,7 +92,12 @@ module WSDirector
92
92
  data["sample"] = [1, parse_multiplier(data["sample"])].max if data["sample"]
93
93
 
94
94
  multiplier = parse_multiplier(data.delete("multiplier") || "1")
95
- Array.new(multiplier) { {"type" => type, "id" => id}.merge(data) }
95
+
96
+ if type == "loop"
97
+ handle_steps(data.fetch("actions")) * multiplier
98
+ else
99
+ Array.new(multiplier) { {"type" => type, "id" => id}.merge(data) }
100
+ end
96
101
  else
97
102
  {"type" => step, "id" => id}
98
103
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WSDirector
4
+ module Ext
5
+ # Extend Object through refinements
6
+ module Formatting
7
+ refine ::Object do
8
+ def truncate(*__rest__) = itself
9
+ end
10
+
11
+ refine ::String do
12
+ def truncate(limit)
13
+ return self if size <= limit
14
+
15
+ "#{self[0..(limit - 3)]}..."
16
+ end
17
+ end
18
+
19
+ refine ::Hash do
20
+ def truncate(limit)
21
+ str = to_json
22
+
23
+ str.truncate(limit)
24
+ end
25
+ end
26
+
27
+ refine ::Float do
28
+ def duration
29
+ if self > 1
30
+ "#{truncate(2)}s"
31
+ else
32
+ "#{(self * 1000).to_i}ms"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -11,7 +11,7 @@ module WSDirector
11
11
  options[:ignore] ||= [PING_IGNORE]
12
12
  options[:subprotocol] ||= "actioncable-v1-json"
13
13
 
14
- super(**options)
14
+ super
15
15
 
16
16
  receive("data>" => {"type" => "welcome"})
17
17
  log(:done) { "Welcomed" }
@@ -64,7 +64,11 @@ module WSDirector
64
64
  key = step.key?("data") ? "data" : "data>"
65
65
 
66
66
  message = step.fetch(key, {})
67
- super(key => {"identifier" => identifier, "message" => message})
67
+
68
+ # Move all protocol-level fields to data
69
+ step[key] = {"identifier" => identifier, "message" => message}.merge(step.slice("offset", "stream_id", "epoch"))
70
+
71
+ super
68
72
  end
69
73
 
70
74
  def receive_all(step)
@@ -78,7 +82,7 @@ module WSDirector
78
82
 
79
83
  key = msg.key?("data") ? "data" : "data>"
80
84
 
81
- msg[key] = {"identifier" => identifier, "message" => msg[key]}
85
+ msg[key] = {"identifier" => identifier, "message" => msg[key]}.merge(step.slice("offset", "stream_id", "epoch"))
82
86
  end
83
87
 
84
88
  super
@@ -88,7 +92,7 @@ module WSDirector
88
92
 
89
93
  def extract_identifier(step)
90
94
  channel = step.delete("channel")
91
- step.fetch("params", {}).merge(channel: channel).to_json
95
+ (step.delete("params") || {}).merge(channel: channel).to_json
92
96
  end
93
97
  end
94
98
  end
@@ -99,6 +99,9 @@ module WSDirector
99
99
 
100
100
  # Base protocol describes basic actions
101
101
  class Base
102
+ class ReceiveTimeoutError < StandardError
103
+ end
104
+
102
105
  include WSDirector::Utils
103
106
 
104
107
  def initialize(task, scale: 1, logger: nil, id: nil, color: nil)
@@ -155,9 +158,11 @@ module WSDirector
155
158
  def receive(step)
156
159
  expected = step["data"] || PartialMatcher.new(step["data>"])
157
160
  ordered = step["ordered"]
161
+ timeout = step.fetch("timeout", 5).to_f
158
162
 
159
- log { "Receive a message: #{expected.truncate(50)}" }
163
+ log { "Receive a message in #{timeout}s: #{expected.inspect.truncate(100)}" }
160
164
 
165
+ start = Time.now.to_f
161
166
  received = nil
162
167
 
163
168
  client.each_message do |msg, id|
@@ -170,14 +175,22 @@ module WSDirector
170
175
  if ordered
171
176
  raise UnmatchedExpectationError, prepare_receive_error(expected, received)
172
177
  end
178
+
179
+ if Time.now.to_f - start > timeout
180
+ raise ReceiveTimeoutError
181
+ end
173
182
  end
174
183
 
175
- log(:done) { "Received a message: #{received&.truncate(50)}" }
176
- rescue ThreadError
184
+ if step["print"]
185
+ debug({"message" => received})
186
+ end
187
+
188
+ log(:done) { "Received a message: #{received&.truncate(100)}" }
189
+ rescue ThreadError, ReceiveTimeoutError
177
190
  if received
178
191
  raise UnmatchedExpectationError, prepare_receive_error(expected, received)
179
192
  else
180
- raise NoMessageError, "Expected to receive #{expected} but nothing has been received"
193
+ raise NoMessageError, "Expected to receive #{expected.inspect} but nothing has been received"
181
194
  end
182
195
  end
183
196
 
@@ -21,7 +21,7 @@ module WSDirector
21
21
  # Make sure we use the v2 of the protocol
22
22
  options[:query][:vsn] = "2.0.0"
23
23
 
24
- super(**options)
24
+ super
25
25
  end
26
26
 
27
27
  def join(step)
@@ -92,7 +92,12 @@ module WSDirector
92
92
  data["sample"] = [1, parse_multiplier(data["sample"])].max if data["sample"]
93
93
 
94
94
  multiplier = parse_multiplier(data.delete("multiplier") || "1")
95
- Array.new(multiplier) { {"type" => type, "id" => id}.merge(data) }
95
+
96
+ if type == "loop"
97
+ handle_steps(data.fetch("actions")) * multiplier
98
+ else
99
+ Array.new(multiplier) { {"type" => type, "id" => id}.merge(data) }
100
+ end
96
101
  else
97
102
  {"type" => step, "id" => id}
98
103
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WSDirector
4
- VERSION = "1.0.2"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wsdirector-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-01-12 00:00:00.000000000 Z
13
+ date: 2024-10-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: websocket-client-simple
@@ -207,6 +207,7 @@ files:
207
207
  - lib/.rbnext/3.1/wsdirector/runner.rb
208
208
  - lib/.rbnext/3.1/wsdirector/scenario_reader.rb
209
209
  - lib/.rbnext/3.1/wsdirector/task.rb
210
+ - lib/.rbnext/3.2/wsdirector/ext/formatting.rb
210
211
  - lib/wsdirector-cli.rb
211
212
  - lib/wsdirector.rb
212
213
  - lib/wsdirector/cli.rb
@@ -245,7 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
245
246
  - !ruby/object:Gem::Version
246
247
  version: '0'
247
248
  requirements: []
248
- rubygems_version: 3.3.11
249
+ rubygems_version: 3.5.18
249
250
  signing_key:
250
251
  specification_version: 4
251
252
  summary: Scenario-based WebSocket black-box testing