anyt-core 1.3.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 +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +38 -0
  4. data/bin/anyt +17 -0
  5. data/bin/console +7 -0
  6. data/bin/setup +8 -0
  7. data/lib/anyt/cli.rb +211 -0
  8. data/lib/anyt/client.rb +146 -0
  9. data/lib/anyt/command.rb +83 -0
  10. data/lib/anyt/config.rb +41 -0
  11. data/lib/anyt/dummy/application.rb +86 -0
  12. data/lib/anyt/dummy/config.ru +16 -0
  13. data/lib/anyt/dummy/routes.rb +4 -0
  14. data/lib/anyt/dummy/tmp/development_secret.txt +1 -0
  15. data/lib/anyt/dummy/tmp/local_secret.txt +1 -0
  16. data/lib/anyt/ext/minitest.rb +151 -0
  17. data/lib/anyt/remote_control.rb +33 -0
  18. data/lib/anyt/rpc.rb +44 -0
  19. data/lib/anyt/tests/core/ping_test.rb +23 -0
  20. data/lib/anyt/tests/core/welcome_test.rb +10 -0
  21. data/lib/anyt/tests/features/channel_state_test.rb +81 -0
  22. data/lib/anyt/tests/features/remote_disconnect_test.rb +35 -0
  23. data/lib/anyt/tests/features/server_restart_test.rb +30 -0
  24. data/lib/anyt/tests/request/channel_test.rb +28 -0
  25. data/lib/anyt/tests/request/connection_test.rb +54 -0
  26. data/lib/anyt/tests/request/disconnect_reasons_test.rb +25 -0
  27. data/lib/anyt/tests/request/disconnection_test.rb +148 -0
  28. data/lib/anyt/tests/streams/broadcast_test.rb +65 -0
  29. data/lib/anyt/tests/streams/multiple_clients_test.rb +61 -0
  30. data/lib/anyt/tests/streams/multiple_test.rb +60 -0
  31. data/lib/anyt/tests/streams/single_test.rb +83 -0
  32. data/lib/anyt/tests/streams/stop_test.rb +57 -0
  33. data/lib/anyt/tests/subscriptions/ack_test.rb +39 -0
  34. data/lib/anyt/tests/subscriptions/params_test.rb +29 -0
  35. data/lib/anyt/tests/subscriptions/perform_test.rb +60 -0
  36. data/lib/anyt/tests/subscriptions/transmissions_test.rb +32 -0
  37. data/lib/anyt/tests.rb +62 -0
  38. data/lib/anyt/utils/async_helpers.rb +16 -0
  39. data/lib/anyt/utils.rb +3 -0
  40. data/lib/anyt/version.rb +5 -0
  41. data/lib/anyt.rb +14 -0
  42. metadata +167 -0
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Broadcast data to stream" do
4
+ channel do
5
+ def subscribed
6
+ stream_from "a"
7
+ end
8
+ end
9
+
10
+ before do
11
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
12
+
13
+ client.send(subscribe_request)
14
+
15
+ ack = {
16
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
17
+ }
18
+
19
+ assert_message ack, client.receive
20
+ end
21
+
22
+ scenario %(
23
+ Broadcast object
24
+ ) do
25
+ ActionCable.server.broadcast(
26
+ "a",
27
+ {data: {user_id: 1, status: "left", meta: {connection_time: "10s"}}}
28
+ )
29
+
30
+ msg = {
31
+ "identifier" => {channel: channel}.to_json,
32
+ "message" => {
33
+ "data" => {"user_id" => 1, "status" => "left", "meta" => {"connection_time" => "10s"}}
34
+ }
35
+ }
36
+
37
+ assert_message msg, client.receive
38
+ end
39
+
40
+ scenario %(
41
+ Broadcast custom string
42
+ ) do
43
+ ActionCable.server.broadcast("a", "<script>alert('Message!');</script>")
44
+
45
+ msg = {
46
+ "identifier" => {channel: channel}.to_json,
47
+ "message" => "<script>alert('Message!');</script>"
48
+ }
49
+
50
+ assert_message msg, client.receive
51
+ end
52
+
53
+ scenario %(
54
+ Broadcast JSON string
55
+ ) do
56
+ ActionCable.server.broadcast("a", '{"script":{"alert":"Message!"}}')
57
+
58
+ msg = {
59
+ "identifier" => {channel: channel}.to_json,
60
+ "message" => '{"script":{"alert":"Message!"}}'
61
+ }
62
+
63
+ assert_message msg, client.receive
64
+ end
65
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Streams with many clients" do
4
+ channel do
5
+ def subscribed
6
+ stream_from "a"
7
+ end
8
+ end
9
+
10
+ let(:client2) { build_client(ignore: %w[ping welcome]) }
11
+
12
+ before do
13
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
14
+
15
+ client.send(subscribe_request)
16
+ client2.send(subscribe_request)
17
+
18
+ ack = {
19
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
20
+ }
21
+
22
+ assert_message ack, client.receive
23
+ assert_message ack, client2.receive
24
+ end
25
+
26
+ scenario %(
27
+ Multiple clients receive messages from stream
28
+ ) do
29
+ ActionCable.server.broadcast("a", {data: "X"})
30
+
31
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
32
+
33
+ assert_message msg, client.receive
34
+ assert_message msg, client2.receive
35
+ end
36
+
37
+ scenario %(
38
+ Client receive messages when another client removes subscription
39
+ ) do
40
+ ActionCable.server.broadcast("a", {data: "X"})
41
+
42
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
43
+
44
+ assert_message msg, client.receive
45
+ assert_message msg, client2.receive
46
+
47
+ unsubscribe_request = {command: "unsubscribe", identifier: {channel: channel}.to_json}
48
+
49
+ client.send(unsubscribe_request)
50
+
51
+ # ActionCable doesn't provide an unsubscription ack :(
52
+ sleep 1
53
+
54
+ ActionCable.server.broadcast("a", {data: "Y"})
55
+
56
+ msg2 = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "Y"}}
57
+
58
+ assert_message msg2, client2.receive
59
+ assert_raises(Anyt::Client::TimeoutError) { client.receive(timeout: 0.5) }
60
+ end
61
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Multiple streams" do
4
+ channel do
5
+ def subscribed
6
+ stream_from "a"
7
+ stream_from "b"
8
+ end
9
+ end
10
+
11
+ before do
12
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
13
+
14
+ client.send(subscribe_request)
15
+
16
+ ack = {
17
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
18
+ }
19
+
20
+ assert_message ack, client.receive
21
+ end
22
+
23
+ scenario %(
24
+ Client receives messages from both streams
25
+ ) do
26
+ ActionCable.server.broadcast("a", {data: "X"})
27
+
28
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
29
+
30
+ assert_message msg, client.receive
31
+
32
+ ActionCable.server.broadcast("b", {data: "Y"})
33
+
34
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "Y"}}
35
+
36
+ assert_message msg, client.receive
37
+ end
38
+
39
+ scenario %(
40
+ Client does not receive messages from any stream after removing subscription
41
+ ) do
42
+ ActionCable.server.broadcast("a", {data: "X"})
43
+
44
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
45
+
46
+ assert_message msg, client.receive
47
+
48
+ unsubscribe_request = {command: "unsubscribe", identifier: {channel: channel}.to_json}
49
+
50
+ client.send(unsubscribe_request)
51
+
52
+ # ActionCable doesn't provide an unsubscription ack :(
53
+ sleep 1
54
+
55
+ ActionCable.server.broadcast("a", {data: "Y"})
56
+ ActionCable.server.broadcast("b", {data: "Z"})
57
+
58
+ assert_raises(Anyt::Client::TimeoutError) { client.receive(timeout: 0.5) }
59
+ end
60
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Single stream" do
4
+ channel do
5
+ def subscribed
6
+ stream_from "a"
7
+ end
8
+ end
9
+
10
+ before do
11
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
12
+
13
+ client.send(subscribe_request)
14
+
15
+ ack = {
16
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
17
+ }
18
+
19
+ assert_message ack, client.receive
20
+ end
21
+
22
+ scenario %(
23
+ Client receives messages from the stream
24
+ ) do
25
+ ActionCable.server.broadcast("a", {data: "X"})
26
+
27
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
28
+
29
+ assert_message msg, client.receive
30
+
31
+ ActionCable.server.broadcast("a", {data: "Y"})
32
+
33
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "Y"}}
34
+
35
+ assert_message msg, client.receive
36
+ end
37
+
38
+ scenario %(
39
+ Client does not receive messages from the stream after removing subscription
40
+ ) do
41
+ ActionCable.server.broadcast("a", {data: "X"})
42
+
43
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
44
+
45
+ assert_message msg, client.receive
46
+
47
+ unsubscribe_request = {command: "unsubscribe", identifier: {channel: channel}.to_json}
48
+
49
+ client.send(unsubscribe_request)
50
+
51
+ # ActionCable doesn't provide an unsubscription ack :(
52
+ sleep 1
53
+
54
+ ActionCable.server.broadcast("a", {data: "Y"})
55
+
56
+ assert_raises(Anyt::Client::TimeoutError) { client.receive(timeout: 0.5) }
57
+ end
58
+
59
+ scenario %(
60
+ Client receives multiple messages when subscribed to the same channel
61
+ with different params
62
+ ) do
63
+ subscribe_request = {command: "subscribe", identifier: {channel: channel, some_param: "test"}.to_json}
64
+
65
+ client.send(subscribe_request)
66
+
67
+ ack = {
68
+ "identifier" => {channel: channel, some_param: "test"}.to_json, "type" => "confirm_subscription"
69
+ }
70
+
71
+ assert_message ack, client.receive
72
+
73
+ ActionCable.server.broadcast("a", {data: "XX"})
74
+
75
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "XX"}}
76
+ msg2 = {"identifier" => {channel: channel, some_param: "test"}.to_json, "message" => {"data" => "XX"}}
77
+
78
+ received = client.receive, client.receive
79
+
80
+ assert_includes_message received, msg
81
+ assert_includes_message received, msg2
82
+ end
83
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Stop streams" do
4
+ channel do
5
+ def subscribed
6
+ stream_from "a"
7
+ stream_from "b"
8
+ end
9
+
10
+ def ping(data)
11
+ ActionCable.server.broadcast data["name"], {reply: "pong"}
12
+ end
13
+
14
+ def unfollow(data)
15
+ stop_stream_from data["name"]
16
+ end
17
+ end
18
+
19
+ before do
20
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
21
+
22
+ client.send(subscribe_request)
23
+
24
+ ack = {
25
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
26
+ }
27
+
28
+ assert_message ack, client.receive
29
+ end
30
+
31
+ scenario %(
32
+ Client unsubscribes from the stream
33
+ ) do
34
+ skip if Anyt.config.use_action_cable && (::ActionCable::VERSION::MAJOR < 6 || ::ActionCable::VERSION::MINOR < 1)
35
+
36
+ ActionCable.server.broadcast("a", {data: "X"})
37
+
38
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "X"}}
39
+
40
+ assert_message msg, client.receive
41
+
42
+ perform_request = {
43
+ :command => "message",
44
+ :identifier => {channel: channel}.to_json,
45
+ "data" => {"action" => "unfollow", "name" => "a"}.to_json
46
+ }
47
+ client.send(perform_request)
48
+ sleep 0.2 # give some time to commit unsubscribe
49
+
50
+ ActionCable.server.broadcast("a", {data: "Y"})
51
+ sleep 0.2 # "a" should be broadcasted first
52
+ ActionCable.server.broadcast("b", {data: "Z"})
53
+
54
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"data" => "Z"}}
55
+ assert_message msg, client.receive
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Subscription aknowledgement" do
4
+ channel
5
+
6
+ channel("rejector") do
7
+ def subscribed
8
+ reject
9
+ end
10
+ end
11
+
12
+ scenario %(
13
+ Client receives subscription confirmation
14
+ ) do
15
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
16
+
17
+ client.send(subscribe_request)
18
+
19
+ ack = {
20
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
21
+ }
22
+
23
+ assert_message ack, client.receive
24
+ end
25
+
26
+ scenario %(
27
+ Client receives subscription rejection
28
+ ) do
29
+ subscribe_request = {command: "subscribe", identifier: {channel: rejector_channel}.to_json}
30
+
31
+ client.send(subscribe_request)
32
+
33
+ ack = {
34
+ "identifier" => {channel: rejector_channel}.to_json, "type" => "reject_subscription"
35
+ }
36
+
37
+ assert_message ack, client.receive
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Subscription with params" do
4
+ channel
5
+
6
+ scenario %(
7
+ Client subscribes to the same channel with different params
8
+ ) do
9
+ subscribe_request = {command: "subscribe", identifier: {channel: channel, id: 1}.to_json}
10
+
11
+ client.send(subscribe_request)
12
+
13
+ ack = {
14
+ "identifier" => {channel: channel, id: 1}.to_json, "type" => "confirm_subscription"
15
+ }
16
+
17
+ assert_message ack, client.receive
18
+
19
+ subscribe_request_2 = {command: "subscribe", identifier: {channel: channel, id: 2}.to_json}
20
+
21
+ client.send(subscribe_request_2)
22
+
23
+ ack = {
24
+ "identifier" => {channel: channel, id: 2}.to_json, "type" => "confirm_subscription"
25
+ }
26
+
27
+ assert_message ack, client.receive
28
+ end
29
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyt/utils"
4
+ using Anyt::AsyncHelpers
5
+
6
+ feature "Subscription perform methods" do
7
+ channel do
8
+ def tick
9
+ transmit("tock")
10
+ end
11
+
12
+ def echo(data)
13
+ transmit({response: data["text"]})
14
+ end
15
+ end
16
+
17
+ before do
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_message ack, client.receive
27
+ end
28
+
29
+ scenario %(
30
+ Client perform actions without arguments
31
+ ) do
32
+ perform_request = {
33
+ :command => "message",
34
+ :identifier => {channel: channel}.to_json,
35
+ "data" => {"action" => "tick"}.to_json
36
+ }
37
+
38
+ client.send(perform_request)
39
+
40
+ msg = {"identifier" => {channel: channel}.to_json, "message" => "tock"}
41
+
42
+ assert_message msg, client.receive
43
+ end
44
+
45
+ scenario %(
46
+ Client perform actions with arguments
47
+ ) do
48
+ perform_request = {
49
+ :command => "message",
50
+ :identifier => {channel: channel}.to_json,
51
+ "data" => {"action" => "echo", "text" => "hello"}.to_json
52
+ }
53
+
54
+ client.send(perform_request)
55
+
56
+ msg = {"identifier" => {channel: channel}.to_json, "message" => {"response" => "hello"}}
57
+
58
+ assert_message msg, client.receive
59
+ end
60
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ feature "Subscription transmissions" do
4
+ channel do
5
+ def subscribed
6
+ transmit("hello")
7
+ transmit("world")
8
+ end
9
+ end
10
+
11
+ scenario %(
12
+ Client receives transmissions from #subscribed callback
13
+ ) do
14
+ subscribe_request = {command: "subscribe", identifier: {channel: channel}.to_json}
15
+
16
+ client.send(subscribe_request)
17
+
18
+ msg = {"identifier" => {channel: channel}.to_json, "message" => "hello"}
19
+
20
+ assert_message msg, client.receive
21
+
22
+ msg = {"identifier" => {channel: channel}.to_json, "message" => "world"}
23
+
24
+ assert_message msg, client.receive
25
+
26
+ ack = {
27
+ "identifier" => {channel: channel}.to_json, "type" => "confirm_subscription"
28
+ }
29
+
30
+ assert_message ack, client.receive
31
+ end
32
+ end
data/lib/anyt/tests.rb ADDED
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyt"
4
+ require "anyt/client"
5
+ require_relative "ext/minitest"
6
+
7
+ module Anyt
8
+ # Loads and runs test cases
9
+ module Tests
10
+ class << self
11
+ DEFAULT_PATTERNS = [
12
+ File.expand_path("tests/**/*.rb", __dir__)
13
+ ].freeze
14
+
15
+ # Run all loaded tests
16
+ def run
17
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
18
+
19
+ AnyCable.logger.debug "Run tests against: #{Anyt.config.target_url}"
20
+ Minitest.run
21
+ end
22
+
23
+ # Load tests code (filtered if present)
24
+ #
25
+ # NOTE: We should run this before launching RPC server
26
+
27
+ def load_tests
28
+ return load_all_tests unless Anyt.config.filter_tests?
29
+
30
+ skipped = []
31
+ filter = Anyt.config.tests_filter
32
+
33
+ test_files_patterns.each do |pattern|
34
+ Dir.glob(pattern).sort.each do |file|
35
+ if filter.call(file)
36
+ require file
37
+ else
38
+ skipped << file.gsub(File.join(__dir__, "tests/"), "").gsub("_test.rb", "")
39
+ end
40
+ end
41
+ end
42
+
43
+ $stdout.print "Skipping tests: #{skipped.join(", ")}\n"
44
+ end
45
+
46
+ # Load all test files
47
+ def load_all_tests
48
+ test_files_patterns.each do |pattern|
49
+ Dir.glob(pattern).sort.each { |file| require file }
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def test_files_patterns
56
+ @test_files_patterns ||= DEFAULT_PATTERNS.dup.tap do |patterns|
57
+ patterns << Anyt.config.tests_path if Anyt.config.tests_path
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyt
4
+ module AsyncHelpers # :nodoc:
5
+ refine Object do
6
+ # Wait for block to return true of raise error
7
+ def wait(timeout = 1)
8
+ until yield
9
+ sleep 0.1
10
+ timeout -= 0.1
11
+ raise "Timeout error" unless timeout.positive?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/anyt/utils.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyt/utils/async_helpers"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anyt
4
+ VERSION = "1.3.0"
5
+ end
data/lib/anyt.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyt/version"
4
+ require "anyt/config"
5
+ require "anyt/utils"
6
+
7
+ # AnyCable conformance testing tool
8
+ module Anyt
9
+ class << self
10
+ def config
11
+ @config ||= Config.new
12
+ end
13
+ end
14
+ end