wsdirector-core 1.0.2 → 1.1.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 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