anyt 0.8.4 → 1.0.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +4 -1
  4. data/lib/anyt/cli.rb +81 -14
  5. data/lib/anyt/client.rb +13 -7
  6. data/lib/anyt/command.rb +33 -18
  7. data/lib/anyt/config.rb +15 -7
  8. data/lib/anyt/dummy/application.rb +31 -6
  9. data/lib/anyt/dummy/config.ru +4 -0
  10. data/lib/anyt/dummy/tmp/development_secret.txt +1 -0
  11. data/lib/anyt/ext/minitest.rb +23 -6
  12. data/lib/anyt/remote_control.rb +33 -0
  13. data/lib/anyt/rpc.rb +4 -6
  14. data/lib/anyt/tests.rb +5 -7
  15. data/lib/anyt/tests/core/ping_test.rb +2 -2
  16. data/lib/anyt/tests/core/welcome_test.rb +2 -2
  17. data/lib/anyt/tests/features/channel_state_test.rb +81 -0
  18. data/lib/anyt/tests/features/remote_disconnect_test.rb +30 -0
  19. data/lib/anyt/tests/features/server_restart_test.rb +28 -0
  20. data/lib/anyt/tests/request/channel_test.rb +28 -0
  21. data/lib/anyt/tests/request/connection_test.rb +23 -21
  22. data/lib/anyt/tests/request/disconnect_reasons_test.rb +23 -0
  23. data/lib/anyt/tests/request/disconnection_test.rb +50 -32
  24. data/lib/anyt/tests/streams/broadcast_test.rb +13 -13
  25. data/lib/anyt/tests/streams/multiple_clients_test.rb +10 -10
  26. data/lib/anyt/tests/streams/multiple_test.rb +10 -10
  27. data/lib/anyt/tests/streams/single_test.rb +35 -10
  28. data/lib/anyt/tests/streams/stop_test.rb +57 -0
  29. data/lib/anyt/tests/subscriptions/ack_test.rb +9 -11
  30. data/lib/anyt/tests/subscriptions/params_test.rb +6 -7
  31. data/lib/anyt/tests/subscriptions/perform_test.rb +16 -18
  32. data/lib/anyt/tests/subscriptions/transmissions_test.rb +6 -7
  33. data/lib/anyt/version.rb +1 -1
  34. metadata +27 -70
  35. data/.gitignore +0 -43
  36. data/.rubocop.yml +0 -80
  37. data/Gemfile +0 -6
  38. data/LICENSE.txt +0 -21
  39. data/Makefile +0 -5
  40. data/anyt.gemspec +0 -42
  41. data/circle.yml +0 -14
  42. data/etc/tests/channel_broadcast_test.rb +0 -51
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "drb/drb"
4
+
5
+ module Anyt
6
+ # Invoke commands within the running Ruby (Action Cable) server
7
+ module RemoteControl
8
+ class Server
9
+ class << self
10
+ alias start new
11
+ end
12
+
13
+ def initialize(port)
14
+ DRb.start_service(
15
+ "druby://localhost:#{port}",
16
+ Client.new
17
+ )
18
+ end
19
+ end
20
+
21
+ class Client
22
+ def self.connect(port)
23
+ DRb.start_service
24
+
25
+ DRbObject.new_with_uri("druby://localhost:#{port}")
26
+ end
27
+
28
+ def restart_action_cable
29
+ ActionCable.server.restart
30
+ end
31
+ end
32
+ end
33
+ end
@@ -11,12 +11,10 @@ module Anyt # :nodoc:
11
11
 
12
12
  class << self
13
13
  attr_accessor :running
14
+ attr_reader :server
14
15
 
15
16
  # rubocop: disable Metrics/AbcSize,Metrics/MethodLength
16
17
  def start
17
- ActionCable.server.config.cable = { "adapter" => "any_cable" }
18
- Rails.application.initialize!
19
-
20
18
  AnyCable.logger.debug "Starting RPC server ..."
21
19
 
22
20
  AnyCable.server_callbacks.each(&:call)
@@ -29,18 +27,18 @@ module Anyt # :nodoc:
29
27
 
30
28
  AnyCable.middleware.freeze
31
29
 
32
- @server.start
30
+ server.start
33
31
 
34
32
  AnyCable.logger.debug "RPC server started"
35
33
  end
36
34
  # rubocop: enable Metrics/AbcSize,Metrics/MethodLength
