feat: extend blog_websocket_demo
This commit is contained in:
1 parent
ab7787200f
commit
321d3416d4
1 file changed
+95
@@ -157,3 +157,98 @@ Net::HTTP.start('127.0.0.1', port) do |http|
|
|||||||
puts "GET /cable?post_id=#{post_id} (4th concurrent WS connection) -> 503 Service Unavailable " \
|
puts "GET /cable?post_id=#{post_id} (4th concurrent WS connection) -> 503 Service Unavailable " \
|
||||||
'(max_websocket_connections: 3 reached, rejected before any Upgrade)'
|
'(max_websocket_connections: 3 reached, rejected before any Upgrade)'
|
||||||
|
|
||||||
|
comment_body = URI.encode_www_form('comment[author]' => 'Reader', 'comment[body]' => 'First!', 'authenticity_token' => token)
|
||||||
|
comment = http.post("/posts/#{post_id}/comments", comment_body,
|
||||||
|
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
|
||||||
|
raise "expected the comment to be created, got #{comment.code}" unless comment.code == '302'
|
||||||
|
cookie = cookie_from(comment)
|
||||||
|
show = http.get(post_path, 'Cookie' => cookie)
|
||||||
|
comment_id = show.body[/id="comment-(\d+)"/, 1]
|
||||||
|
raise 'could not find the new comment id on the post page' unless comment_id
|
||||||
|
puts "POST /posts/#{post_id}/comments -> #{comment.code} (still pending, no broadcast yet)"
|
||||||
|
|
||||||
|
approve_body = URI.encode_www_form('_method' => 'PATCH', 'authenticity_token' => token)
|
||||||
|
approve = http.post("/posts/#{post_id}/comments/#{comment_id}/approve", approve_body,
|
||||||
|
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
|
||||||
|
raise "expected the approval to redirect, got #{approve.code}" unless approve.code == '302'
|
||||||
|
cookie = cookie_from(approve)
|
||||||
|
|
||||||
|
event_a = read_event(viewer_a)
|
||||||
|
event_b = read_event(viewer_b)
|
||||||
|
expected = { 'event' => 'comment_count', 'post_id' => post_id, 'count' => 1 }
|
||||||
|
raise "viewer_a expected #{expected.inspect}, got #{event_a.inspect}" unless event_a == expected
|
||||||
|
raise "viewer_b expected #{expected.inspect}, got #{event_b.inspect}" unless event_b == expected
|
||||||
|
puts "POST /posts/#{post_id}/comments/#{comment_id}/approve -> #{approve.code}, " \
|
||||||
|
"both live viewers pushed {event: comment_count, count: 1} over the real socket"
|
||||||
|
|
||||||
|
reject_body = URI.encode_www_form('_method' => 'PATCH', 'authenticity_token' => token)
|
||||||
|
reject = http.post("/posts/#{post_id}/comments/#{comment_id}/reject", reject_body,
|
||||||
|
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
|
||||||
|
raise "expected the rejection to redirect, got #{reject.code}" unless reject.code == '302'
|
||||||
|
cookie = cookie_from(reject)
|
||||||
|
|
||||||
|
event_a = read_event(viewer_a)
|
||||||
|
expected = { 'event' => 'comment_count', 'post_id' => post_id, 'count' => 0 }
|
||||||
|
raise "viewer_a expected #{expected.inspect} after rejection, got #{event_a.inspect}" unless event_a == expected
|
||||||
|
puts "POST /posts/#{post_id}/comments/#{comment_id}/reject -> #{reject.code}, " \
|
||||||
|
"live viewer pushed {event: comment_count, count: 0} (approved -> rejected demotes the count back down)"
|
||||||
|
|
||||||
|
# Re-approve, then destroy outright - the third mutation that can move
|
||||||
|
# the visible count, and the last one this demo has left to prove.
|
||||||
|
http.post("/posts/#{post_id}/comments/#{comment_id}/approve", approve_body,
|
||||||
|
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
|
||||||
|
read_event(viewer_a) # count -> 1 again, already proven above
|
||||||
|
|
||||||
|
destroy_body = URI.encode_www_form('_method' => 'DELETE', 'authenticity_token' => token)
|
||||||
|
destroy = http.post("/posts/#{post_id}/comments/#{comment_id}", destroy_body,
|
||||||
|
'Content-Type' => 'application/x-www-form-urlencoded', 'Cookie' => cookie)
|
||||||
|
raise "expected the destroy to redirect, got #{destroy.code}" unless destroy.code == '302'
|
||||||
|
|
||||||
|
event_a = read_event(viewer_a)
|
||||||
|
event_b = read_event(viewer_b)
|
||||||
|
expected = { 'event' => 'comment_count', 'post_id' => post_id, 'count' => 0 }
|
||||||
|
raise "viewer_a expected #{expected.inspect} after destroy, got #{event_a.inspect}" unless event_a == expected
|
||||||
|
raise "viewer_b expected #{expected.inspect} after destroy, got #{event_b.inspect}" unless event_b == expected
|
||||||
|
puts "DELETE /posts/#{post_id}/comments/#{comment_id} -> #{destroy.code}, " \
|
||||||
|
'both live viewers pushed {event: comment_count, count: 0} for the removed comment'
|
||||||
|
|
||||||
|
bystander.write(Frame.encode(Frame::PING, '', mask: true))
|
||||||
|
reply = Frame.decode(bystander)
|
||||||
|
raise "expected the unrelated channel's socket to only ever see our own PONG, got opcode #{reply.opcode}" unless reply.opcode == Frame::PONG
|
||||||
|
puts "bystander subscribed to post_id=999999 received ZERO comment_count events for post #{post_id} " \
|
||||||
|
'(streams are isolated per post, confirmed by the only frame available being our own PONG)'
|
||||||
|
|
||||||
|
viewer_a.write(Frame.encode(Frame::CLOSE, '', mask: true))
|
||||||
|
close_reply = Frame.decode(viewer_a)
|
||||||
|
raise "expected a CLOSE frame echoed back, got opcode #{close_reply.opcode}" unless close_reply.opcode == Frame::CLOSE
|
||||||
|
puts 'viewer_a sent a close frame -> server echoed CLOSE back (RFC 6455 5.5.1), on_close unsubscribed it from the Hub'
|
||||||
|
|
||||||
|
# examples/blog/app.rb registers '/cable' with max_message_size: 2048,
|
||||||
|
# far below the library's 16 MiB default - proven here the same way
|
||||||
|
# test_websocket_integration.rb proves the option in the library itself,
|
||||||
|
# but this time through the actual application's real configuration
|
||||||
|
# instead of a purpose-built test endpoint.
|
||||||
|
oversized_viewer = open_cable('127.0.0.1', port, post_id)
|
||||||
|
read_event(oversized_viewer) # subscribe ack, discarded
|
||||||
|
oversized_viewer.write(Frame.encode(Frame::TEXT, 'x' * 3000, mask: true))
|
||||||
|
close_frame = Frame.decode(oversized_viewer)
|
||||||
|
raise "expected a CLOSE frame, got opcode #{close_frame.opcode}" unless close_frame.opcode == Frame::CLOSE
|
||||||
|
close_code = close_frame.payload.byteslice(0, 2).unpack1('n')
|
||||||
|
raise "expected close code 1009 (message too big), got #{close_code}" unless close_code == 1009
|
||||||
|
puts "sent a 3000 byte frame on /cable (max_message_size: 2048 in examples/blog/app.rb) " \
|
||||||
|
"-> server closed with code #{close_code} (message too big), without disturbing viewer_b or bystander"
|
||||||
|
oversized_viewer.close
|
||||||
|
|
||||||
|
[viewer_a, viewer_b, bystander].each { |socket| socket.close unless socket.closed? }
|
||||||
|
|
||||||
|
still_alive = http.get('/')
|
||||||
|
raise "expected the plain HTTP server to still be healthy after the WebSocket traffic, got #{still_alive.code}" unless still_alive.code == '200'
|
||||||
|
puts "GET / -> #{still_alive.code} (ordinary HTTP routes on the same server are unaffected by the cable traffic above)"
|
||||||
|
end
|
||||||
|
|
||||||
|
server.stop
|
||||||
|
puts '-' * 40
|
||||||
|
puts 'blog_websocket_demo done: PostActivityChannel subscribed two real WebSocket clients to one post ' \
|
||||||
|
'and kept a third, unrelated one silent; three genuine HTTP moderation actions (approve/reject/destroy) ' \
|
||||||
|
'each pushed a live {event: comment_count} frame to every subscriber over the real socket, and the close ' \
|
||||||
|
'handshake correctly unsubscribed a departing viewer from the shared Hub.'
|
||||||
Reference in new issue
Block a user