File: action_cable_subscription_test.rb

package info (click to toggle)
ruby-graphql 2.2.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 9,584 kB
  • sloc: ruby: 67,505; ansic: 1,753; yacc: 831; javascript: 331; makefile: 6
file content (172 lines) | stat: -rw-r--r-- 6,924 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# frozen_string_literal: true
require "application_system_test_case"

class ActionCableSubscriptionsTest < ApplicationSystemTestCase
  # This test covers a lot of ground!
  test "it handles subscriptions" do
    # Load the page and let the subscriptions happen
    visit "/"
    # make sure they connect successfully
    assert_selector "#updates-1-connected"
    assert_selector "#updates-2-connected"

    # Trigger a few updates, make sure we get a client update:
    click_on("Trigger 1")
    click_on("Trigger 1")
    click_on("Trigger 1")
    assert_selector "#updates-1-3", text: "3"
    # Make sure there aren't any unexpected elements:
    refute_selector "#updates-1-4"
    refute_selector "#updates-2-1"

    # Now, trigger updates to a different stream
    # and make sure the previous stream is not affected
    click_on("Trigger 2")
    click_on("Trigger 2")
    assert_selector "#updates-2-1", text: "1"
    assert_selector "#updates-2-2", text: "2"
    refute_selector "#updates-2-3"
    refute_selector "#updates-1-4"

    # Now unsubscribe one, it should not receive updates but the other should
    click_on("Unsubscribe 1")
    click_on("Trigger 1")
    # This should not have changed
    refute_selector "#updates-1-4"

    click_on("Trigger 2")
    assert_selector "#updates-2-3", text: "3"
    refute_selector "#updates-1-4"

    # wacky behavior to make sure the custom serializer is used:
    click_on("Trigger 2")
    assert_selector "#updates-2-400", text: "400"
  end

  # Wrap an `assert_selector` call in a debugging check
  def debug_assert_selector(selector)
    if !page.has_css?(selector)
      puts "[debug_assert_selector(#{selector.inspect})] Failed to find #{selector.inspect} in:"
      puts page.html
    else
      puts "[debug_assert_selector(#{selector.inspect})] Found #{selector.inspect}"
    end
    assert_selector(selector)
  end

  # It seems like ActionCable's order of evaluation here is non-deterministic,
  # so detect which order to make the assertions.
  # (They still both have to pass, but we don't know exactly what order the evaluations went in.)
  def detect_update_values(possibility_1, possibility_2)
    if page.has_css?("#fingerprint-updates-1-update-1-value-#{possibility_1}")
      [possibility_1, possibility_2]
    else
      [possibility_2, possibility_1]
    end
  end


  test "it only re-runs queries once for subscriptions with matching fingerprints" do
    GraphqlChannel::CounterIncremented.reset_call_count
    visit "/"
    using_wait_time 10 do
      sleep 1
      # Make 3 subscriptions to the same payload
      click_on("Subscribe with fingerprint 1")

      debug_assert_selector "#fingerprint-updates-1-connected-1"

      click_on("Subscribe with fingerprint 1")
      debug_assert_selector "#fingerprint-updates-1-connected-2"
      click_on("Subscribe with fingerprint 1")
      debug_assert_selector "#fingerprint-updates-1-connected-3"

      # And two to the next payload
      click_on("Subscribe with fingerprint 2")
      debug_assert_selector "#fingerprint-updates-2-connected-1"
      click_on("Subscribe with fingerprint 2")
      debug_assert_selector "#fingerprint-updates-2-connected-2"

      # Now trigger. We expect a total of two updates:
      # - One is built & delivered to the first three subscribers
      # - Another is built & delivered to the next two
      click_on("Trigger with fingerprint 2")

      # The order here is random, I think depending on ActionCable's internal storage:
      fingerprint_1_value, fingerprint_2_value = detect_update_values(1, 2)

      # These all share the first value:
      debug_assert_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value}"
      debug_assert_selector "#fingerprint-updates-1-update-2-value-#{fingerprint_1_value}"
      debug_assert_selector "#fingerprint-updates-1-update-3-value-#{fingerprint_1_value}"
      # and these share the second value:
      debug_assert_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value}"
      debug_assert_selector "#fingerprint-updates-2-update-2-value-#{fingerprint_2_value}"

      click_on("Unsubscribe with fingerprint 2")
      click_on("Trigger with fingerprint 1")

      fingerprint_1_value_2, fingerprint_2_value_2 = detect_update_values(3, 4)

      # These get an update
      debug_assert_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2}"
      debug_assert_selector "#fingerprint-updates-1-update-2-value-#{fingerprint_1_value_2}"
      debug_assert_selector "#fingerprint-updates-1-update-3-value-#{fingerprint_1_value_2}"
      # But these are unsubscribed:
      refute_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value_2}"
      refute_selector "#fingerprint-updates-2-update-2-value-#{fingerprint_2_value_2}"
      click_on("Unsubscribe with fingerprint 1")
      # Make a new subscription and make sure it's updated:
      click_on("Subscribe with fingerprint 2")
      click_on("Trigger with fingerprint 2")
      debug_assert_selector "#fingerprint-updates-2-update-1-value-#{fingerprint_2_value_2}"
      # But this one was unsubscribed:
      refute_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2 + 1}"
      refute_selector "#fingerprint-updates-1-update-1-value-#{fingerprint_1_value_2 + 2}"
    end
  end

  test "it unsubscribes from the server" do
    GraphqlChannel::CounterIncremented.reset_call_count
    visit "/"
    using_wait_time 10 do
      sleep 1
      # Establish the connection
      click_on("Subscribe with fingerprint 1")
      debug_assert_selector "#fingerprint-updates-1-connected-1"
      # Trigger once
      click_on("Trigger with fingerprint 1")
      debug_assert_selector "#fingerprint-updates-1-update-1-value-1"

      # Server unsubscribe
      click_on("Server-side unsubscribe with fingerprint 1")
      # Subsequent updates should fail
      click_on("Trigger with fingerprint 1")
      refute_selector "#fingerprint-updates-1-update-2-value-2"

      # The client has only 2 connections (from the initial 2)
      assert_text "Remaining ActionCable subscriptions: 2"
    end
  end

  test "it unsubscribes with a message" do
    GraphqlChannel::CounterIncremented.reset_call_count
    visit "/"
    using_wait_time 10 do
      sleep 1
      # Establish the connection
      click_on("Subscribe with fingerprint 1")
      debug_assert_selector "#fingerprint-updates-1-connected-1"
      # Trigger once
      click_on("Trigger with fingerprint 1")
      debug_assert_selector "#fingerprint-updates-1-update-1-value-1"

      # Server unsubscribe
      click_on("Unsubscribe with message with fingerprint 1")
      # Magic value from unsubscribe hook:
      debug_assert_selector "#fingerprint-updates-1-update-1-value-9999"
      # The client has only 2 connections (from the initial 2)
      assert_text "Remaining ActionCable subscriptions: 2"
    end
  end
end