37
35
 
38
36
  def stop
39
- @server.stop
37
+ server&.stop
40
38
  end
41
39
  end
42
40
 
43
41
  AnyCable.connection_factory = ActionCable.server.config.connection_class.call
44
- AnyCable.logger = Logger.new(IO::NULL) unless AnyCable.config.debug
42
+ AnyCable.config.log_level = :fatal unless AnyCable.config.debug
45
43
  end
46
44
  end
@@ -24,8 +24,6 @@ module Anyt
24
24
  #
25
25
  # NOTE: We should run this before launching RPC server
26
26
 
27
- # rubocop:disable Metrics/AbcSize
28
- # rubocop:disable Metrics/MethodLength
29
27
  def load_tests
30
28
  return load_all_tests unless Anyt.config.filter_tests?
31
29
 
@@ -33,22 +31,22 @@ module Anyt
33
31
  filter = Anyt.config.tests_filter
34
32
 
35
33
  test_files_patterns.each do |pattern|
36
- Dir.glob(pattern).each do |file|
37
- if file.match?(filter)
34
+ Dir.glob(pattern).sort.each do |file|
35
+ if filter.call(file)
38
36
  require file
39
37
  else
40
- skipped << file.gsub(File.join(__dir__, 'tests/'), '').gsub('_test.rb', '')
38
+ skipped << file.gsub(File.join(__dir__, "tests/"), "").gsub("_test.rb", "")
41
39
  end
42
40
  end
43
41
  end
44
42
 
45
- $stdout.print "Skipping tests: #{skipped.join(', ')}\n"
43
+ $stdout.print "Skipping tests: #{skipped.join(", ")}\n"
46
44
  end
47
45
 
48
46
  # Load all test files
49
47
  def load_all_tests
50
48
  test_files_patterns.each do |pattern|
51
- Dir.glob(pattern).each { |file| require file }
49
+ Dir.glob(pattern).sort.each { |file| require file }
52
50
  end
53
51
  end
54
52
 
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  feature "Ping" do
4
- scenario %{
4
+ scenario %(
5
5
  Client receives pings with timestamps
6
- } do
6
+ ) do
7
7
  client = build_client(ignore: ["welcome"])
8
8
 
9
9
  previous_stamp = 0
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  feature "Welcome" do
4
- scenario %{
4
+ scenario %(
5
5
  Client receives "welcome" message
6
- } do
6
+ ) do
7
7
  client = build_client(ignore: ["ping"])
8
8
  assert_equal client.receive, "type" => "welcome"
