anyt 1.2.3 → 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 -194
  7. data/lib/anyt/client.rb +0 -112
  8. data/lib/anyt/command.rb +0 -63
  9. data/lib/anyt/config.rb +0 -41
  10. data/lib/anyt/dummy/application.rb +0 -83
  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 -150
  15. data/lib/anyt/remote_control.rb +0 -33
  16. data/lib/anyt/rpc.rb +0 -46
  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,83 +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
- module ApplicationCable
24
- class Connection < ActionCable::Connection::Base
25
- delegate :params, to: :request
26
-
27
- identified_by :uid
28
-
29
- def connect
30
- logger.debug "Connected"
31
- Anyt::ConnectHandlers.call(self)
32
- end
33
-
34
- def disconnect
35
- logger.debug "Disconnected"
36
- end
37
- end
38
- end
39
-
40
- module ApplicationCable
41
- class Channel < ActionCable::Channel::Base
42
- end
43
- end
44
-
45
- ECHO_DELAY = ENV["ANYCABLE_BENCHMARK_ECHO_DELAY"].to_f
46
-
47
- # BenchmarkChannel is useful when running Rails app only or RPC only
48
- class BenchmarkChannel < ApplicationCable::Channel
49
- def subscribed
50
- stream_from "all#{stream_id}"
51
- end
52
-
53
- def echo(data)
54
- if ECHO_DELAY > 0
55
- sleep ECHO_DELAY
56
- end
57
- transmit data
58
- end
59
-
60
- def broadcast(data)
61
- ActionCable.server.broadcast "all#{stream_id}", data
62
- data["action"] = "broadcastResult"
63
- transmit data
64
- end
65
-
66
-
67
- def counter(data)
68
- num = data.fetch("num", 100).to_i
69
- num.times { ActionCable.server.broadcast "all", {text: "Count: #{_1}"} }
70
- end
71
-
72
- private
73
-
74
- def stream_id
75
- params[:id] || ""
76
- end
77
- end
78
-
79
- ActionCable.server.config.cable = {"adapter" => "redis"}
80
- ActionCable.server.config.connection_class = -> { ApplicationCable::Connection }
81
- ActionCable.server.config.disable_request_forgery_protection = true
82
- ActionCable.server.config.logger =
83
- 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,150 +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/reporters"
6
-
7
- module Anyt
8
- # Common tests helpers
9
- module TestHelpers
10
- def self.included(base)
11
- base.let(:client) { build_client(ignore: %w[ping welcome]) }
12
- base.after { @clients&.each { |client| client.close(allow_messages: true) } }
13
- end
14
-
15
- def build_client(**args)
16
- @clients ||= []
17
- Anyt::Client.new(**args).tap do |client|
18
- @clients << client
19
- end
20
- end
21
-
22
- def restart_server!
23
- if Anyt.config.use_action_cable
24
- remote_client.restart_action_cable
25
- else
26
- Command.restart
27
- end
28
- end
29
-
30
- def remote_client
31
- @remote_client ||= RemoteControl::Client.connect(Anyt.config.remote_control_port)
32
- end
33
-
34
- # Verifies that the actual message Hash is a subset of the expected one
35
- # (so we can ignore some irrelevant fields)
36
- def assert_message(expected, actual)
37
- assert_equal expected, actual.slice(*expected.keys)
38
- end
39
-
40
- def assert_includes_message(collection, expected)
41
- found = collection.find do |el|
42
- el.slice(*expected.keys) == expected
43
- end
44
-
45
- assert found, "Expecte #{collection} to include a message matching #{expected}"
46
- end
47
- end
48
- end
49
-
50
- module Anyt
51
- # Namespace for test channels
52
- module TestChannels; end
53
-
54
- # Custom #connect handlers management
55
- module ConnectHandlers
56
- class << self
57
- def call(connection)
58
- handlers_for(connection).each do |(_, handler)|
59
- connection.reject_unauthorized_connection unless
60
- connection.instance_eval(&handler)
61
- end
62
- end
63
-
64
- def add(tag, &block)
65
- handlers << [tag, block]
66
- end
67
-
68
- private
69
-
70
- def handlers_for(connection)
71
- handlers.select do |(tag, _)|
72
- connection.params["test"] == tag
73
- end
74
- end
75
-
76
- def handlers
77
- @handlers ||= []
78
- end
79
- end
80
- end
81
- end
82
-
83
- # Kernel extensions
84
- module Kernel
85
- ## Wraps `describe` and include shared helpers
86
- private def feature(*args, &block)
87
- cls = describe(*args, &block)
88
- cls.include Anyt::TestHelpers
89
- cls
90
- end
91
- end
92
-
93
- # Extend Minitest Spec DSL with custom methodss
94
- module Minitest::Spec::DSL
95
- # Simplified version of `it` which doesn't care
96
- # about unique method names
97
- def scenario(desc, &block)
98
- block ||= proc { skip "(no tests defined)" }
99
-
100
- define_method "test_ #{desc}", &block
101
-
102
- desc
103
- end
104
-
105
- # Generates Channel class dynamically and
106
- # add memoized helper to access its name
107
- def channel(id = nil, &block)
108
- class_name = @name.gsub(/\s+/, "_")
109
- class_name += "_#{id}" if id
110
- class_name += "_channel"
111
-
112
- cls = Class.new(ApplicationCable::Channel, &block)
113
-
114
- Anyt::TestChannels.const_set(class_name.classify, cls)
115
-
116
- helper_name = id ? "#{id}_channel" : "channel"
117
-
118
- let(helper_name) { cls.name }
119
- end
120
-
121
- # Add new #connect handler
122
- def connect_handler(tag, &block)
123
- Anyt::ConnectHandlers.add(tag, &block)
124
- end
125
- end
126
-
127
- module Anyt
128
- # Patch Minitest load_plugins to disable Rails plugin
129
- # See: https://github.com/kern/minitest-reporters/issues/230
130
- module MinitestPatch
131
- def load_plugins
132
- super
133
- extensions.delete("rails")
134
- end
135
- end
136
-
137
- # Patch Spec reporter
138
- module ReporterPatch # :nodoc:
139
- def record_print_status(test)
140
- test_name = test.name.gsub(/^test_/, "").strip
141
- print(magenta { pad_test(test_name) })
142
- print_colored_status(test)
143
- print(" (%.2fs)" % test.time) unless test.time.nil?
144
- puts
145
- end
146
- end
147
- end
148
-
149
- Minitest::Reporters::SpecReporter.prepend Anyt::ReporterPatch
150
- 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,46 +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
- module RPC
10
- using AsyncHelpers
11
-
12
- class << self
13
- attr_accessor :running
14
- attr_reader :server
15
-
16
- def start
17
- AnyCable.logger.debug "Starting RPC server ..."
18
-
19
- AnyCable.server_callbacks.each(&:call)
20
-
21
- @server = AnyCable::GRPC::Server.new(
22
- host: AnyCable.config.rpc_host,
23
- **AnyCable.config.to_grpc_params
24
- )
25
-
26
- if defined?(::AnyCable::Middlewares::EnvSid)
27
- AnyCable.middleware.use(::AnyCable::Middlewares::EnvSid)
28
- end
29
-
30
- AnyCable.middleware.freeze
31
-
32
- server.start
33
-
34
- AnyCable.logger.debug "RPC server started"
35
- end
36
- # rubocop: enable Metrics/AbcSize,Metrics/MethodLength
37
-
38
- def stop
39
- server&.stop
40
- end
41
- end
42
-
43
- AnyCable.connection_factory = ActionCable.server.config.connection_class.call
44
- AnyCable.config.log_level = :fatal unless AnyCable.config.debug
45
- end
46
- 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