anyt 1.2.4 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. metadata +13 -119
  4. data/bin/console +0 -7
  5. data/bin/setup +0 -8
  6. data/lib/anyt/cli.rb +0 -211
  7. data/lib/anyt/client.rb +0 -112
  8. data/lib/anyt/command.rb +0 -83
  9. data/lib/anyt/config.rb +0 -41
  10. data/lib/anyt/dummy/application.rb +0 -87
  11. data/lib/anyt/dummy/config.ru +0 -16
  12. data/lib/anyt/dummy/routes.rb +0 -4
  13. data/lib/anyt/dummy/tmp/development_secret.txt +0 -1
  14. data/lib/anyt/ext/minitest.rb +0 -151
  15. data/lib/anyt/remote_control.rb +0 -33
  16. data/lib/anyt/rpc.rb +0 -44
  17. data/lib/anyt/tests/core/ping_test.rb +0 -23
  18. data/lib/anyt/tests/core/welcome_test.rb +0 -10
  19. data/lib/anyt/tests/features/channel_state_test.rb +0 -81
  20. data/lib/anyt/tests/features/remote_disconnect_test.rb +0 -35
  21. data/lib/anyt/tests/features/server_restart_test.rb +0 -30
  22. data/lib/anyt/tests/request/channel_test.rb +0 -28
  23. data/lib/anyt/tests/request/connection_test.rb +0 -54
  24. data/lib/anyt/tests/request/disconnect_reasons_test.rb +0 -25
  25. data/lib/anyt/tests/request/disconnection_test.rb +0 -148
  26. data/lib/anyt/tests/streams/broadcast_test.rb +0 -65
  27. data/lib/anyt/tests/streams/multiple_clients_test.rb +0 -61
  28. data/lib/anyt/tests/streams/multiple_test.rb +0 -60
  29. data/lib/anyt/tests/streams/single_test.rb +0 -83
  30. data/lib/anyt/tests/streams/stop_test.rb +0 -57
  31. data/lib/anyt/tests/subscriptions/ack_test.rb +0 -39
  32. data/lib/anyt/tests/subscriptions/params_test.rb +0 -29
  33. data/lib/anyt/tests/subscriptions/perform_test.rb +0 -60
  34. data/lib/anyt/tests/subscriptions/transmissions_test.rb +0 -32
  35. data/lib/anyt/tests.rb +0 -62
  36. data/lib/anyt/utils/async_helpers.rb +0 -16
  37. data/lib/anyt/utils.rb +0 -3
  38. data/lib/anyt/version.rb +0 -5
  39. data/lib/anyt.rb +0 -14
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails"
4
- require "action_controller/railtie"
5
- require "action_cable/engine"
6
-
7
- require "redis"
8
- require "anycable-rails"
9
-
10
- require "action_dispatch/middleware/cookies"
11
-
12
- class TestApp < Rails::Application
13
- secrets.secret_token = "secret_token"
14
- secrets.secret_key_base = "secret_key_base"
15
-
16
- config.logger = Logger.new($stdout)
17
- config.log_level = AnyCable.config.log_level
18
- config.eager_load = true
19
-
20
- config.paths["config/routes.rb"] << File.join(__dir__, "routes.rb")
21
- end
22
-
23
- DISCONNECT_DELAY = ENV["ANYCABLE_DISCONNECT_DELAY"].to_f
24
-
25
- module ApplicationCable
26
- class Connection < ActionCable::Connection::Base
27
- delegate :params, to: :request
28
-
29
- identified_by :uid
30
-
31
- def connect
32
- logger.debug "Connected"
33
- Anyt::ConnectHandlers.call(self)
34
- end
35
-
36
- def disconnect
37
- if DISCONNECT_DELAY > 0
38
- sleep DISCONNECT_DELAY
39
- end
40
- logger.debug "Disconnected"
41
- end
42
- end
43
- end
44
-
45
- module ApplicationCable
46
- class Channel < ActionCable::Channel::Base
47
- end
48
- end
49
-
50
- ECHO_DELAY = ENV["ANYCABLE_BENCHMARK_ECHO_DELAY"].to_f
51
-
52
- # BenchmarkChannel is useful when running Rails app only or RPC only
53
- class BenchmarkChannel < ApplicationCable::Channel
54
- def subscribed
55
- stream_from "all#{stream_id}"
56
- end
57
-
58
- def echo(data)
59
- if ECHO_DELAY > 0
60
- sleep ECHO_DELAY
61
- end
62
- transmit data
63
- end
64
-
65
- def broadcast(data)
66
- ActionCable.server.broadcast "all#{stream_id}", data
67
- data["action"] = "broadcastResult"
68
- transmit data
69
- end
70
-
71
- def counter(data)
72
- num = data.fetch("num", 100).to_i
73
- num.times { ActionCable.server.broadcast "all", {text: "Count: #{_1}"} }
74
- end
75
-
76
- private
77
-
78
- def stream_id
79
- params[:id] || ""
80
- end
81
- end
82
-
83
- ActionCable.server.config.cable = {"adapter" => ENV.fetch("ACTION_CABLE_ADAPTER", "redis")}
84
- ActionCable.server.config.connection_class = -> { ApplicationCable::Connection }
85
- ActionCable.server.config.disable_request_forgery_protection = true
86
- ActionCable.server.config.logger =
87
- Rails.logger
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "application"
4
-
5
- require_relative "../tests"
6
- require_relative "../remote_control"
7
-
8
- # Start remote control
9
- Anyt::RemoteControl::Server.start(Anyt.config.remote_control_port)
10
-
11
- # Load channels from tests
12
- Anyt::Tests.load_all_tests
13
-
14
- Rails.application.initialize!
15
-
16
- run Rails.application
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Rails.application.routes.draw do
4
- end
@@ -1 +0,0 @@
1
- fe2ecbcf229d5547a64bcdaa9ef4e543404ca3fc9d4ee83aaa7ddab34b6a378fe090c2b1836c9ebd3e5ebbf01c239ddac95e219358baaefc1afaa04cd07bf78c
@@ -1,151 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ENV["TERM"] = "#{ENV["TERM"]}color" unless ENV["TERM"]&.match?(/color/)
4
- require "minitest/spec"
5
- require "minitest/unit"
6
- require "minitest/reporters"
7
-
8
- module Anyt
9
- # Common tests helpers
10
- module TestHelpers
11
- def self.included(base)
12
- base.let(:client) { build_client(ignore: %w[ping welcome]) }
13
- base.after { @clients&.each { |client| client.close(allow_messages: true) } }
14
- end
15
-
16
- def build_client(**args)
17
- @clients ||= []
18
- Anyt::Client.new(**args).tap do |client|
19
- @clients << client
20
- end
21
- end
22
-
23
- def restart_server!
24
- if Anyt.config.use_action_cable
25
- remote_client.restart_action_cable
26
- else
27
- Command.restart
28
- end
29
- end
30
-
31
- def remote_client
32
- @remote_client ||= RemoteControl::Client.connect(Anyt.config.remote_control_port)
33
- end
34
-
35
- # Verifies that the actual message Hash is a subset of the expected one
36
- # (so we can ignore some irrelevant fields)
37
- def assert_message(expected, actual)
38
- assert_equal expected, actual.slice(*expected.keys)
39
- end
40
-
41
- def assert_includes_message(collection, expected)
42
- found = collection.find do |el|
43
- el.slice(*expected.keys) == expected
44
- end
45
-
46
- assert found, "Expecte #{collection} to include a message matching #{expected}"
47
- end
48
- end
49
- end
50
-
51
- module Anyt
52
- # Namespace for test channels
53
- module TestChannels; end
54
-
55
- # Custom #connect handlers management
56
- module ConnectHandlers
57
- class << self
58
- def call(connection)
59
- handlers_for(connection).each do |(_, handler)|
60
- connection.reject_unauthorized_connection unless
61
- connection.instance_eval(&handler)
62
- end
63
- end
64
-
65
- def add(tag, &block)
66
- handlers << [tag, block]
67
- end
68
-
69
- private
70
-
71
- def handlers_for(connection)
72
- handlers.select do |(tag, _)|
73
- connection.params["test"] == tag
74
- end
75
- end
76
-
77
- def handlers
78
- @handlers ||= []
79
- end
80
- end
81
- end
82
- end
83
-
84
- # Kernel extensions
85
- module Kernel
86
- ## Wraps `describe` and include shared helpers
87
- private def feature(*args, &block)
88
- cls = describe(*args, &block)
89
- cls.include Anyt::TestHelpers
90
- cls
91
- end
92
- end
93
-
94
- # Extend Minitest Spec DSL with custom methodss
95
- module Minitest::Spec::DSL
96
- # Simplified version of `it` which doesn't care
97
- # about unique method names
98
- def scenario(desc, &block)
99
- block ||= proc { skip "(no tests defined)" }
100
-
101
- define_method "test_ #{desc}", &block
102
-
103
- desc
104
- end
105
-
106
- # Generates Channel class dynamically and
107
- # add memoized helper to access its name
108
- def channel(id = nil, &block)
109
- class_name = @name.gsub(/\s+/, "_")
110
- class_name += "_#{id}" if id
111
- class_name += "_channel"
112
-
113
- cls = Class.new(ApplicationCable::Channel, &block)
114
-
115
- Anyt::TestChannels.const_set(class_name.classify, cls)
116
-
117
- helper_name = id ? "#{id}_channel" : "channel"
118
-
119
- let(helper_name) { cls.name }
120
- end
121
-
122
- # Add new #connect handler
123
- def connect_handler(tag, &block)
124
- Anyt::ConnectHandlers.add(tag, &block)
125
- end
126
- end
127
-
128
- module Anyt
129
- # Patch Minitest load_plugins to disable Rails plugin
130
- # See: https://github.com/kern/minitest-reporters/issues/230
131
- module MinitestPatch
132
- def load_plugins
133
- super
134
- extensions.delete("rails")
135
- end
136
- end
137
-
138
- # Patch Spec reporter
139
- module ReporterPatch # :nodoc:
140
- def record_print_status(test)
141
- test_name = test.name.gsub(/^test_/, "").strip
142
- print(magenta { pad_test(test_name) })
143
- print_colored_status(test)
144
- print(" (%.2fs)" % test.time) unless test.time.nil?
145
- puts
146
- end
147
- end
148
- end
149
-
150
- Minitest::Reporters::SpecReporter.prepend Anyt::ReporterPatch
151
- Minitest.singleton_class.prepend Anyt::MinitestPatch
@@ -1,33 +0,0 @@
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_method :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 DELETED
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "anyt/dummy/application"
4
- require "anycable-rails"
5
- require "redis"
6
-
7
- module Anyt # :nodoc:
8
- # Runs AnyCable RPC server in the background
9
- class RPC
10
- using AsyncHelpers
11
-
12
- attr_accessor :running
13
- attr_reader :server
14
-
15
- def start
16
- AnyCable.logger.debug "Starting RPC server ..."
17
-
18
- AnyCable.server_callbacks.each(&:call)
19
-
20
- @server = AnyCable::GRPC::Server.new(
21
- host: AnyCable.config.rpc_host,
22
- **AnyCable.config.to_grpc_params
23
- )
24
-
25
- if defined?(::AnyCable::Middlewares::EnvSid)
26
- AnyCable.middleware.use(::AnyCable::Middlewares::EnvSid)
27
- end
28
-
29
- AnyCable.middleware.freeze
30
-
31
- server.start
32
-
33
- AnyCable.logger.debug "RPC server started"
34
- end
35
- # rubocop: enable Metrics/AbcSize,Metrics/MethodLength
36
-
37
- def stop
38
- server&.stop
39
- end
40
-
41
- AnyCable.connection_factory = ActionCable.server.config.connection_class.call
42
- AnyCable.config.log_level = :fatal unless AnyCable.config.debug
43
- end
44
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- feature "Ping" do
4
- scenario %(
5
- Client receives pings with timestamps
6
- ) do
7
- client = build_client(ignore: ["welcome"])
8
-
9
- previous_stamp = 0
10
-
11
- 2.times do
12
- ping = client.receive
13
-
14
- current_stamp = ping["message"]
15
-
16
- assert_equal "ping", ping["type"]
17
- assert_kind_of Integer, current_stamp
18
- refute_operator previous_stamp, :>=, current_stamp
19
-
20
- previous_stamp = current_stamp
21
- end
22
- end
23
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- feature "Welcome" do
4
- scenario %(
5
- Client receives "welcome" message
6
- ) do
7
- client = build_client(ignore: ["ping"])
8
- assert_message({"type" => "welcome"}, client.receive)
9
- end
10
- end
@@ -1,81 +0,0 @@
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_message 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_message 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_message ack, client2.receive
71
-
72
- client2.close
73
-
74
- msg = {
75
- "identifier" => identifier,
76
- "message" => {"data" => "user left: chipollone"}
77
- }
78
-
79
- assert_message msg, client.receive
80
- end
81
- end
@@ -1,35 +0,0 @@
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_message({"type" => "welcome"}, client.receive)
14
-
15
- # Prevent race conditions when we send disconnect before internal channel subscription has been made
16
- # (only for Action Cable)
17
- sleep 1
18
- ActionCable.server.remote_connections.where(uid: "26").disconnect
19
-
20
- # Waiting for https://github.com/rails/rails/pull/39544
21
- unless Anyt.config.use_action_cable
22
- assert_message(
23
- {
24
- "type" => "disconnect",
25
- "reconnect" => true,
26
- "reason" => "remote"
27
- },
28
- client.receive
29
- )
30
- end
31
-
32
- client.wait_for_close
33
- assert client.closed?
34
- end
35
- end
@@ -1,30 +0,0 @@
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_message({"type" => "welcome"}, client.receive)
18
-
19
- restart_server!
20
-
21
- assert_message(
22
- {
23
- "type" => "disconnect",
24
- "reconnect" => true,
25
- "reason" => "server_restart"
26
- },
27
- client.receive
28
- )
29
- end
30
- end
@@ -1,28 +0,0 @@
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_message({"type" => "welcome"}, client.receive)
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_message ack, client.receive
27
- end
28
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- feature "Request" do
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
8
- end
9
-
10
- scenario %(
11
- Url is set during connection
12
- ) do
13
- client = build_client(qs: "test=request_url")
14
- assert_message({"type" => "welcome"}, client.receive)
15
- end
16
-
17
- connect_handler("cookies") do
18
- cookies[:username] == "john green"
19
- end
20
-
21
- scenario %(
22
- Reject when required cookies are not set
23
- ) do
24
- client = build_client(qs: "test=cookies")
25
- client.wait_for_close
26
- assert client.closed?
27
- end
28
-
29
- scenario %(
30
- Accepts when required cookies are set
31
- ) do
32
- client = build_client(qs: "test=cookies", cookies: "username=john green")
33
- assert_message({"type" => "welcome"}, client.receive)
34
- end
35
-
36
- connect_handler("headers") do
37
- request.headers["X-API-TOKEN"] == "abc"
38
- end
39
-
40
- scenario %(
41
- Reject when required header is missing
42
- ) do
43
- client = build_client(qs: "test=headers")
44
- client.wait_for_close
45
- assert client.closed?
46
- end
47
-
48
- scenario %(
49
- Accepts when required header is set
50
- ) do
51
- client = build_client(qs: "test=headers", headers: {"x-api-token" => "abc"})
52
- assert_message({"type" => "welcome"}, client.receive)
53
- end
54
- end
@@ -1,25 +0,0 @@
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_message(
14
- {
15
- "type" => "disconnect",
16
- "reconnect" => false,
17
- "reason" => "unauthorized"
18
- },
19
- client.receive
20
- )
21
-
22
- client.wait_for_close
23
- assert client.closed?
24
- end
25
- end