anyt 0.8.3 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +1 -1
- data/README.md +4 -1
- data/lib/anyt/cli.rb +32 -6
- data/lib/anyt/client.rb +13 -7
- data/lib/anyt/command.rb +31 -18
- data/lib/anyt/config.rb +15 -7
- data/lib/anyt/dummy/application.rb +9 -11
- data/lib/anyt/dummy/config.ru +4 -0
- data/lib/anyt/dummy/routes.rb +4 -0
- data/lib/anyt/dummy/tmp/development_secret.txt +1 -0
- data/lib/anyt/ext/minitest.rb +23 -6
- data/lib/anyt/remote_control.rb +33 -0
- data/lib/anyt/rpc.rb +4 -6
- data/lib/anyt/tests.rb +5 -7
- data/lib/anyt/tests/core/ping_test.rb +2 -2
- data/lib/anyt/tests/core/welcome_test.rb +3 -3
- data/lib/anyt/tests/features/channel_state_test.rb +79 -0
- data/lib/anyt/tests/features/remote_disconnect_test.rb +30 -0
- data/lib/anyt/tests/features/server_restart_test.rb +28 -0
- data/lib/anyt/tests/request/channel_test.rb +28 -0
- data/lib/anyt/tests/request/connection_test.rb +23 -21
- data/lib/anyt/tests/request/disconnect_reasons_test.rb +23 -0
- data/lib/anyt/tests/request/disconnection_test.rb +50 -32
- data/lib/anyt/tests/streams/broadcast_test.rb +13 -13
- data/lib/anyt/tests/streams/multiple_clients_test.rb +10 -10
- data/lib/anyt/tests/streams/multiple_test.rb +10 -10
- data/lib/anyt/tests/streams/single_test.rb +35 -10
- data/lib/anyt/tests/streams/stop_test.rb +57 -0
- data/lib/anyt/tests/subscriptions/ack_test.rb +9 -11
- data/lib/anyt/tests/subscriptions/params_test.rb +6 -7
- data/lib/anyt/tests/subscriptions/perform_test.rb +16 -18
- data/lib/anyt/tests/subscriptions/transmissions_test.rb +6 -7
- data/lib/anyt/version.rb +1 -1
- metadata +28 -70
- data/.gitignore +0 -43
- data/.rubocop.yml +0 -80
- data/Gemfile +0 -6
- data/LICENSE.txt +0 -21
- data/Makefile +0 -5
- data/anyt.gemspec +0 -42
- data/circle.yml +0 -14
- data/etc/tests/channel_broadcast_test.rb +0 -51
data/lib/anyt/rpc.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
37
|
+
server&.stop
|
40
38
|
end
|
41
39
|
end
|
42
40
|
|
43
41
|
AnyCable.connection_factory = ActionCable.server.config.connection_class.call
|
44
|
-
AnyCable.
|
42
|
+
AnyCable.config.log_level = :fatal unless AnyCable.config.debug
|
45
43
|
end
|
46
44
|
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
|
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__,
|
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(
|
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,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
feature "Welcome" do
|
4
|
-
scenario %
|
4
|
+
scenario %(
|
5
5
|
Client receives "welcome" message
|
6
|
-
|
7
|
-
client = build_client
|
6
|
+
) do
|
7
|
+
client = build_client(ignore: ["ping"])
|
8
8
|
assert_equal client.receive, "type" => "welcome"
|
9
9
|
end
|
10
10
|
end
|
@@ -0,0 +1,79 @@
|
|
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
|
+
ActionCable.server.broadcast("state_counts", data: "user left: #{user[:name]}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:identifier) { {channel: channel, name: "chipolino"}.to_json }
|
25
|
+
|
26
|
+
let(:client2) { build_client(ignore: %w[ping welcome]) }
|
27
|
+
let(:identifier2) { {channel: channel, name: "chipollone"}.to_json }
|
28
|
+
|
29
|
+
before do
|
30
|
+
subscribe_request = {command: "subscribe", identifier: identifier}
|
31
|
+
|
32
|
+
client.send(subscribe_request)
|
33
|
+
|
34
|
+
ack = {
|
35
|
+
"identifier" => identifier, "type" => "confirm_subscription"
|
36
|
+
}
|
37
|
+
|
38
|
+
assert_equal ack, client.receive
|
39
|
+
end
|
40
|
+
|
41
|
+
scenario %(
|
42
|
+
Channel state is kept between commands
|
43
|
+
) do
|
44
|
+
perform_request = {
|
45
|
+
:command => "message",
|
46
|
+
:identifier => identifier,
|
47
|
+
"data" => {"action" => "tick"}.to_json
|
48
|
+
}
|
49
|
+
|
50
|
+
client.send(perform_request)
|
51
|
+
|
52
|
+
msg = {"identifier" => identifier, "message" => {"count" => 3, "name" => "chipolino"}}
|
53
|
+
|
54
|
+
assert_equal msg, client.receive
|
55
|
+
end
|
56
|
+
|
57
|
+
scenario %(
|
58
|
+
Channel state is available in #unsubscribe callbacks
|
59
|
+
) do
|
60
|
+
subscribe_request = {command: "subscribe", identifier: identifier2}
|
61
|
+
|
62
|
+
client2.send(subscribe_request)
|
63
|
+
|
64
|
+
ack = {
|
65
|
+
"identifier" => identifier2, "type" => "confirm_subscription"
|
66
|
+
}
|
67
|
+
|
68
|
+
assert_equal ack, client2.receive
|
69
|
+
|
70
|
+
client.close
|
71
|
+
|
72
|
+
msg = {
|
73
|
+
"identifier" => identifier2,
|
74
|
+
"message" => {"data" => "user left: chipolino"}
|
75
|
+
}
|
76
|
+
|
77
|
+
assert_equal msg, client2.receive
|
78
|
+
end
|
79
|
+
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
|
-
|
5
|
-
|
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
|
-
|
11
|
-
client = build_client(qs:
|
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(
|
16
|
-
cookies[:username] ==
|
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
|
-
|
22
|
-
client = build_client(qs:
|
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
|
-
|
30
|
-
client = build_client(qs:
|
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(
|
35
|
-
request.headers[
|
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
|
-
|
41
|
-
client = build_client(qs:
|
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
|
-
|
49
|
-
client = build_client(qs:
|
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 "
|
6
|
+
stream_from "request_a"
|
7
7
|
end
|
8
8
|
|
9
9
|
def unsubscribed
|
10
|
-
ActionCable.server.broadcast("
|
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 "
|
16
|
+
stream_from "request_b"
|
17
17
|
end
|
18
18
|
|
19
19
|
def unsubscribed
|
20
|
-
ActionCable.server.broadcast("
|
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
|
-
|
27
|
-
|
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" => {
|
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 = {
|
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" => {
|
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
|
-
|
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" => {
|
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 = {
|
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" => {
|
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" => {
|
76
|
-
"message" => {
|
83
|
+
"identifier" => {channel: a_channel}.to_json,
|
84
|
+
"message" => {"data" => "user left"}
|
77
85
|
}
|
78
86
|
msg2 = {
|
79
|
-
"identifier" => {
|
80
|
-
"message" => {
|
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
|
-
|
93
|
-
subscribe_request = {
|
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" => {
|
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 = {
|
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" => {
|
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" => {
|
117
|
-
"message" => {
|
134
|
+
"identifier" => {channel: c_channel}.to_json,
|
135
|
+
"message" => {"data" => "user left1"}
|
118
136
|
}
|
119
137
|
|
120
138
|
msg2 = {
|
121
|
-
"identifier" => {
|
122
|
-
"message" => {
|
139
|
+
"identifier" => {channel: c_channel}.to_json,
|
140
|
+
"message" => {"data" => "user left2"}
|
123
141
|
}
|
124
142
|
|
125
143
|
msgs = [client.receive, client.receive]
|