9
9
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Channel state" do
4
+ channel do
5
+ state_attr_accessor :user, :count
6
+
7
+ def subscribed
8
+ self.user = {name: params["name"]}
9
+ self.count = 1
10
+
11
+ stream_from "state_counts"
12
+ end
13
+
14
+ def tick
15
+ self.count += 2
16
+ transmit(count: count, name: user[:name])
17
+ end
18
+
19
+ def unsubscribed
20
+ return unless params["notify_disconnect"]
21
+
22
+ ActionCable.server.broadcast("state_counts", data: "user left: #{user[:name]}")
23
+ end
24
+ end
25
+
26
+ let(:identifier) { {channel: channel, name: "chipolino"}.to_json }
27
+
28
+ let(:client2) { build_client(ignore: %w[ping welcome]) }
29
+ let(:identifier2) { {channel: channel, name: "chipollone", notify_disconnect: true}.to_json }
30
+
31
+ before do
32
+ subscribe_request = {command: "subscribe", identifier: identifier}
33
+
34
+ client.send(subscribe_request)
35
+
36
+ ack = {
37
+ "identifier" => identifier, "type" => "confirm_subscription"
38
+ }
39
+
40
+ assert_equal ack, client.receive
41
+ end
42
+
43
+ scenario %(
44
+ Channel state is kept between commands
45
+ ) do
46
+ perform_request = {
47
+ :command => "message",
48
+ :identifier => identifier,
49
+ "data" => {"action" => "tick"}.to_json
50
+ }
51
+
52
+ client.send(perform_request)
53
+
54
+ msg = {"identifier" => identifier, "message" => {"count" => 3, "name" => "chipolino"}}
55
+
56
+ assert_equal msg, client.receive
57
+ end
58
+
59
+ scenario %(
60
+ Channel state is available in #unsubscribe callbacks
61
+ ) do
62
+ subscribe_request = {command: "subscribe", identifier: identifier2}
63
+
64
+ client2.send(subscribe_request)
65
+
66
+ ack = {
67
+ "identifier" => identifier2, "type" => "confirm_subscription"
68
+ }
69
+
70
+ assert_equal ack, client2.receive
71
+
72
+ client2.close
73
+
74
+ msg = {
75
+ "identifier" => identifier,
76
+ "message" => {"data" => "user left: chipollone"}
77
+ }
78
+
79
+ assert_equal msg, client.receive
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Remote disconnect" do
4
+ connect_handler("uid") do
5
+ self.uid = request.params["uid"]
6
+ uid.present?
7
+ end
8
+
9
+ scenario %(
10
+ Close single connection by id
11
+ ) do
12
+ client = build_client(qs: "test=uid&uid=26", ignore: %w[ping])
13
+ assert_equal client.receive, "type" => "welcome"
14
+
15
+ ActionCable.server.remote_connections.where(uid: "26").disconnect
16
+
17
+ # Waiting for https://github.com/rails/rails/pull/39544
18
+ unless Anyt.config.use_action_cable
19
+ assert_equal(
20
+ client.receive,
21
+ "type" => "disconnect",
22
+ "reconnect" => true,
23
+ "reason" => "remote"
24
+ )
25
+ end
26
+
27
+ client.wait_for_close
28
+ assert client.closed?
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Server restart" do
4
+ connect_handler("reasons") do
5
+ next false if request.params[:reason] == "unauthorized"
6
+ true
7
+ end
8
+
9
+ scenario %(
10
+ Client receives disconnect message
11
+ ) do
12
+ client = build_client(
13
+ qs: "test=reasons&reason=server_restart",
14
+ ignore: %(ping)
15
+ )
16
+
17
+ assert_equal client.receive, "type" => "welcome"
18
+
19
+ restart_server!
20
+
21
+ assert_equal(
22
+ client.receive,
23
+ "type" => "disconnect",
24
+ "reconnect" => true,
25
+ "reason" => "server_restart"
26
+ )
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Request" do
4
+ channel do
5
+ delegate :params, to: :connection
6
+
7
+ def subscribed
8
+ reject unless params[:token] == "secret"
9
+ end
10
+ end
11
+
12
+ scenario %(
13
+ Channel has access to request
14
+ ) do
15
+ client = build_client(qs: "token=secret", ignore: %w[ping])
16
+ assert_equal client.receive, "type" => "welcome"
17
+
18
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
19
+
20
+ client.send(subscribe_request)
21
+
22
+ ack = {
23
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
24
+ }
25
+
26
+ assert_equal ack, client.receive
27
+ end
28
+ end
@@ -1,52 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  feature "Request" do
4
- connect_handler('request_url') do
5
- request.url =~ /test=request_url/
4
+ target_host = URI.parse(Anyt.config.target_url).host
5
+
6
+ connect_handler("request_url") do
7
+ request.url =~ /test=request_url/ && request.host == target_host
6
8
  end
7
9
 
8
- scenario %{
10
+ scenario %(
9
11
  Url is set during connection
10
- } do
11
- client = build_client(qs: 'test=request_url')
12
+ ) do
13
+ client = build_client(qs: "test=request_url")
12
14
  assert_equal client.receive, "type" => "welcome"
13
15
  end
14
16
 
15
- connect_handler('cookies') do
16
- cookies[:username] == 'john green'
17
+ connect_handler("cookies") do
18
+ cookies[:username] == "john green"
17
19
  end
18
20
 
19
- scenario %{
21
+ scenario %(
20
22
  Reject when required cookies are not set
21
- } do
22
- client = build_client(qs: 'test=cookies')
23
+ ) do
24
+ client = build_client(qs: "test=cookies")
23
25
  client.wait_for_close
24
26
  assert client.closed?
25
27
  end
26
28
 
27
- scenario %{
29
+ scenario %(
28
30
  Accepts when required cookies are set
29
- } do
30
- client = build_client(qs: 'test=cookies', cookies: 'username=john green')
31
+ ) do
32
+ client = build_client(qs: "test=cookies", cookies: "username=john green")
31
33
  assert_equal client.receive, "type" => "welcome"
32
34
  end
33
35
 
