anyt 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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