anyt 0.8.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 +12 -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 +24 -7
  12. data/lib/anyt/remote_control.rb +33 -0
  13. data/lib/anyt/rpc.rb +6 -9
  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 +13 -13
  26. data/lib/anyt/tests/streams/multiple_test.rb +15 -15
  27. data/lib/anyt/tests/streams/single_test.rb +39 -14
  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 +38 -74
  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
data/lib/anyt/rpc.rb CHANGED
@@ -11,36 +11,33 @@ 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)
23
21
 
24
- @server = AnyCable::Server.new(
22
+ @server = AnyCable::GRPC::Server.new(
25
23
  host: AnyCable.config.rpc_host,
26
- **AnyCable.config.to_grpc_params,
27
- interceptors: AnyCable.middleware.to_a
24
+ **AnyCable.config.to_grpc_params
28
25
  )
29
26
 
30
27
  AnyCable.middleware.freeze
31
28
 
32
- @server.start
29
+ server.start
33
30
 
34
31
  AnyCable.logger.debug "RPC server started"
35
32
  end
36
33
  # rubocop: enable Metrics/AbcSize,Metrics/MethodLength
37
34
 
38
35
  def stop
39
- @server.stop
36
+ server&.stop
40
37
  end
41
38
  end
42
39
 
43
40
  AnyCable.connection_factory = ActionCable.server.config.connection_class.call
44
- AnyCable.logger = Logger.new(IO::NULL) unless AnyCable.config.debug
41
+ AnyCable.config.log_level = :fatal unless AnyCable.config.debug
45
42
  end
46
43
  end
data/lib/anyt/tests.rb CHANGED
@@ -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]