34
- connect_handler('headers') do
35
- request.headers['X-API-TOKEN'] == 'abc'
36
+ connect_handler("headers") do
37
+ request.headers["X-API-TOKEN"] == "abc"
36
38
  end
37
39
 
38
- scenario %{
40
+ scenario %(
39
41
  Reject when required header is missing
40
- } do
41
- client = build_client(qs: 'test=headers')
42
+ ) do
43
+ client = build_client(qs: "test=headers")
42
44
  client.wait_for_close
43
45
  assert client.closed?
44
46
  end
45
47
 
46
- scenario %{
48
+ scenario %(
47
49
  Accepts when required header is set
48
- } do
49
- client = build_client(qs: 'test=headers', headers: { 'x-api-token' => 'abc' })
50
+ ) do
51
+ client = build_client(qs: "test=headers", headers: {"x-api-token" => "abc"})
50
52
  assert_equal client.receive, "type" => "welcome"
51
53
  end
52
54
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Request" do
4
+ connect_handler("reasons") do
5
+ next false if request.params[:reason] == "unauthorized"
6
+ true
7
+ end
8
+
9
+ scenario %(
10
+ Receives disconnect message when rejected
11
+ ) do
12
+ client = build_client(qs: "test=reasons&reason=unauthorized")
13
+ assert_equal(
14
+ client.receive,
15
+ "type" => "disconnect",
16
+ "reconnect" => false,
17
+ "reason" => "unauthorized"
18
+ )
19
+
20
+ client.wait_for_close
21
+ assert client.closed?
22
+ end
23
+ end
@@ -3,68 +3,76 @@
3
3
  feature "Request" do
4
4
  channel(:a) do
5
5
  def subscribed
6
- stream_from "a"
6
+ stream_from "request_a"
7
7
  end
8
8
 
9
9
  def unsubscribed
10
- ActionCable.server.broadcast("a", data: "user left#{params[:id].presence}")
10
+ ActionCable.server.broadcast("request_a", data: "user left")
11
11
  end
12
12
  end
13
13
 
14
14
  channel(:b) do
15
15
  def subscribed
16
- stream_from "b"
16
+ stream_from "request_b"
17
17
  end
18
18
 
19
19
  def unsubscribed
20
- ActionCable.server.broadcast("b", data: "user left")
20
+ ActionCable.server.broadcast("request_b", data: "user left")
21
+ end
22
+ end
23
+
24
+ channel(:c) do
25
+ def subscribed
26
+ stream_from "request_c"
27
+ end
28
+
29
+ def unsubscribed
30
+ ActionCable.server.broadcast("request_c", data: "user left#{params[:id].presence}")
21
31
  end
22
32
  end
23
33
 
24
34
  let(:client2) { build_client(ignore: %w[ping welcome]) }
25
35
 
26
- before do
27
- subscribe_request = { command: "subscribe", identifier: { channel: a_channel }.to_json }
36
+ scenario %(
37
+ Client disconnect invokes #unsubscribe callbacks
38
+ for different channels
39
+ ) do
40
+ subscribe_request = {command: "subscribe", identifier: {channel: a_channel}.to_json}
28
41
 
29
42
  client.send(subscribe_request)
30
43
 
31
44
  ack = {
32
- "identifier" => { channel: a_channel }.to_json, "type" => "confirm_subscription"
45
+ "identifier" => {channel: a_channel}.to_json, "type" => "confirm_subscription"
33
46
  }
34
47
 
35
48
  assert_equal ack, client.receive
36
49
 
37
- subscribe_request = { command: "subscribe", identifier: { channel: b_channel }.to_json }
50
+ subscribe_request = {command: "subscribe", identifier: {channel: b_channel}.to_json}
38
51
 
39
52
  client.send(subscribe_request)
40
53
 
41
54
  ack = {
42
- "identifier" => { channel: b_channel }.to_json, "type" => "confirm_subscription"
55
+ "identifier" => {channel: b_channel}.to_json, "type" => "confirm_subscription"
43
56
  }
44
57
 
45
58
  assert_equal ack, client.receive
46
- end
47
59
 
48
- scenario %{
49
- Client disconnect invokes #unsubscribe callbacks
50
- for different channels
51
- } do
52
- subscribe_request = { command: "subscribe", identifier: { channel: a_channel }.to_json }
60
+ subscribe_request = {command: "subscribe", identifier: {channel: a_channel}.to_json}
53
61
 
