juggernaut_rails 0.5.9
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.
- data/README.rdoc +95 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/bin/juggernaut +4 -0
- data/lib/juggernaut.rb +158 -0
- data/lib/juggernaut/client.rb +263 -0
- data/lib/juggernaut/message.rb +19 -0
- data/lib/juggernaut/miscel.rb +27 -0
- data/lib/juggernaut/rails.rb +25 -0
- data/lib/juggernaut/rails/convenience_methods.rb +137 -0
- data/lib/juggernaut/rails/helpers.rb +27 -0
- data/lib/juggernaut/rails/render_extension.rb +77 -0
- data/lib/juggernaut/rails/tasks.rb +66 -0
- data/lib/juggernaut/runner.rb +221 -0
- data/lib/juggernaut/server.rb +368 -0
- data/lib/juggernaut/utils.rb +11 -0
- data/lib/juggernaut_rails.rb +4 -0
- data/media/expressinstall.swf +0 -0
- data/media/jquery.js +3549 -0
- data/media/jquerynaut.js +57 -0
- data/media/json.js +97 -0
- data/media/juggernaut.as +79 -0
- data/media/juggernaut.js +204 -0
- data/media/juggernaut.swf +0 -0
- data/media/juggernaut.yml +121 -0
- data/media/juggernaut_hosts.yml +18 -0
- data/media/log/juggernaut.log +0 -0
- data/media/swfobject.js +5 -0
- data/rails/init.rb +21 -0
- data/test/test_client.rb +176 -0
- data/test/test_juggernaut.rb +34 -0
- data/test/test_message.rb +26 -0
- data/test/test_runner.rb +26 -0
- data/test/test_server.rb +573 -0
- data/test/test_utils.rb +26 -0
- metadata +114 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# You should list any juggernaut hosts here.
|
2
|
+
# You need only specify the secret key if you're using that type of authentication (see juggernaut.yml)
|
3
|
+
#
|
4
|
+
# Name: Mapping:
|
5
|
+
# :port internal push server's port
|
6
|
+
# :host internal push server's host/ip
|
7
|
+
# :public_host public push server's host/ip (accessible from external clients)
|
8
|
+
# :public_port public push server's port
|
9
|
+
# :secret_key (optional) shared secret (should map to the key specified in the push server's config)
|
10
|
+
# :environment (optional) limit host to a particular RAILS_ENV
|
11
|
+
|
12
|
+
:hosts:
|
13
|
+
- :port: 5001
|
14
|
+
:host: 127.0.0.1
|
15
|
+
:public_host: 127.0.0.1
|
16
|
+
:public_port: 5001
|
17
|
+
# :secret_key: your_secret_key
|
18
|
+
# :environment: :development
|
File without changes
|
data/media/swfobject.js
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
/* SWFObject v2.0 <http://code.google.com/p/swfobject/>
|
2
|
+
Copyright (c) 2007 Geoff Stearns, Michael Williams, and Bobby van der Sluis
|
3
|
+
This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
|
4
|
+
*/
|
5
|
+
var swfobject=function(){var Z="undefined",P="object",B="Shockwave Flash",h="ShockwaveFlash.ShockwaveFlash",W="application/x-shockwave-flash",K="SWFObjectExprInst",G=window,g=document,N=navigator,f=[],H=[],Q=null,L=null,T=null,S=false,C=false;var a=function(){var l=typeof g.getElementById!=Z&&typeof g.getElementsByTagName!=Z&&typeof g.createElement!=Z&&typeof g.appendChild!=Z&&typeof g.replaceChild!=Z&&typeof g.removeChild!=Z&&typeof g.cloneNode!=Z,t=[0,0,0],n=null;if(typeof N.plugins!=Z&&typeof N.plugins[B]==P){n=N.plugins[B].description;if(n){n=n.replace(/^.*\s+(\S+\s+\S+$)/,"$1");t[0]=parseInt(n.replace(/^(.*)\..*$/,"$1"),10);t[1]=parseInt(n.replace(/^.*\.(.*)\s.*$/,"$1"),10);t[2]=/r/.test(n)?parseInt(n.replace(/^.*r(.*)$/,"$1"),10):0}}else{if(typeof G.ActiveXObject!=Z){var o=null,s=false;try{o=new ActiveXObject(h+".7")}catch(k){try{o=new ActiveXObject(h+".6");t=[6,0,21];o.AllowScriptAccess="always"}catch(k){if(t[0]==6){s=true}}if(!s){try{o=new ActiveXObject(h)}catch(k){}}}if(!s&&o){try{n=o.GetVariable("$version");if(n){n=n.split(" ")[1].split(",");t=[parseInt(n[0],10),parseInt(n[1],10),parseInt(n[2],10)]}}catch(k){}}}}var v=N.userAgent.toLowerCase(),j=N.platform.toLowerCase(),r=/webkit/.test(v)?parseFloat(v.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,i=false,q=j?/win/.test(j):/win/.test(v),m=j?/mac/.test(j):/mac/.test(v);/*@cc_on i=true;@if(@_win32)q=true;@elif(@_mac)m=true;@end@*/return{w3cdom:l,pv:t,webkit:r,ie:i,win:q,mac:m}}();var e=function(){if(!a.w3cdom){return }J(I);if(a.ie&&a.win){try{g.write("<script id=__ie_ondomload defer=true src=//:><\/script>");var i=c("__ie_ondomload");if(i){i.onreadystatechange=function(){if(this.readyState=="complete"){this.parentNode.removeChild(this);V()}}}}catch(j){}}if(a.webkit&&typeof g.readyState!=Z){Q=setInterval(function(){if(/loaded|complete/.test(g.readyState)){V()}},10)}if(typeof g.addEventListener!=Z){g.addEventListener("DOMContentLoaded",V,null)}M(V)}();function V(){if(S){return }if(a.ie&&a.win){var m=Y("span");try{var l=g.getElementsByTagName("body")[0].appendChild(m);l.parentNode.removeChild(l)}catch(n){return }}S=true;if(Q){clearInterval(Q);Q=null}var j=f.length;for(var k=0;k<j;k++){f[k]()}}function J(i){if(S){i()}else{f[f.length]=i}}function M(j){if(typeof G.addEventListener!=Z){G.addEventListener("load",j,false)}else{if(typeof g.addEventListener!=Z){g.addEventListener("load",j,false)}else{if(typeof G.attachEvent!=Z){G.attachEvent("onload",j)}else{if(typeof G.onload=="function"){var i=G.onload;G.onload=function(){i();j()}}else{G.onload=j}}}}}function I(){var l=H.length;for(var j=0;j<l;j++){var m=H[j].id;if(a.pv[0]>0){var k=c(m);if(k){H[j].width=k.getAttribute("width")?k.getAttribute("width"):"0";H[j].height=k.getAttribute("height")?k.getAttribute("height"):"0";if(O(H[j].swfVersion)){if(a.webkit&&a.webkit<312){U(k)}X(m,true)}else{if(H[j].expressInstall&&!C&&O("6.0.65")&&(a.win||a.mac)){D(H[j])}else{d(k)}}}}else{X(m,true)}}}function U(m){var k=m.getElementsByTagName(P)[0];if(k){var p=Y("embed"),r=k.attributes;if(r){var o=r.length;for(var n=0;n<o;n++){if(r[n].nodeName.toLowerCase()=="data"){p.setAttribute("src",r[n].nodeValue)}else{p.setAttribute(r[n].nodeName,r[n].nodeValue)}}}var q=k.childNodes;if(q){var s=q.length;for(var l=0;l<s;l++){if(q[l].nodeType==1&&q[l].nodeName.toLowerCase()=="param"){p.setAttribute(q[l].getAttribute("name"),q[l].getAttribute("value"))}}}m.parentNode.replaceChild(p,m)}}function F(i){if(a.ie&&a.win&&O("8.0.0")){G.attachEvent("onunload",function(){var k=c(i);if(k){for(var j in k){if(typeof k[j]=="function"){k[j]=function(){}}}k.parentNode.removeChild(k)}})}}function D(j){C=true;var o=c(j.id);if(o){if(j.altContentId){var l=c(j.altContentId);if(l){L=l;T=j.altContentId}}else{L=b(o)}if(!(/%$/.test(j.width))&&parseInt(j.width,10)<310){j.width="310"}if(!(/%$/.test(j.height))&&parseInt(j.height,10)<137){j.height="137"}g.title=g.title.slice(0,47)+" - Flash Player Installation";var n=a.ie&&a.win?"ActiveX":"PlugIn",k=g.title,m="MMredirectURL="+G.location+"&MMplayerType="+n+"&MMdoctitle="+k,p=j.id;if(a.ie&&a.win&&o.readyState!=4){var i=Y("div");p+="SWFObjectNew";i.setAttribute("id",p);o.parentNode.insertBefore(i,o);o.style.display="none";G.attachEvent("onload",function(){o.parentNode.removeChild(o)})}R({data:j.expressInstall,id:K,width:j.width,height:j.height},{flashvars:m},p)}}function d(j){if(a.ie&&a.win&&j.readyState!=4){var i=Y("div");j.parentNode.insertBefore(i,j);i.parentNode.replaceChild(b(j),i);j.style.display="none";G.attachEvent("onload",function(){j.parentNode.removeChild(j)})}else{j.parentNode.replaceChild(b(j),j)}}function b(n){var m=Y("div");if(a.win&&a.ie){m.innerHTML=n.innerHTML}else{var k=n.getElementsByTagName(P)[0];if(k){var o=k.childNodes;if(o){var j=o.length;for(var l=0;l<j;l++){if(!(o[l].nodeType==1&&o[l].nodeName.toLowerCase()=="param")&&!(o[l].nodeType==8)){m.appendChild(o[l].cloneNode(true))}}}}}return m}function R(AE,AC,q){var p,t=c(q);if(typeof AE.id==Z){AE.id=q}if(a.ie&&a.win){var AD="";for(var z in AE){if(AE[z]!=Object.prototype[z]){if(z=="data"){AC.movie=AE[z]}else{if(z.toLowerCase()=="styleclass"){AD+=' class="'+AE[z]+'"'}else{if(z!="classid"){AD+=" "+z+'="'+AE[z]+'"'}}}}}var AB="";for(var y in AC){if(AC[y]!=Object.prototype[y]){AB+='<param name="'+y+'" value="'+AC[y]+'" />'}}t.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AD+">"+AB+"</object>";F(AE.id);p=c(AE.id)}else{if(a.webkit&&a.webkit<312){var AA=Y("embed");AA.setAttribute("type",W);for(var x in AE){if(AE[x]!=Object.prototype[x]){if(x=="data"){AA.setAttribute("src",AE[x])}else{if(x.toLowerCase()=="styleclass"){AA.setAttribute("class",AE[x])}else{if(x!="classid"){AA.setAttribute(x,AE[x])}}}}}for(var w in AC){if(AC[w]!=Object.prototype[w]){if(w!="movie"){AA.setAttribute(w,AC[w])}}}t.parentNode.replaceChild(AA,t);p=AA}else{var s=Y(P);s.setAttribute("type",W);for(var v in AE){if(AE[v]!=Object.prototype[v]){if(v.toLowerCase()=="styleclass"){s.setAttribute("class",AE[v])}else{if(v!="classid"){s.setAttribute(v,AE[v])}}}}for(var u in AC){if(AC[u]!=Object.prototype[u]&&u!="movie"){E(s,u,AC[u])}}t.parentNode.replaceChild(s,t);p=s}}return p}function E(k,i,j){var l=Y("param");l.setAttribute("name",i);l.setAttribute("value",j);k.appendChild(l)}function c(i){return g.getElementById(i)}function Y(i){return g.createElement(i)}function O(k){var j=a.pv,i=k.split(".");i[0]=parseInt(i[0],10);i[1]=parseInt(i[1],10);i[2]=parseInt(i[2],10);return(j[0]>i[0]||(j[0]==i[0]&&j[1]>i[1])||(j[0]==i[0]&&j[1]==i[1]&&j[2]>=i[2]))?true:false}function A(m,j){if(a.ie&&a.mac){return }var l=g.getElementsByTagName("head")[0],k=Y("style");k.setAttribute("type","text/css");k.setAttribute("media","screen");if(!(a.ie&&a.win)&&typeof g.createTextNode!=Z){k.appendChild(g.createTextNode(m+" {"+j+"}"))}l.appendChild(k);if(a.ie&&a.win&&typeof g.styleSheets!=Z&&g.styleSheets.length>0){var i=g.styleSheets[g.styleSheets.length-1];if(typeof i.addRule==P){i.addRule(m,j)}}}function X(k,i){var j=i?"visible":"hidden";if(S){c(k).style.visibility=j}else{A("#"+k,"visibility:"+j)}}return{registerObject:function(l,i,k){if(!a.w3cdom||!l||!i){return }var j={};j.id=l;j.swfVersion=i;j.expressInstall=k?k:false;H[H.length]=j;X(l,false)},getObjectById:function(l){var i=null;if(a.w3cdom&&S){var j=c(l);if(j){var k=j.getElementsByTagName(P)[0];if(!k||(k&&typeof j.SetVariable!=Z)){i=j}else{if(typeof k.SetVariable!=Z){i=k}}}}return i},embedSWF:function(n,u,r,t,j,m,k,p,s){if(!a.w3cdom||!n||!u||!r||!t||!j){return }r+="";t+="";if(O(j)){X(u,false);var q=(typeof s==P)?s:{};q.data=n;q.width=r;q.height=t;var o=(typeof p==P)?p:{};if(typeof k==P){for(var l in k){if(k[l]!=Object.prototype[l]){if(typeof o.flashvars!=Z){o.flashvars+="&"+l+"="+k[l]}else{o.flashvars=l+"="+k[l]}}}}J(function(){R(q,o,u);if(q.id==u){X(u,true)}})}else{if(m&&!C&&O("6.0.65")&&(a.win||a.mac)){X(u,false);J(function(){var i={};i.id=i.altContentId=u;i.width=r;i.height=t;i.expressInstall=m;D(i)})}}},getFlashPlayerVersion:function(){return{major:a.pv[0],minor:a.pv[1],release:a.pv[2]}},hasFlashPlayerVersion:O,createSWF:function(k,j,i){if(a.w3cdom&&S){return R(k,j,i)}else{return undefined}},createCSS:function(j,i){if(a.w3cdom){A(j,i)}},addDomLoadEvent:J,addLoadEvent:M,getQueryParamValue:function(m){var l=g.location.search||g.location.hash;if(m==null){return l}if(l){var k=l.substring(1).split("&");for(var j=0;j<k.length;j++){if(k[j].substring(0,k[j].indexOf("="))==m){return k[j].substring((k[j].indexOf("=")+1))}}}return""},expressInstallCallback:function(){if(C&&L){var i=c(K);if(i){i.parentNode.replaceChild(L,i);if(T){X(T,true);if(a.ie&&a.win){L.style.display="block"}}L=null;T=null;C=false}}}}}();
|
data/rails/init.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/juggernaut')
|
2
|
+
require File.join(File.dirname(__FILE__), '../lib/juggernaut/rails')
|
3
|
+
require File.join(File.dirname(__FILE__), '../lib/juggernaut/rails/helpers')
|
4
|
+
|
5
|
+
Juggernaut.send(:include, Juggernaut::Rails::ConvenienceMethods)
|
6
|
+
ActionView::Base.send(:include, Juggernaut::Rails::Helpers)
|
7
|
+
|
8
|
+
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :juggernaut => ['juggernaut/swfobject', 'juggernaut/juggernaut']
|
9
|
+
ActionView::Helpers::AssetTagHelper.register_javascript_expansion :juggernaut_jquery => ['juggernaut/json', 'juggernaut/juggernaut', 'juggernaut/jquerynaut', 'juggernaut/swfobject']
|
10
|
+
|
11
|
+
ActionController::Base.class_eval do
|
12
|
+
alias_method :render_without_juggernaut, :render
|
13
|
+
include Juggernaut::Rails::RenderExtension
|
14
|
+
alias_method :render, :render_with_juggernaut
|
15
|
+
end
|
16
|
+
|
17
|
+
ActionView::Base.class_eval do
|
18
|
+
alias_method :render_without_juggernaut, :render
|
19
|
+
include Juggernaut::Rails::RenderExtension
|
20
|
+
alias_method :render, :render_with_juggernaut
|
21
|
+
end
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
|
2
|
+
$:.unshift "../lib"
|
3
|
+
require "juggernaut"
|
4
|
+
require "test/unit"
|
5
|
+
require "shoulda"
|
6
|
+
require "mocha"
|
7
|
+
|
8
|
+
class TestClient < Test::Unit::TestCase
|
9
|
+
|
10
|
+
CONFIG = File.join(File.dirname(__FILE__), "files", "juggernaut.yml")
|
11
|
+
|
12
|
+
class DummySubscriber; end
|
13
|
+
|
14
|
+
EXAMPLE_URL = "http://localhost:5000/callbacks/example"
|
15
|
+
SECURE_URL = "https://localhost:5000/callbacks/example"
|
16
|
+
|
17
|
+
context "Client" do
|
18
|
+
|
19
|
+
setup do
|
20
|
+
Juggernaut.options = {
|
21
|
+
:logout_connection_url => "http://localhost:5000/callbacks/logout_connection",
|
22
|
+
:logout_url => "http://localhost:5000/callbacks/logout",
|
23
|
+
:subscription_url => "http://localhost:5000/callbacks/subscription"
|
24
|
+
}
|
25
|
+
@s1 = DummySubscriber.new
|
26
|
+
@request = {
|
27
|
+
:client_id => "jonny",
|
28
|
+
:session_id => rand(1_000_000).to_s(16)
|
29
|
+
}
|
30
|
+
@client = Juggernaut::Client.find_or_create(@s1, @request)
|
31
|
+
end
|
32
|
+
|
33
|
+
teardown do
|
34
|
+
Juggernaut::Client.reset!
|
35
|
+
end
|
36
|
+
|
37
|
+
should "have correct JSON representation" do
|
38
|
+
assert_nothing_raised do
|
39
|
+
json = {
|
40
|
+
"client_id" => "jonny",
|
41
|
+
"num_connections" => 1,
|
42
|
+
"session_id" => @request[:session_id]
|
43
|
+
}
|
44
|
+
assert_equal json, JSON.parse(@client.to_json)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
should "return the client based on subscriber's signature" do
|
49
|
+
@s1.stubs(:signature).returns("012345")
|
50
|
+
assert_equal @client, Juggernaut::Client.find_by_signature("012345")
|
51
|
+
end
|
52
|
+
|
53
|
+
should "return the client based on client ID and channel list" do
|
54
|
+
@client.stubs(:has_channels?).with(%w(a couple of channels)).returns(true)
|
55
|
+
assert_equal @client, Juggernaut::Client.find_by_id_and_channels("jonny", %w(a couple of channels))
|
56
|
+
assert_nil Juggernaut::Client.find_by_id_and_channels("peter", %w(a couple of channels))
|
57
|
+
@client.stubs(:has_channels?).with(%w(something else)).returns(false)
|
58
|
+
assert_nil Juggernaut::Client.find_by_id_and_channels("jonny", %w(something else))
|
59
|
+
end
|
60
|
+
|
61
|
+
should "automatically be registered, and can unregister" do
|
62
|
+
assert @client.send(:registered?)
|
63
|
+
assert_equal @client, @client.send(:unregister)
|
64
|
+
assert_equal false, @client.send(:registered?)
|
65
|
+
end
|
66
|
+
|
67
|
+
should "be alive if at least one subscriber is alive" do
|
68
|
+
@s1.stubs(:alive?).returns(true)
|
69
|
+
@s2 = DummySubscriber.new
|
70
|
+
@client.add_new_connection(@s2)
|
71
|
+
@s2.stubs(:alive?).returns(false)
|
72
|
+
assert @client.alive?
|
73
|
+
end
|
74
|
+
|
75
|
+
should "not be alive if no subscriber is alive" do
|
76
|
+
@s1.stubs(:alive?).returns(false)
|
77
|
+
@s2 = DummySubscriber.new
|
78
|
+
@client.add_new_connection(@s2)
|
79
|
+
@s2.stubs(:alive?).returns(false)
|
80
|
+
assert_equal false, @client.alive?
|
81
|
+
end
|
82
|
+
|
83
|
+
should "not give up if within the timeout period" do
|
84
|
+
Juggernaut.options[:timeout] = 10
|
85
|
+
@s1.stubs(:alive?).returns(false)
|
86
|
+
@client.send(:reset_logout_timeout!)
|
87
|
+
assert_equal false, @client.give_up?
|
88
|
+
end
|
89
|
+
|
90
|
+
should "not give up if at least one subscriber is alive" do
|
91
|
+
Juggernaut.options[:timeout] = 0
|
92
|
+
@s1.stubs(:alive?).returns(true)
|
93
|
+
@client.send(:reset_logout_timeout!)
|
94
|
+
assert_equal false, @client.give_up?
|
95
|
+
end
|
96
|
+
|
97
|
+
should "send logouts after timeout" do
|
98
|
+
Juggernaut.options[:timeout] = 0
|
99
|
+
@s1.stubs(:alive?).returns(false)
|
100
|
+
@client.send(:reset_logout_timeout!)
|
101
|
+
@client.expects(:logout_request).once
|
102
|
+
Juggernaut::Client.send_logouts_after_timeout
|
103
|
+
end
|
104
|
+
|
105
|
+
%w(subscription logout_connection).each do |type|
|
106
|
+
|
107
|
+
context "#{type} request" do
|
108
|
+
|
109
|
+
should "post to the correct URL" do
|
110
|
+
@client.expects(:post_request).with(Juggernaut.options[:"#{type}_url"], %w(master), :timeout => 5).returns(true)
|
111
|
+
assert_equal true, @client.send("#{type}_request", %w(master))
|
112
|
+
end
|
113
|
+
|
114
|
+
should "not raise exceptions if posting raises an exception" do
|
115
|
+
@client.expects(:post_request).with(Juggernaut.options[:"#{type}_url"], %w(master), :timeout => 5).returns(false)
|
116
|
+
assert_nothing_raised {
|
117
|
+
assert_equal false, @client.send("#{type}_request", %w(master))
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
context "post to URL" do
|
126
|
+
|
127
|
+
should "return true when successful" do
|
128
|
+
Net::HTTP.any_instance.
|
129
|
+
expects(:post).
|
130
|
+
with("/callbacks/example", "client_id=jonny&session_id=#{@request[:session_id]}&channels[]=master&channels[]=slave", {"User-Agent" => "Ruby/#{RUBY_VERSION}"}).
|
131
|
+
returns([Net::HTTPOK.new('1.1', '200', 'OK'), ''])
|
132
|
+
assert_equal true, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
|
133
|
+
end
|
134
|
+
|
135
|
+
should "return false on an internal server error" do
|
136
|
+
Net::HTTP.any_instance.expects(:post).returns([Net::HTTPInternalServerError.new('1.1', '500', 'Internal Server Error'), ''])
|
137
|
+
assert_equal false, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
|
138
|
+
end
|
139
|
+
|
140
|
+
should "return false when a runtime error is caught" do
|
141
|
+
Net::HTTP.any_instance.expects(:post).raises(RuntimeError)
|
142
|
+
assert_equal false, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
|
143
|
+
end
|
144
|
+
|
145
|
+
should "return false when callback times out" do
|
146
|
+
Net::HTTP.any_instance.expects(:post).raises(Timeout::Error)
|
147
|
+
assert_equal false, @client.send(:post_request, EXAMPLE_URL, %w(master slave))
|
148
|
+
end
|
149
|
+
|
150
|
+
context "using a secure URL" do
|
151
|
+
|
152
|
+
should "return true when successful" do
|
153
|
+
Net::HTTP.any_instance.expects(:post).returns([Net::HTTPOK.new('1.1', '200', 'OK'), ''])
|
154
|
+
Net::HTTP.any_instance.expects(:use_ssl=).with(true).returns(true)
|
155
|
+
assert_equal true, @client.send(:post_request, SECURE_URL, %w(master slave))
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
context "channel list" do
|
163
|
+
|
164
|
+
should "be the unique list of all channels in the subscribers" do
|
165
|
+
@s1.stubs(:channels).returns(%w(master slave1))
|
166
|
+
@s2 = DummySubscriber.new
|
167
|
+
@s2.stubs(:channels).returns(%w(master slave2))
|
168
|
+
@client.add_new_connection(@s2)
|
169
|
+
assert_same_elements %w(master slave1 slave2), @client.channels
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
$:.unshift "../lib"
|
3
|
+
require "juggernaut"
|
4
|
+
require "test/unit"
|
5
|
+
require "shoulda"
|
6
|
+
require "mocha"
|
7
|
+
|
8
|
+
class TestJuggernaut < Test::Unit::TestCase
|
9
|
+
|
10
|
+
context "Juggernaut" do
|
11
|
+
|
12
|
+
setup do
|
13
|
+
Juggernaut.options = { }
|
14
|
+
end
|
15
|
+
|
16
|
+
should "set options correctly" do
|
17
|
+
options = {
|
18
|
+
:host => "0.0.0.0",
|
19
|
+
:port => 5001,
|
20
|
+
:debug => false
|
21
|
+
}
|
22
|
+
Juggernaut.options = options
|
23
|
+
assert_equal options, Juggernaut.options
|
24
|
+
end
|
25
|
+
|
26
|
+
should "have a debug logger by default" do
|
27
|
+
log = Juggernaut.logger
|
28
|
+
assert_not_nil log
|
29
|
+
assert_equal Logger::DEBUG, log.level
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
$:.unshift "../lib"
|
3
|
+
require "juggernaut"
|
4
|
+
require "test/unit"
|
5
|
+
require "shoulda"
|
6
|
+
require "mocha"
|
7
|
+
|
8
|
+
class TestMessage < Test::Unit::TestCase
|
9
|
+
|
10
|
+
context "Message" do
|
11
|
+
|
12
|
+
setup do
|
13
|
+
@msg = Juggernaut::Message.new(1, "A pre-determined message body", "a81fef13919")
|
14
|
+
assert_not_nil @msg
|
15
|
+
end
|
16
|
+
|
17
|
+
should "generate valid JSON" do
|
18
|
+
obj = {"signature" => "a81fef13919", "body" => "A pre-determined message body", "id" => "1"}
|
19
|
+
assert_nothing_raised do
|
20
|
+
assert_equal obj, JSON.parse(@msg.to_s)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/test/test_runner.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
$:.unshift "../lib"
|
3
|
+
require "juggernaut"
|
4
|
+
require "test/unit"
|
5
|
+
require "shoulda"
|
6
|
+
require "mocha"
|
7
|
+
require "tempfile"
|
8
|
+
|
9
|
+
class TestRunner < Test::Unit::TestCase
|
10
|
+
|
11
|
+
CONFIG = File.join(File.dirname(__FILE__), "files", "juggernaut.yml")
|
12
|
+
|
13
|
+
context "Runner" do
|
14
|
+
|
15
|
+
should "always be true" do
|
16
|
+
assert true
|
17
|
+
end
|
18
|
+
|
19
|
+
# should "run" do
|
20
|
+
# EM.run { EM.stop }
|
21
|
+
# Juggernaut::Runner.run(["-c", CONFIG])
|
22
|
+
# end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/test/test_server.rb
ADDED
@@ -0,0 +1,573 @@
|
|
1
|
+
|
2
|
+
$:.unshift "../lib"
|
3
|
+
require "juggernaut"
|
4
|
+
require "test/unit"
|
5
|
+
require "shoulda"
|
6
|
+
require "mocha"
|
7
|
+
|
8
|
+
class TestServer < Test::Unit::TestCase
|
9
|
+
|
10
|
+
CONFIG = File.join(File.dirname(__FILE__), "files", "juggernaut.yml")
|
11
|
+
|
12
|
+
DEFAULT_OPTIONS = {
|
13
|
+
:host => "0.0.0.0",
|
14
|
+
:port => 5001,
|
15
|
+
:debug => false,
|
16
|
+
:cleanup_timer => 2,
|
17
|
+
:timeout => 10,
|
18
|
+
:store_messages => false
|
19
|
+
}
|
20
|
+
|
21
|
+
OPTIONS = DEFAULT_OPTIONS.merge(YAML::load(ERB.new(IO.read(CONFIG)).result))
|
22
|
+
|
23
|
+
class DirectClient
|
24
|
+
attr_reader :channels
|
25
|
+
def broadcast_to_channels(channels, body)
|
26
|
+
self.transmit :command => :broadcast, :type => :to_channels, :channels => channels, :body => body
|
27
|
+
self
|
28
|
+
end
|
29
|
+
def broadcast_to_clients(clients, body)
|
30
|
+
self.transmit :command => :broadcast, :type => :to_clients, :client_ids => clients, :body => body
|
31
|
+
end
|
32
|
+
def close
|
33
|
+
@socket.close if @socket
|
34
|
+
end
|
35
|
+
def initialize(options)
|
36
|
+
@options = options
|
37
|
+
@socket = nil
|
38
|
+
@client_id = options[:client_id]
|
39
|
+
@session_id = options[:session_id] || rand(1_000_000).to_s(16)
|
40
|
+
@channels = [ ]
|
41
|
+
@socket = TCPSocket.new(@options[:host], @options[:port])
|
42
|
+
end
|
43
|
+
def inspect
|
44
|
+
{:channels => @channels, :client_id => @client_id, :session_id => @session_id}.inspect
|
45
|
+
end
|
46
|
+
def request_crossdomain_file
|
47
|
+
@socket.print "<policy-file-request/>\0"
|
48
|
+
self
|
49
|
+
end
|
50
|
+
def query_remove_channels_from_all_clients(channels)
|
51
|
+
self.transmit :command => :query, :type => :remove_channels_from_all_clients, :channels => channels
|
52
|
+
self
|
53
|
+
end
|
54
|
+
def query_remove_channels_from_client(channels, clients)
|
55
|
+
self.transmit :command => :query, :type => :remove_channels_from_client, :client_ids => clients, :channels => channels
|
56
|
+
self
|
57
|
+
end
|
58
|
+
def query_show_channels_for_client(client_id)
|
59
|
+
self.transmit :command => :query, :type => :show_channels_for_client, :client_id => client_id
|
60
|
+
self
|
61
|
+
end
|
62
|
+
def query_show_client(client_id)
|
63
|
+
self.transmit :command => :query, :type => :show_client, :client_id => client_id
|
64
|
+
self
|
65
|
+
end
|
66
|
+
def query_show_clients(client_ids = [])
|
67
|
+
self.transmit :command => :query, :type => :show_clients, :client_ids => client_ids
|
68
|
+
self
|
69
|
+
end
|
70
|
+
def query_show_clients_for_channels(channels)
|
71
|
+
self.transmit :command => :query, :type => :show_clients_for_channels, :channels => channels
|
72
|
+
self
|
73
|
+
end
|
74
|
+
def receive(as_json = true)
|
75
|
+
return nil unless @socket
|
76
|
+
begin
|
77
|
+
# response = @socket.read.to_s
|
78
|
+
# response = @socket.readline("\0").to_s
|
79
|
+
response = ""
|
80
|
+
begin
|
81
|
+
response << @socket.read_nonblock(1024)
|
82
|
+
rescue Errno::EAGAIN
|
83
|
+
end
|
84
|
+
response.chomp!("\0")
|
85
|
+
Juggernaut.logger.info "DirectClient read: " + response.inspect
|
86
|
+
as_json ? JSON.parse(response) : response
|
87
|
+
rescue => e
|
88
|
+
Juggernaut.logger.error "DirectClient #{e.class}: #{e.message}"
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
def subscribe(channels)
|
93
|
+
channels.each do |channel|
|
94
|
+
@channels << channel.to_s unless @channels.include?(channel.to_s)
|
95
|
+
end
|
96
|
+
self.transmit :command => :subscribe, :channels => channels
|
97
|
+
self
|
98
|
+
end
|
99
|
+
def send_raw(raw, wait_response = false)
|
100
|
+
@socket.print(raw + "\0")
|
101
|
+
@socket.flush
|
102
|
+
if wait_response
|
103
|
+
self.receive
|
104
|
+
else
|
105
|
+
nil
|
106
|
+
end
|
107
|
+
end
|
108
|
+
def transmit(hash, wait_response = false)
|
109
|
+
hash[:client_id] ||= @client_id
|
110
|
+
hash[:session_id] ||= @session_id
|
111
|
+
self.send_raw(hash.to_json, wait_response)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Assert that the DirectClient has an awaiting message with +body+.
|
116
|
+
def assert_body(body, subscriber)
|
117
|
+
assert_response subscriber do |result|
|
118
|
+
assert_respond_to result, :[]
|
119
|
+
assert_equal body, result["body"]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Assert that the DirectClient has no awaiting message.
|
124
|
+
def assert_no_body(subscriber)
|
125
|
+
assert_response subscriber do |result|
|
126
|
+
assert_equal false, result
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def assert_no_response(subscriber)
|
131
|
+
assert_not_nil subscriber
|
132
|
+
assert_raise(EOFError) { subscriber.receive }
|
133
|
+
ensure
|
134
|
+
subscriber.close
|
135
|
+
end
|
136
|
+
|
137
|
+
def assert_raw_response(subscriber, response = nil)
|
138
|
+
assert_not_nil subscriber
|
139
|
+
result = nil
|
140
|
+
assert_nothing_raised { result = subscriber.receive(false) }
|
141
|
+
assert_not_nil result
|
142
|
+
if block_given?
|
143
|
+
yield result
|
144
|
+
else
|
145
|
+
assert_equal response, result
|
146
|
+
end
|
147
|
+
ensure
|
148
|
+
subscriber.close
|
149
|
+
end
|
150
|
+
|
151
|
+
def assert_response(subscriber, response = nil)
|
152
|
+
assert_not_nil subscriber
|
153
|
+
result = nil
|
154
|
+
assert_nothing_raised { result = subscriber.receive }
|
155
|
+
assert_not_nil result
|
156
|
+
if block_given?
|
157
|
+
yield result
|
158
|
+
else
|
159
|
+
assert_equal response, result
|
160
|
+
end
|
161
|
+
ensure
|
162
|
+
subscriber.close
|
163
|
+
end
|
164
|
+
|
165
|
+
def assert_server_disconnected(subscriber)
|
166
|
+
assert_not_nil subscriber
|
167
|
+
assert_raise(Errno::ECONNRESET, EOFError) { subscriber.receive }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Convenience method to create a new DirectClient instance with overridable options.
|
171
|
+
# If a block is passed, control is yielded, passing the new client in. This method
|
172
|
+
# returns the value returned from that block, or the new client if no block was given.
|
173
|
+
def new_client(options = { })
|
174
|
+
c = DirectClient.new(OPTIONS.merge(options))
|
175
|
+
if block_given?
|
176
|
+
yield(c)
|
177
|
+
else
|
178
|
+
c
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Shortcut to run tests that require setting up, starting, then shutting down EventMachine.
|
183
|
+
# So ugly, but EventMachine doesn't have test examples on code that require back-and-forth
|
184
|
+
# communication over a long-running connection.
|
185
|
+
def with_server(options = { }, &block)
|
186
|
+
# We should not have any clients before we start
|
187
|
+
Juggernaut::Client.reset!
|
188
|
+
|
189
|
+
# Save the current options. This is an obvious hack.
|
190
|
+
old_options, Juggernaut.options = Juggernaut.options, OPTIONS.merge(options)
|
191
|
+
Juggernaut.logger.level = Logger::DEBUG
|
192
|
+
|
193
|
+
# Initialize an array to keep track of connections made to the server in this instance.
|
194
|
+
@connections = [ ]
|
195
|
+
|
196
|
+
EM.run do
|
197
|
+
# Start the server, and save each connection made so we can refer to it later.
|
198
|
+
EM.start_server(Juggernaut.options[:host], Juggernaut.options[:port], Juggernaut::Server) { |c| @connections << c }
|
199
|
+
|
200
|
+
# Guard against never-ending tests by shutting off at 2 seconds.
|
201
|
+
EM.add_timer(2) do
|
202
|
+
Juggernaut::Client.send_logouts_to_all_clients
|
203
|
+
EM.stop
|
204
|
+
end
|
205
|
+
|
206
|
+
# Deferred: evaluate the block and then run the shutdown proc. By using instance_eval,
|
207
|
+
# our block gets access to assert_* methods and the +@connections+ variable above.
|
208
|
+
EM.defer proc {
|
209
|
+
instance_eval(&block)
|
210
|
+
}, proc {
|
211
|
+
# There's probably a better way of doing this, but without this line, different
|
212
|
+
# clients may create a race condition in tests, causing some of them to sometimes
|
213
|
+
# fail. This isn't foolproof either, should any client take more than 200 ms.
|
214
|
+
EM.add_timer(0.2) do
|
215
|
+
Juggernaut::Client.send_logouts_to_all_clients
|
216
|
+
EM.stop
|
217
|
+
end
|
218
|
+
}
|
219
|
+
end
|
220
|
+
ensure
|
221
|
+
# Restore old options.
|
222
|
+
Juggernaut.options = old_options if old_options
|
223
|
+
end
|
224
|
+
|
225
|
+
context "Server" do
|
226
|
+
|
227
|
+
should "accept a connection" do
|
228
|
+
with_server do
|
229
|
+
self.new_client do |c|
|
230
|
+
c.transmit :command => :subscribe, :channels => [ ]
|
231
|
+
end
|
232
|
+
assert_equal 1, @connections.select { |c| c.alive? }.size
|
233
|
+
assert_equal true, @connections.first.alive?
|
234
|
+
end
|
235
|
+
assert_equal false, @connections.first.alive?
|
236
|
+
end
|
237
|
+
|
238
|
+
should "register channels correctly" do
|
239
|
+
with_server do
|
240
|
+
self.new_client { |c| c.transmit :command => :subscribe, :channels => ["master", "slave"] }
|
241
|
+
end
|
242
|
+
assert @connections.first.has_channel?("master")
|
243
|
+
assert_equal false, @connections.first.has_channel?("non_existant")
|
244
|
+
assert @connections.first.has_channels?(["non_existant", "master", "slave"])
|
245
|
+
assert_equal false, @connections.first.has_channels?(["non_existant", "invalid"])
|
246
|
+
end
|
247
|
+
|
248
|
+
context "channel-wide broadcast" do
|
249
|
+
|
250
|
+
body = "This is a channel-wide broadcast test!"
|
251
|
+
|
252
|
+
should "be received by client in the same channel" do
|
253
|
+
subscriber = nil
|
254
|
+
with_server do
|
255
|
+
subscriber = self.new_client(:client_id => "broadcast_channel") { |c| c.subscribe %w(master) }
|
256
|
+
self.new_client { |c| c.broadcast_to_channels %w(master), body }
|
257
|
+
end
|
258
|
+
assert_not_nil subscriber
|
259
|
+
result = subscriber.receive
|
260
|
+
subscriber.close
|
261
|
+
assert_respond_to result, :[]
|
262
|
+
assert_equal body, result["body"]
|
263
|
+
end
|
264
|
+
|
265
|
+
should "not be received by client not in a channel" do
|
266
|
+
subscriber = nil
|
267
|
+
with_server do
|
268
|
+
subscriber = self.new_client(:client_id => "broadcast_channel") { |c| c.subscribe %w() }
|
269
|
+
self.new_client { |c| c.broadcast_to_channels %w(master), body }
|
270
|
+
end
|
271
|
+
assert_no_response subscriber
|
272
|
+
end
|
273
|
+
|
274
|
+
should "not be received by client in a different channel" do
|
275
|
+
subscriber = nil
|
276
|
+
with_server do
|
277
|
+
subscriber = self.new_client(:client_id => "broadcast_test") { |c| c.subscribe %w(slave) }
|
278
|
+
self.new_client { |c| c.broadcast_to_channels %w(broadcast_channel), body }
|
279
|
+
end
|
280
|
+
assert_no_response subscriber
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
# For some reason, these refuse to pass:
|
286
|
+
context "broadcast with no specific channel" do
|
287
|
+
|
288
|
+
body = "This is a broadcast test!"
|
289
|
+
|
290
|
+
should "be received by client not in any channels" do
|
291
|
+
subscriber = nil
|
292
|
+
with_server do
|
293
|
+
subscriber = self.new_client(:client_id => "broadcast_all") { |c| c.subscribe %w() }
|
294
|
+
self.new_client { |c| c.broadcast_to_channels %w(), body }
|
295
|
+
end
|
296
|
+
assert_body body, subscriber
|
297
|
+
end
|
298
|
+
|
299
|
+
should "be received by client in a channel" do
|
300
|
+
subscriber = nil
|
301
|
+
with_server do
|
302
|
+
subscriber = self.new_client(:client_id => "broadcast_all") { |c| c.subscribe %w(master) }
|
303
|
+
self.new_client { |c| c.broadcast_to_channels %w(), body }
|
304
|
+
end
|
305
|
+
assert_body body, subscriber
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
|
310
|
+
context "broadcast to a client" do
|
311
|
+
|
312
|
+
body = "This is a client-specific broadcast test!"
|
313
|
+
|
314
|
+
should "be received by the target client" do
|
315
|
+
subscriber = nil
|
316
|
+
with_server do
|
317
|
+
subscriber = self.new_client(:client_id => "broadcast_client") { |c| c.subscribe %w() }
|
318
|
+
self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
|
319
|
+
end
|
320
|
+
assert_body body, subscriber
|
321
|
+
end
|
322
|
+
|
323
|
+
should "not be received by other clients" do
|
324
|
+
subscriber = nil
|
325
|
+
with_server do
|
326
|
+
subscriber = self.new_client(:client_id => "broadcast_faker") { |c| c.subscribe %w() }
|
327
|
+
self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
|
328
|
+
end
|
329
|
+
assert_no_response subscriber
|
330
|
+
end
|
331
|
+
|
332
|
+
should "be saved until the client reconnects" do
|
333
|
+
subscriber = nil
|
334
|
+
with_server :store_messages => true do
|
335
|
+
self.new_client(:client_id => "broadcast_client") { |c| c.subscribe %w() }.close
|
336
|
+
self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
|
337
|
+
subscriber = self.new_client(:client_id => "broadcast_client") { |c| c.subscribe %w() }
|
338
|
+
end
|
339
|
+
assert_body body, subscriber
|
340
|
+
end
|
341
|
+
|
342
|
+
should "only be sent to new client connection" do
|
343
|
+
old_subscriber = nil
|
344
|
+
new_subscriber = nil
|
345
|
+
|
346
|
+
with_server :store_messages => true, :timeout => 30 do
|
347
|
+
old_subscriber = self.new_client(:client_id => "broadcast_client", :session_id => "1") { |c| c.subscribe %w() }
|
348
|
+
self.new_client { |c| c.broadcast_to_clients %w(broadcast_client), body }
|
349
|
+
@connections.first.client.expects(:send_message_to_connection).times(2)
|
350
|
+
new_subscriber = self.new_client(:client_id => "broadcast_client", :session_id => "2") { |c| c.subscribe %w() }
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
context "querying client list" do
|
357
|
+
|
358
|
+
should "return all clients" do
|
359
|
+
subscriber = nil
|
360
|
+
with_server do
|
361
|
+
self.new_client(:client_id => "alex") { |c| c.subscribe %w() }
|
362
|
+
self.new_client(:client_id => "bob") { |c| c.subscribe %w() }
|
363
|
+
subscriber = self.new_client(:client_id => "cindy") { |c| c.subscribe %w(); c.query_show_clients }
|
364
|
+
end
|
365
|
+
assert_not_nil subscriber
|
366
|
+
result = subscriber.receive
|
367
|
+
assert_not_nil result
|
368
|
+
assert_equal 3, result.size
|
369
|
+
assert_same_elements %w(alex bob cindy), result.collect { |r| r["client_id"] }
|
370
|
+
end
|
371
|
+
|
372
|
+
should "not include disconnected clients" do
|
373
|
+
subscriber = nil
|
374
|
+
with_server(:timeout => 0) do
|
375
|
+
self.new_client(:client_id => "sandra") { |c| c.subscribe %w() }
|
376
|
+
self.new_client(:client_id => "tom") { |c| c.subscribe %w() }.close
|
377
|
+
subscriber = self.new_client(:client_id => "vivian") { |c| c.subscribe %w(); c.query_show_clients }
|
378
|
+
end
|
379
|
+
assert_not_nil subscriber
|
380
|
+
result = subscriber.receive
|
381
|
+
assert_not_nil result
|
382
|
+
assert_equal 2, result.size
|
383
|
+
assert_same_elements %w(sandra vivian), result.collect { |r| r["client_id"] }
|
384
|
+
end
|
385
|
+
|
386
|
+
should "only return requested clients" do
|
387
|
+
subscriber = nil
|
388
|
+
with_server do
|
389
|
+
self.new_client(:client_id => "dixie") { |c| c.subscribe %w() }
|
390
|
+
self.new_client(:client_id => "eamon") { |c| c.subscribe %w() }
|
391
|
+
self.new_client(:client_id => "fanny") { |c| c.subscribe %w() }
|
392
|
+
subscriber = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(dixie fanny) }
|
393
|
+
end
|
394
|
+
assert_not_nil subscriber
|
395
|
+
result = subscriber.receive
|
396
|
+
assert_not_nil result
|
397
|
+
assert_equal 2, result.size
|
398
|
+
assert_same_elements %w(dixie fanny), result.collect { |r| r["client_id"] }
|
399
|
+
end
|
400
|
+
|
401
|
+
should "never return non-existant clients even when requested" do
|
402
|
+
subscriber = nil
|
403
|
+
with_server do
|
404
|
+
self.new_client(:client_id => "dixie") { |c| c.subscribe %w() }
|
405
|
+
self.new_client(:client_id => "eamon") { |c| c.subscribe %w() }
|
406
|
+
self.new_client(:client_id => "fanny") { |c| c.subscribe %w() }
|
407
|
+
subscriber = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(ginny homer) }
|
408
|
+
end
|
409
|
+
assert_not_nil subscriber
|
410
|
+
result = subscriber.receive
|
411
|
+
assert_not_nil result
|
412
|
+
assert_equal 0, result.size
|
413
|
+
end
|
414
|
+
|
415
|
+
should "return correct number of active connections" do
|
416
|
+
subscriber = nil
|
417
|
+
with_server do
|
418
|
+
5.times { self.new_client(:client_id => "homer") { |c| c.subscribe %w() } }
|
419
|
+
subscriber = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(homer) }
|
420
|
+
end
|
421
|
+
assert_not_nil subscriber
|
422
|
+
result = subscriber.receive
|
423
|
+
assert_not_nil result
|
424
|
+
assert_equal 1, result.size
|
425
|
+
assert_equal 5, result.first["num_connections"]
|
426
|
+
end
|
427
|
+
|
428
|
+
should "be equivalent when querying one client" do
|
429
|
+
s1, s2 = nil
|
430
|
+
with_server do
|
431
|
+
5.times { self.new_client(:client_id => "homer") { |c| c.subscribe %w() } }
|
432
|
+
s1 = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_client "homer" }
|
433
|
+
s2 = self.new_client(:client_id => "zelda") { |c| c.subscribe %w(); c.query_show_clients %w(homer) }
|
434
|
+
end
|
435
|
+
assert_not_nil s1
|
436
|
+
assert_not_nil s2
|
437
|
+
r1 = s1.receive
|
438
|
+
assert_not_nil r1
|
439
|
+
r2 = s2.receive
|
440
|
+
assert_not_nil r2
|
441
|
+
assert_equal 1, r2.size
|
442
|
+
assert_equal r1, r2.first
|
443
|
+
end
|
444
|
+
|
445
|
+
should "only return clients in specific channels" do
|
446
|
+
subscriber = nil
|
447
|
+
with_server do
|
448
|
+
self.new_client(:client_id => "alexa") { |c| c.subscribe %w(master slave zoo) }
|
449
|
+
self.new_client(:client_id => "bobby") { |c| c.subscribe %w(master slave) }
|
450
|
+
self.new_client(:client_id => "cindy") { |c| c.subscribe %w(master zoo) }
|
451
|
+
self.new_client(:client_id => "dixon") { |c| c.subscribe %w(slave zoo) }
|
452
|
+
self.new_client(:client_id => "eamon") { |c| c.subscribe %w(slave) }
|
453
|
+
self.new_client(:client_id => "flack") { |c| c.subscribe %w(decoy slave) }
|
454
|
+
subscriber = self.new_client(:client_id => "geoff") { |c| c.subscribe %w(zoo); c.query_show_clients_for_channels %w(master zoo) }
|
455
|
+
end
|
456
|
+
assert_response subscriber do |result|
|
457
|
+
assert_equal 5, result.size
|
458
|
+
assert_same_elements %w(alexa bobby cindy dixon geoff), result.collect { |r| r["client_id"] }
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
463
|
+
|
464
|
+
context "upon processing an invalid command" do
|
465
|
+
|
466
|
+
should "disconnect immediately" do
|
467
|
+
subscriber = nil
|
468
|
+
with_server do
|
469
|
+
subscriber = self.new_client(:client_id => "pinocchio") { |c| c.transmit :command => :some_undefined_command; c.subscribe %w(); c }
|
470
|
+
end
|
471
|
+
assert_server_disconnected subscriber
|
472
|
+
end
|
473
|
+
|
474
|
+
end
|
475
|
+
|
476
|
+
%w(broadcast subscribe query).each do |type|
|
477
|
+
|
478
|
+
context "upon receiving malformed #{type}" do
|
479
|
+
|
480
|
+
should "disconnect immediately" do
|
481
|
+
subscriber = nil
|
482
|
+
with_server do
|
483
|
+
subscriber = self.new_client(:client_id => "pinocchio") { |c| c.transmit :command => type, :type => :unknown; c.subscribe %w(); c }
|
484
|
+
end
|
485
|
+
assert_server_disconnected subscriber
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|
490
|
+
end
|
491
|
+
|
492
|
+
context "upon receiving invalid JSON" do
|
493
|
+
|
494
|
+
should "disconnect immediately" do
|
495
|
+
subscriber = nil
|
496
|
+
with_server do
|
497
|
+
subscriber = self.new_client(:client_id => "pinocchio") { |c| c.send_raw "invalid json..."; c }
|
498
|
+
end
|
499
|
+
assert_server_disconnected subscriber
|
500
|
+
end
|
501
|
+
|
502
|
+
end
|
503
|
+
|
504
|
+
context "crossdomain file request" do
|
505
|
+
|
506
|
+
should "return contents of crossdomain file" do
|
507
|
+
subscriber = nil
|
508
|
+
with_server do
|
509
|
+
subscriber = self.new_client(:client_id => "pinocchio") { |c| c.request_crossdomain_file }
|
510
|
+
end
|
511
|
+
assert_raw_response subscriber, <<-EOF
|
512
|
+
<cross-domain-policy>
|
513
|
+
<allow-access-from domain="*" to-ports="#{OPTIONS[:port]}" />
|
514
|
+
</cross-domain-policy>
|
515
|
+
EOF
|
516
|
+
end
|
517
|
+
|
518
|
+
end
|
519
|
+
|
520
|
+
context "querying channel list" do
|
521
|
+
|
522
|
+
should "return channel list" do
|
523
|
+
subscribe = nil
|
524
|
+
with_server do
|
525
|
+
self.new_client(:client_id => "homer") { |c| c.subscribe %w(groupie master slave1 slave2) }
|
526
|
+
self.new_client(:client_id => "marge") { |c| c.subscribe %w(master slave1 slave2) }
|
527
|
+
subscribe = self.new_client(:client_id => "pinocchio") { |c|
|
528
|
+
c.subscribe %w(master slave1)
|
529
|
+
c.query_show_channels_for_client "marge"
|
530
|
+
}
|
531
|
+
end
|
532
|
+
assert_response subscribe do |result|
|
533
|
+
assert_equal 3, result.size
|
534
|
+
assert_same_elements %w(master slave1 slave2), result
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
end
|
539
|
+
|
540
|
+
context "remove channel request" do
|
541
|
+
|
542
|
+
should "work on all clients when requested" do
|
543
|
+
with_server do
|
544
|
+
self.new_client(:client_id => "homer") { |c| c.subscribe %w(groupie master slave1 slave2) }
|
545
|
+
self.new_client(:client_id => "marge") { |c| c.subscribe %w(master slave1 slave2) }
|
546
|
+
self.new_client(:client_id => "pinocchio") { |c|
|
547
|
+
c.subscribe %w(master slave1 slave2)
|
548
|
+
c.query_remove_channels_from_all_clients %w(slave1 slave2)
|
549
|
+
}
|
550
|
+
end
|
551
|
+
@connections.each do |connection|
|
552
|
+
assert_does_not_contain connection.channels, /slave/
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
should "work on specific clients when requested" do
|
557
|
+
with_server do
|
558
|
+
self.new_client(:client_id => "homer") { |c| c.subscribe %w(groupie master slave1 slave2) }
|
559
|
+
self.new_client(:client_id => "marge") { |c| c.subscribe %w(master slave1 slave2) }
|
560
|
+
self.new_client(:client_id => "pinocchio") { |c|
|
561
|
+
c.subscribe %w(master slave1 slave2)
|
562
|
+
c.query_remove_channels_from_client %w(slave1 slave2), %w(homer)
|
563
|
+
}
|
564
|
+
end
|
565
|
+
assert_does_not_contain @connections.find { |c| c.instance_eval("@request[:client_id]") == "homer" }.channels, /slave/
|
566
|
+
assert_contains @connections.find { |c| c.instance_eval("@request[:client_id]") == "marge" }.channels, /slave/
|
567
|
+
end
|
568
|
+
|
569
|
+
end
|
570
|
+
|
571
|
+
end
|
572
|
+
|
573
|
+
end
|