54
62
  client2.send(subscribe_request)
55
63
 
56
64
  ack = {
57
- "identifier" => { channel: a_channel }.to_json, "type" => "confirm_subscription"
65
+ "identifier" => {channel: a_channel}.to_json, "type" => "confirm_subscription"
58
66
  }
59
67
 
60
68
  assert_equal ack, client2.receive
61
69
 
62
- subscribe_request = { command: "subscribe", identifier: { channel: b_channel }.to_json }
70
+ subscribe_request = {command: "subscribe", identifier: {channel: b_channel}.to_json}
63
71
 
64
72
  client2.send(subscribe_request)
65
73
 
66
74
  ack = {
67
- "identifier" => { channel: b_channel }.to_json, "type" => "confirm_subscription"
75
+ "identifier" => {channel: b_channel}.to_json, "type" => "confirm_subscription"
68
76
  }
69
77
 
70
78
  assert_equal ack, client2.receive
@@ -72,12 +80,12 @@ feature "Request" do
72
80
  client2.close
73
81
 
74
82
  msg = {
75
- "identifier" => { channel: a_channel }.to_json,
76
- "message" => { "data" => "user left" }
83
+ "identifier" => {channel: a_channel}.to_json,
84
+ "message" => {"data" => "user left"}
77
85
  }
78
86
  msg2 = {
79
- "identifier" => { channel: b_channel }.to_json,
80
- "message" => { "data" => "user left" }
87
+ "identifier" => {channel: b_channel}.to_json,
88
+ "message" => {"data" => "user left"}
81
89
  }
82
90
 
83
91
  msgs = [client.receive, client.receive]
@@ -86,26 +94,36 @@ feature "Request" do
86
94
  assert_includes msgs, msg2
87
95
  end
88
96
 
89
- scenario %{
97
+ scenario %(
90
98
  Client disconnect invokes #unsubscribe callbacks
91
99
  for multiple subscriptions from the same channel
92
- } do
93
- subscribe_request = { command: "subscribe", identifier: { channel: a_channel, id: 1 }.to_json }
100
+ ) do
101
+ subscribe_request = {command: "subscribe", identifier: {channel: c_channel}.to_json}
102
+
103
+ client.send(subscribe_request)
104
+
105
+ ack = {
106
+ "identifier" => {channel: c_channel}.to_json, "type" => "confirm_subscription"
107
+ }
108
+
109
+ assert_equal ack, client.receive
110
+
111
+ subscribe_request = {command: "subscribe", identifier: {channel: c_channel, id: 1}.to_json}
94
112
 
95
113
  client2.send(subscribe_request)
96
114
 
97
115
  ack = {
98
- "identifier" => { channel: a_channel, id: 1 }.to_json, "type" => "confirm_subscription"
116
+ "identifier" => {channel: c_channel, id: 1}.to_json, "type" => "confirm_subscription"
99
117
  }
100
118
 
101
119
  assert_equal ack, client2.receive
102
120
 
103
- subscribe_request = { command: "subscribe", identifier: { channel: a_channel, id: 2 }.to_json }
121
+ subscribe_request = {command: "subscribe", identifier: {channel: c_channel, id: 2}.to_json}
104
122
 
105
123
  client2.send(subscribe_request)
106
124
 
107
125
  ack = {
108
- "identifier" => { channel: a_channel, id: 2 }.to_json, "type" => "confirm_subscription"
126
+ "identifier" => {channel: c_channel, id: 2}.to_json, "type" => "confirm_subscription"
109
127
  }
110
128
 
111
129
  assert_equal ack, client2.receive
@@ -113,13 +131,13 @@ feature "Request" do
113
131
  client2.close
114
132
 
115
133
  msg = {
116
- "identifier" => { channel: a_channel }.to_json,
117
- "message" => { "data" => "user left1" }
134
+ "identifier" => {channel: c_channel}.to_json,
135
+ "message" => {"data" => "user left1"}
118
136
  }
119
137
 
120
138
  msg2 = {
121
- "identifier" => { channel: a_channel }.to_json,
122
- "message" => { "data" => "user left2" }
139
+ "identifier" => {channel: c_channel}.to_json,
140
+ "message" => {"data" => "user left2"}
123
141
  }
124
142
 
125
143
  msgs = [client.receive, client.receive]