serverside 0.2.9 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +56 -0
- data/Rakefile +12 -52
- data/bin/serverside +1 -1
- data/lib/serverside/application.rb +2 -1
- data/lib/serverside/caching.rb +62 -50
- data/lib/serverside/controllers.rb +91 -0
- data/lib/serverside/core_ext.rb +6 -0
- data/lib/serverside/daemon.rb +25 -5
- data/lib/serverside/request.rb +17 -11
- data/lib/serverside/routing.rb +11 -10
- data/lib/serverside/server.rb +14 -6
- data/lib/serverside/static.rb +7 -18
- data/lib/serverside/template.rb +20 -12
- data/spec/caching_spec.rb +318 -0
- data/spec/cluster_spec.rb +140 -0
- data/{test/spec → spec}/connection_spec.rb +4 -4
- data/{test/spec/controller_spec.rb → spec/controllers_spec.rb} +15 -12
- data/{test/spec → spec}/core_ext_spec.rb +23 -18
- data/spec/daemon_spec.rb +99 -0
- data/{test/spec → spec}/request_spec.rb +45 -45
- data/spec/routing_spec.rb +240 -0
- data/spec/server_spec.rb +40 -0
- data/spec/static_spec.rb +279 -0
- data/spec/template_spec.rb +129 -0
- metadata +21 -35
- data/lib/serverside/controller.rb +0 -67
- data/test/functional/primitive_static_server_test.rb +0 -61
- data/test/functional/request_body_test.rb +0 -93
- data/test/functional/routing_server.rb +0 -14
- data/test/functional/routing_server_test.rb +0 -41
- data/test/functional/static_profile.rb +0 -17
- data/test/functional/static_rfuzz.rb +0 -31
- data/test/functional/static_server.rb +0 -7
- data/test/functional/static_server_test.rb +0 -31
- data/test/spec/caching_spec.rb +0 -139
- data/test/test_helper.rb +0 -2
- data/test/unit/cluster_test.rb +0 -129
- data/test/unit/connection_test.rb +0 -48
- data/test/unit/core_ext_test.rb +0 -46
- data/test/unit/daemon_test.rb +0 -75
- data/test/unit/request_test.rb +0 -278
- data/test/unit/routing_test.rb +0 -171
- data/test/unit/server_test.rb +0 -28
- data/test/unit/static_test.rb +0 -171
- data/test/unit/template_test.rb +0 -78
@@ -1,24 +1,29 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/serverside')
|
2
2
|
|
3
3
|
# String extensions
|
4
4
|
|
5
5
|
context "String" do
|
6
6
|
specify "should have URI escaping functionality" do
|
7
|
-
'a b c'.uri_escape.
|
8
|
-
'a/b#1@6%8K'.uri_escape.
|
7
|
+
'a b c'.uri_escape.should == 'a+b+c'
|
8
|
+
'a/b#1@6%8K'.uri_escape.should == 'a%2Fb%231%406%258K'
|
9
9
|
end
|
10
10
|
|
11
11
|
specify "should have URI unescaping functionality" do
|
12
|
-
'a%20b%20c'.uri_unescape.
|
13
|
-
'a%2Fb%231%406%258K'.uri_unescape.
|
12
|
+
'a%20b%20c'.uri_unescape.should == 'a b c'
|
13
|
+
'a%2Fb%231%406%258K'.uri_unescape.should == 'a/b#1@6%8K'
|
14
14
|
s = 'b!-=&*%aAåabéfak{}":,m"\'Mbac( 12313t awerqwe)'
|
15
|
-
s.uri_escape.uri_unescape.
|
15
|
+
s.uri_escape.uri_unescape.should == s
|
16
16
|
end
|
17
17
|
|
18
18
|
specify "should have a / operator for joining paths." do
|
19
|
-
('abc'/'def').
|
20
|
-
('/hello/'/'there').
|
21
|
-
('touch'/'/me/'/'hold'/'/me').
|
19
|
+
('abc'/'def').should == 'abc/def'
|
20
|
+
('/hello/'/'there').should == '/hello/there'
|
21
|
+
('touch'/'/me/'/'hold'/'/me').should == 'touch/me/hold/me'
|
22
|
+
end
|
23
|
+
|
24
|
+
specify ".underscore should turn camel-cased phrases to underscored ones" do
|
25
|
+
'CamelCase'.underscore.should == 'camel_case'
|
26
|
+
'Content-Type'.underscore.should == 'content_type'
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
@@ -30,15 +35,15 @@ end
|
|
30
35
|
|
31
36
|
context "Symbol.to_s" do
|
32
37
|
specify "should convert the symbol to a string" do
|
33
|
-
:abc_def.to_s.
|
38
|
+
:abc_def.to_s.should == 'abc_def'
|
34
39
|
:def_ghi.to_s.should_be_instance_of String
|
35
|
-
:ghi_jkl.to_s.
|
40
|
+
:ghi_jkl.to_s.should == :ghi_jkl.id2name
|
36
41
|
end
|
37
42
|
|
38
43
|
specify "should cache the id2name value" do
|
39
44
|
:kwantz_mit_krantz._to_s.should_be_nil
|
40
45
|
:kwantz_mit_krantz.to_s
|
41
|
-
:kwantz_mit_krantz._to_s.
|
46
|
+
:kwantz_mit_krantz._to_s.should == :kwantz_mit_krantz.id2name
|
42
47
|
end
|
43
48
|
|
44
49
|
specify "should always return the same cached value" do
|
@@ -57,11 +62,11 @@ context "Proc.proc_tag" do
|
|
57
62
|
end
|
58
63
|
|
59
64
|
specify "should return the same tag always" do
|
60
|
-
@l1.proc_tag.
|
65
|
+
@l1.proc_tag.should == @l1.proc_tag
|
61
66
|
end
|
62
67
|
|
63
68
|
specify "should return the object id in base 36 prefixed with 'proc_'" do
|
64
|
-
@l1.proc_tag.
|
69
|
+
@l1.proc_tag.should == 'proc_' + @l1.object_id.to_s(36).sub('-', '_')
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
@@ -76,12 +81,12 @@ context "Object.const_tag" do
|
|
76
81
|
end
|
77
82
|
|
78
83
|
specify "should return the same tag always" do
|
79
|
-
@o1.const_tag.
|
80
|
-
@o2.const_tag.
|
84
|
+
@o1.const_tag.should == @o1.const_tag
|
85
|
+
@o2.const_tag.should == @o2.const_tag
|
81
86
|
end
|
82
87
|
|
83
88
|
specify "should return the object id in base 36 (upcase) prefixed with 'C'" do
|
84
|
-
@o1.const_tag.
|
85
|
-
@o2.const_tag.
|
89
|
+
@o1.const_tag.should == 'C' + @o1.object_id.to_s(36).upcase.sub('-', '_')
|
90
|
+
@o2.const_tag.should == 'C' + @o2.object_id.to_s(36).upcase.sub('-', '_')
|
86
91
|
end
|
87
92
|
end
|
data/spec/daemon_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/serverside')
|
2
|
+
|
3
|
+
class TestDaemon < Daemon::Base
|
4
|
+
def self.start
|
5
|
+
@count = 0
|
6
|
+
loop {@count += 1; sleep 0.1}
|
7
|
+
puts "finished"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.result_fn
|
11
|
+
File.join(Daemon::WorkingDirectory, 'test.result')
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.stop
|
15
|
+
#File.open(result_fn, 'w') {|f| f << @count}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "Daemon::WorkingDirectory" do
|
20
|
+
specify "should be the working directory (pwd)" do
|
21
|
+
Daemon::WorkingDirectory.should == FileUtils.pwd
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "Daemon::Base.pid_fn" do
|
26
|
+
specify "should construct the pid_fn according to the class name" do
|
27
|
+
Daemon::Base.pid_fn.should ==
|
28
|
+
File.join(Daemon::WorkingDirectory, 'daemon.base.pid')
|
29
|
+
|
30
|
+
TestDaemon.pid_fn.should ==
|
31
|
+
File.join(Daemon::WorkingDirectory, 'testdaemon.pid')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "Daemon::PidFile" do
|
36
|
+
specify "should store the pid in a file" do
|
37
|
+
pid = rand(1_000_000)
|
38
|
+
Daemon::PidFile.store(TestDaemon, pid)
|
39
|
+
File.file?(TestDaemon.pid_fn).should == true
|
40
|
+
IO.read(TestDaemon.pid_fn).should == pid.to_s
|
41
|
+
Daemon::PidFile.remove(TestDaemon)
|
42
|
+
end
|
43
|
+
|
44
|
+
specify "should recall the pid from the pid file" do
|
45
|
+
pid = rand(1_000_000)
|
46
|
+
Daemon::PidFile.store(TestDaemon, pid)
|
47
|
+
Daemon::PidFile.recall(TestDaemon).should == pid
|
48
|
+
Daemon::PidFile.recall(TestDaemon).should_be_a_kind_of Fixnum
|
49
|
+
Daemon::PidFile.remove(TestDaemon)
|
50
|
+
end
|
51
|
+
|
52
|
+
specify "should raise an exception if can't recall pid" do
|
53
|
+
Daemon::PidFile.remove(TestDaemon) # make sure the file doesn't exist
|
54
|
+
proc {Daemon::PidFile.recall(TestDaemon)}.should_raise
|
55
|
+
end
|
56
|
+
|
57
|
+
specify "should remove the pid file" do
|
58
|
+
File.file?(TestDaemon.pid_fn).should == false
|
59
|
+
Daemon::PidFile.store(TestDaemon, 1024)
|
60
|
+
File.file?(TestDaemon.pid_fn).should == true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "Daemon.control" do
|
65
|
+
# teardown {Daemon.control(TestDaemon, :stop) rescue nil}
|
66
|
+
|
67
|
+
# specify "should start and stop the daemon" do
|
68
|
+
# Daemon::PidFile.remove(TestDaemon)
|
69
|
+
# Daemon.control(TestDaemon, :start)
|
70
|
+
# sleep 0.2
|
71
|
+
# File.file?(TestDaemon.pid_fn).should == true
|
72
|
+
# sleep 0.5
|
73
|
+
# proc {Daemon::PidFile.recall(TestDaemon)}.should_not_raise
|
74
|
+
# Daemon.control(TestDaemon, :stop)
|
75
|
+
# sleep 0.2
|
76
|
+
# File.file?(TestDaemon.result_fn).should == false
|
77
|
+
# end
|
78
|
+
|
79
|
+
# specify "should restart the daemon" do
|
80
|
+
# Daemon::PidFile.remove(TestDaemon)
|
81
|
+
# Daemon.control(TestDaemon, :start)
|
82
|
+
# begin
|
83
|
+
# sleep 1
|
84
|
+
# pid1 = Daemon::PidFile.recall(TestDaemon)
|
85
|
+
# Daemon.control(TestDaemon, :restart)
|
86
|
+
# sleep 1
|
87
|
+
# pid2 = Daemon::PidFile.recall(TestDaemon)
|
88
|
+
# pid1.should_not == pid2
|
89
|
+
# ensure
|
90
|
+
# Daemon.control(TestDaemon, :stop)
|
91
|
+
# Daemon::PidFile.remove(TestDaemon)
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
|
95
|
+
specify "should raise RuntimeError for invalid command" do
|
96
|
+
proc {Daemon.control(TestDaemon, :invalid)}.should_raise RuntimeError
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/serverside')
|
2
2
|
require 'stringio'
|
3
3
|
|
4
4
|
class DummyRequest2 < ServerSide::HTTP::Request
|
@@ -20,24 +20,24 @@ context "HTTP::Request.process" do
|
|
20
20
|
specify "should call parse and and short-circuit if the result is nil" do
|
21
21
|
r = DummyRequest2.new(nil)
|
22
22
|
r.process.should_be_nil
|
23
|
-
r.calls.
|
23
|
+
r.calls.should == [:parse]
|
24
24
|
|
25
25
|
r.calls = []
|
26
26
|
r.parse_result = false
|
27
27
|
r.process.should_be false
|
28
|
-
r.calls.
|
28
|
+
r.calls.should == [:parse]
|
29
29
|
end
|
30
30
|
|
31
31
|
specify "should follow parse with respond and return @persistent" do
|
32
32
|
r = DummyRequest2.new(nil)
|
33
33
|
r.parse_result = true
|
34
34
|
r.process.should_be_nil
|
35
|
-
r.calls.
|
35
|
+
r.calls.should == [:parse, :respond]
|
36
36
|
|
37
37
|
r.calls = []
|
38
38
|
r.persistent = 'mau'
|
39
|
-
r.process.
|
40
|
-
r.calls.
|
39
|
+
r.process.should == 'mau'
|
40
|
+
r.calls.should == [:parse, :respond]
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -69,27 +69,27 @@ context "HTTP::Request.parse" do
|
|
69
69
|
r.socket = StringIO.new(
|
70
70
|
"POST /test HTTP/1.1\r\nContent-Type: text/html\r\n\r\n")
|
71
71
|
r.parse.should_be r.headers
|
72
|
-
r.method.
|
73
|
-
r.path.
|
72
|
+
r.method.should == :post
|
73
|
+
r.path.should == '/test'
|
74
74
|
r.query.should_be_nil
|
75
|
-
r.version.
|
76
|
-
r.parameters.
|
77
|
-
r.headers.
|
78
|
-
r.cookies.
|
75
|
+
r.version.should == '1.1'
|
76
|
+
r.parameters.should == {}
|
77
|
+
r.headers.should == {'Content-Type' => 'text/html'}
|
78
|
+
r.cookies.should == {}
|
79
79
|
r.response_cookies.should_be_nil
|
80
|
-
r.persistent.
|
80
|
+
r.persistent.should == true
|
81
81
|
end
|
82
82
|
|
83
83
|
specify "should correctly handle trailing slash in path" do
|
84
84
|
r = ServerSide::HTTP::Request.new(nil)
|
85
85
|
r.socket = StringIO.new("POST /test/asdf/qw/ HTTP/1.1\r\n\r\n")
|
86
86
|
r.parse.should_not_be_nil
|
87
|
-
r.path.
|
87
|
+
r.path.should == '/test/asdf/qw'
|
88
88
|
|
89
89
|
r.socket = StringIO.new(
|
90
90
|
"POST /test/asdf/qw/?time=24%20hours HTTP/1.1\r\n\r\n")
|
91
91
|
r.parse.should_not_be_nil
|
92
|
-
r.path.
|
92
|
+
r.path.should == '/test/asdf/qw'
|
93
93
|
end
|
94
94
|
|
95
95
|
specify "should parse URL-encoded parameters" do
|
@@ -97,9 +97,9 @@ context "HTTP::Request.parse" do
|
|
97
97
|
r.socket = StringIO.new(
|
98
98
|
"POST /test?q=node_history&time=24%20hours HTTP/1.1\r\n\r\n")
|
99
99
|
r.parse.should_not_be_nil
|
100
|
-
r.parameters.size.
|
101
|
-
r.parameters[:time].
|
102
|
-
r.parameters[:q].
|
100
|
+
r.parameters.size.should == 2
|
101
|
+
r.parameters[:time].should == '24 hours'
|
102
|
+
r.parameters[:q].should == 'node_history'
|
103
103
|
end
|
104
104
|
|
105
105
|
specify "should correctly parse the HTTP version" do
|
@@ -107,30 +107,30 @@ context "HTTP::Request.parse" do
|
|
107
107
|
r.socket = StringIO.new(
|
108
108
|
"POST / HTTP/1.0\r\n\r\n")
|
109
109
|
r.parse.should_not_be_nil
|
110
|
-
r.version.
|
110
|
+
r.version.should == '1.0'
|
111
111
|
r.socket = StringIO.new(
|
112
112
|
"POST / HTTP/3.2\r\n\r\n")
|
113
113
|
r.parse.should_not_be_nil
|
114
|
-
r.version.
|
114
|
+
r.version.should == '3.2'
|
115
115
|
end
|
116
116
|
|
117
117
|
specify "should set @persistent correctly" do
|
118
118
|
r = ServerSide::HTTP::Request.new(nil)
|
119
119
|
r.socket = StringIO.new("POST / HTTP/1.0\r\n\r\n")
|
120
120
|
r.parse.should_not_be_nil
|
121
|
-
r.persistent.
|
121
|
+
r.persistent.should == false
|
122
122
|
r.socket = StringIO.new("POST / HTTP/1.1\r\n\r\n")
|
123
123
|
r.parse.should_not_be_nil
|
124
|
-
r.persistent.
|
124
|
+
r.persistent.should == true
|
125
125
|
r.socket = StringIO.new("POST / HTTP/0.6\r\n\r\n")
|
126
126
|
r.parse.should_not_be_nil
|
127
|
-
r.persistent.
|
127
|
+
r.persistent.should == false
|
128
128
|
r.socket = StringIO.new("POST / HTTP/1.1\r\nConnection: close\r\n\r\n")
|
129
129
|
r.parse.should_not_be_nil
|
130
|
-
r.persistent.
|
130
|
+
r.persistent.should == false
|
131
131
|
r.socket = StringIO.new("POST / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n")
|
132
132
|
r.parse.should_not_be_nil
|
133
|
-
r.persistent.
|
133
|
+
r.persistent.should == true
|
134
134
|
end
|
135
135
|
|
136
136
|
specify "should parse cookies" do
|
@@ -138,10 +138,10 @@ context "HTTP::Request.parse" do
|
|
138
138
|
r.socket = StringIO.new(
|
139
139
|
"POST / HTTP/1.0\r\nCookie: abc=1342; def=7%2f4\r\n\r\n")
|
140
140
|
r.parse.should_not_be_nil
|
141
|
-
r.headers['Cookie'].
|
142
|
-
r.cookies.size.
|
143
|
-
r.cookies[:abc].
|
144
|
-
r.cookies[:def].
|
141
|
+
r.headers['Cookie'].should == 'abc=1342; def=7%2f4'
|
142
|
+
r.cookies.size.should == 2
|
143
|
+
r.cookies[:abc].should == '1342'
|
144
|
+
r.cookies[:def].should == '7/4'
|
145
145
|
end
|
146
146
|
|
147
147
|
specify "should parse the post body" do
|
@@ -149,9 +149,9 @@ context "HTTP::Request.parse" do
|
|
149
149
|
r.socket = StringIO.new(
|
150
150
|
"POST /?q=node_history HTTP/1.0\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 15\r\n\r\ntime=24%20hours")
|
151
151
|
r.parse.should_not_be_nil
|
152
|
-
r.parameters.size.
|
153
|
-
r.parameters[:q].
|
154
|
-
r.parameters[:time].
|
152
|
+
r.parameters.size.should == 2
|
153
|
+
r.parameters[:q].should == 'node_history'
|
154
|
+
r.parameters[:time].should == '24 hours'
|
155
155
|
end
|
156
156
|
end
|
157
157
|
|
@@ -161,7 +161,7 @@ context "HTTP::Request.send_response" do
|
|
161
161
|
r.socket = StringIO.new
|
162
162
|
r.send_response(200, 'text', 'Hello there!')
|
163
163
|
r.socket.rewind
|
164
|
-
r.socket.read.
|
164
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
|
165
165
|
end
|
166
166
|
|
167
167
|
specify "should format a response without connect-close when persistent" do
|
@@ -170,7 +170,7 @@ context "HTTP::Request.send_response" do
|
|
170
170
|
r.persistent = true
|
171
171
|
r.send_response(200, 'text', 'Hello there!')
|
172
172
|
r.socket.rewind
|
173
|
-
r.socket.read.
|
173
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
|
174
174
|
end
|
175
175
|
|
176
176
|
specify "should format a response without content-length for streaming response" do
|
@@ -179,10 +179,10 @@ context "HTTP::Request.send_response" do
|
|
179
179
|
r.persistent = true
|
180
180
|
r.send_response(200, 'text')
|
181
181
|
r.socket.rewind
|
182
|
-
r.socket.read.
|
182
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\n"
|
183
183
|
r.stream('hey there')
|
184
184
|
r.socket.rewind
|
185
|
-
r.socket.read.
|
185
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\nhey there"
|
186
186
|
end
|
187
187
|
|
188
188
|
specify "should include response_headers and headers in the response" do
|
@@ -192,21 +192,21 @@ context "HTTP::Request.send_response" do
|
|
192
192
|
r.response_headers['XXX'] = 'Test'
|
193
193
|
r.send_response(200, 'text')
|
194
194
|
r.socket.rewind
|
195
|
-
r.socket.read.
|
195
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nXXX: Test\r\n\r\n"
|
196
196
|
|
197
197
|
r = ServerSide::HTTP::Request.new(nil)
|
198
198
|
r.socket = StringIO.new
|
199
199
|
r.persistent = true
|
200
200
|
r.send_response(200, 'text', nil, nil, {'YYY' => 'TTT'})
|
201
201
|
r.socket.rewind
|
202
|
-
r.socket.read.
|
202
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nYYY: TTT\r\n\r\n"
|
203
203
|
end
|
204
204
|
|
205
205
|
specify "should set persistent to false if exception is raised" do
|
206
206
|
r = ServerSide::HTTP::Request.new(nil)
|
207
207
|
r.persistent = true
|
208
208
|
proc {r.send_response(200, 'text', 'Hello there!')}.should_not_raise
|
209
|
-
r.persistent.
|
209
|
+
r.persistent.should == false
|
210
210
|
end
|
211
211
|
|
212
212
|
specify "should include cookies in the response" do
|
@@ -214,18 +214,18 @@ context "HTTP::Request.send_response" do
|
|
214
214
|
r.socket = StringIO.new
|
215
215
|
t = Time.now + 360
|
216
216
|
r.set_cookie(:session, "ABCDEFG", t)
|
217
|
-
r.response_cookies.
|
217
|
+
r.response_cookies.should == "Set-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\n"
|
218
218
|
r.send_response(200, 'text', 'Hi!')
|
219
219
|
r.socket.rewind
|
220
|
-
r.socket.read.
|
220
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\nContent-Length: 3\r\n\r\nHi!"
|
221
221
|
|
222
222
|
r = ServerSide::HTTP::Request.new(nil)
|
223
223
|
r.socket = StringIO.new
|
224
224
|
r.delete_cookie(:session)
|
225
|
-
r.response_cookies.
|
225
|
+
r.response_cookies.should == "Set-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\n"
|
226
226
|
r.send_response(200, 'text', 'Hi!')
|
227
227
|
r.socket.rewind
|
228
|
-
r.socket.read.
|
228
|
+
r.socket.read.should == "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nSet-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\nContent-Length: 3\r\n\r\nHi!"
|
229
229
|
end
|
230
230
|
end
|
231
231
|
|
@@ -275,7 +275,7 @@ context "HTTP::Request.redirect" do
|
|
275
275
|
r.socket = StringIO.new
|
276
276
|
r.redirect('http://mau.com/132')
|
277
277
|
r.socket.rewind
|
278
|
-
r.socket.read.
|
278
|
+
r.socket.read.should == "HTTP/1.1 302\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
|
279
279
|
end
|
280
280
|
|
281
281
|
specify "should send a 301 response for permanent redirect" do
|
@@ -283,6 +283,6 @@ context "HTTP::Request.redirect" do
|
|
283
283
|
r.socket = StringIO.new
|
284
284
|
r.redirect('http://mau.com/132', true)
|
285
285
|
r.socket.rewind
|
286
|
-
r.socket.read.
|
286
|
+
r.socket.read.should == "HTTP/1.1 301\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
|
287
287
|
end
|
288
288
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../lib/serverside')
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
class ServerSide::Router
|
5
|
+
attr_accessor :t, :parameters, :path
|
6
|
+
|
7
|
+
def self.rules
|
8
|
+
@@rules
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.reset
|
12
|
+
@@rules = []
|
13
|
+
@@default_route = nil
|
14
|
+
define_method(:respond) {nil}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
R = ServerSide::Router
|
19
|
+
|
20
|
+
context "Router.routes_defined?" do
|
21
|
+
specify "should return nil if no routes were defined" do
|
22
|
+
R.reset
|
23
|
+
R.routes_defined?.should_be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "should return true if routes were defined" do
|
27
|
+
R.reset
|
28
|
+
R.route('/controller') {}
|
29
|
+
R.routes_defined?.should_be true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "Router.route" do
|
34
|
+
specify "should add the rule to @@rules" do
|
35
|
+
l = proc {1 + 1}
|
36
|
+
R.reset
|
37
|
+
R.route(:path => '/t', &l)
|
38
|
+
R.rules.size.should == 1
|
39
|
+
R.rules[0][0].should == {:path => '/t'}
|
40
|
+
R.rules[0][1].should == l
|
41
|
+
end
|
42
|
+
|
43
|
+
specify "should convert a string argument to a path rule" do
|
44
|
+
R.reset
|
45
|
+
R.route('/test') {}
|
46
|
+
R.rules[0][0].should == {:path => '/test'}
|
47
|
+
end
|
48
|
+
|
49
|
+
specify "should convert a regexp argument to a path rule" do
|
50
|
+
R.reset
|
51
|
+
R.route(/abc/) {}
|
52
|
+
R.rules[0][0].should == {:path => /abc/}
|
53
|
+
end
|
54
|
+
|
55
|
+
specify "should convert an array argument into a multiple path rule" do
|
56
|
+
R.reset
|
57
|
+
R.route(['/a', '/b', '/c']) {}
|
58
|
+
R.rules[0][0].should == {:path => ['/a', '/b', '/c']}
|
59
|
+
end
|
60
|
+
|
61
|
+
specify "should store a hash argument as the rule" do
|
62
|
+
R.reset
|
63
|
+
R.route(:a => 'abc', :b => 'def') {}
|
64
|
+
R.rules[0][0].should_be_a_kind_of Hash
|
65
|
+
R.rules[0][0].size.should == 2
|
66
|
+
R.rules[0][0][:a].should == 'abc'
|
67
|
+
R.rules[0][0][:b].should == 'def'
|
68
|
+
end
|
69
|
+
|
70
|
+
specify "should unshift new rules into the rules array" do
|
71
|
+
R.reset
|
72
|
+
R.route('abc') {}
|
73
|
+
R.route('def') {}
|
74
|
+
R.route('ghi') {}
|
75
|
+
R.rules.size.should == 3
|
76
|
+
R.rules[0][0][:path].should == 'ghi'
|
77
|
+
R.rules[1][0][:path].should == 'def'
|
78
|
+
R.rules[2][0][:path].should == 'abc'
|
79
|
+
end
|
80
|
+
|
81
|
+
specify "should accept a proc as a rule" do
|
82
|
+
R.reset
|
83
|
+
l1 = proc {}
|
84
|
+
l2 = proc {}
|
85
|
+
R.route(l1, &l2)
|
86
|
+
R.rules.size.should == 1
|
87
|
+
R.rules[0][0].should_be l1
|
88
|
+
R.rules[0][1].should_be l2
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "Router.compile_rules" do
|
93
|
+
specify "should compile a respond method for routing requests" do
|
94
|
+
R.reset
|
95
|
+
R.new(StringIO.new).respond.should_be_nil
|
96
|
+
R.rules << [{:t => 'abc'}, proc{:abc}]
|
97
|
+
R.rules << [{:t => 'def'}, proc{:def}]
|
98
|
+
R.default_route {:default}
|
99
|
+
# R.compile_rules - already called by default_route
|
100
|
+
r = R.new(StringIO.new)
|
101
|
+
r.t = 'abc'
|
102
|
+
r.respond.should == :abc
|
103
|
+
r.t = 'def'
|
104
|
+
r.respond.should == :def
|
105
|
+
r.t = ''
|
106
|
+
r.respond.should == :default
|
107
|
+
end
|
108
|
+
|
109
|
+
specify "should allow handlers to give up on a request, and then pass it on." do
|
110
|
+
R.reset
|
111
|
+
R.default_route {:default}
|
112
|
+
R.new(StringIO.new).respond.should == :default
|
113
|
+
R.route('.*') {@path == '/first' ? :first : nil}
|
114
|
+
R.route('.*') {@path == '/second' ? :second : nil}
|
115
|
+
r = R.new(StringIO.new)
|
116
|
+
r.path = '/second'
|
117
|
+
r.respond.should == :second
|
118
|
+
r.path = '/first'
|
119
|
+
r.respond.should == :first
|
120
|
+
r.path = '/other'
|
121
|
+
r.respond.should == :default
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "Router.rule_to_statement" do
|
126
|
+
specify "should define procs as methods and construct a test expression" do
|
127
|
+
l1 = proc {}
|
128
|
+
l2 = proc {}
|
129
|
+
R.rule_to_statement(l1, l2).should == "if #{l1.proc_tag} && (r = #{l2.proc_tag}); return r; end\n"
|
130
|
+
r = R.new(StringIO.new)
|
131
|
+
r.should_respond_to l1.proc_tag
|
132
|
+
r.should_respond_to l2.proc_tag
|
133
|
+
end
|
134
|
+
|
135
|
+
specify "should convert hash rule with single key-value to a test expression" do
|
136
|
+
l3 = proc {}
|
137
|
+
s = R.rule_to_statement({:path => '/.*'}, l3)
|
138
|
+
s =~ /^if \(@path =~ ([^\(]*)\)/
|
139
|
+
eval("R::#{$1}").should == /\/.*/
|
140
|
+
r = R.new(StringIO.new)
|
141
|
+
r.should_respond_to l3.proc_tag
|
142
|
+
end
|
143
|
+
|
144
|
+
specify "should convert hash with multiple key-values to an OR test expression" do
|
145
|
+
l4 = proc {}
|
146
|
+
|
147
|
+
s = R.rule_to_statement({:path => '/controller', :host => 'static'}, l4)
|
148
|
+
s.should_match /\(@path\s=~\s([^\)]+)\)/
|
149
|
+
s =~ /\(@path\s=~\s([^\)]+)\)/
|
150
|
+
eval("R::#{$1}").should == /\/controller/
|
151
|
+
s.should_match /\(@host\s=~\s([^\)]+)\)/
|
152
|
+
s =~ /\(@host\s=~\s([^\)]+)\)/
|
153
|
+
eval("R::#{$1}").should == /static/
|
154
|
+
r = R.new(StringIO.new)
|
155
|
+
r.should_respond_to l4.proc_tag
|
156
|
+
end
|
157
|
+
|
158
|
+
specify "should convert hash with Array value to a test expression" do
|
159
|
+
l5 = proc {}
|
160
|
+
s = R.rule_to_statement({:path => ['/x', '/y']}, l5)
|
161
|
+
s =~ /^if\s\(\(@path\s=~\s([^\)]*)\)\|\|\(@path\s=~\s([^\)]*)\)\)/
|
162
|
+
eval("R::#{$1}").should == /\/x/
|
163
|
+
eval("R::#{$2}").should == /\/y/
|
164
|
+
r = R.new(StringIO.new)
|
165
|
+
r.should_respond_to l5.proc_tag
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "Router.condition part" do
|
170
|
+
specify "should compile a condition expression with key and value" do
|
171
|
+
s = R.condition_part(:path, 'abc')
|
172
|
+
s.should_match /\(@path\s=~\s(.*)\)$/
|
173
|
+
s =~ /\(@path\s=~\s(.*)\)$/
|
174
|
+
eval("R::#{$1}").should == /abc/
|
175
|
+
end
|
176
|
+
|
177
|
+
specify "should parse parametrized value and compile it into a lambda" do
|
178
|
+
s = R.condition_part(:t, ':action/:id')
|
179
|
+
(s =~ /^\((.*)\)$/).should_not_be_nil
|
180
|
+
tag = $1
|
181
|
+
r = R.new(StringIO.new)
|
182
|
+
r.should_respond_to tag
|
183
|
+
r.parameters = {}
|
184
|
+
r.t = 'abc'
|
185
|
+
r.send(tag).should_be false
|
186
|
+
r.t = 'show/16'
|
187
|
+
r.send(tag).should_be true
|
188
|
+
r.parameters[:action].should == 'show'
|
189
|
+
r.parameters[:id].should == '16'
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "Router.define_proc" do
|
194
|
+
specify "should convert a lambda into an instance method" do
|
195
|
+
l1 = proc {1 + 1}
|
196
|
+
tag = R.define_proc(&l1)
|
197
|
+
tag.should_be_a_kind_of Symbol
|
198
|
+
tag.should == l1.proc_tag.to_sym
|
199
|
+
r = R.new(StringIO.new)
|
200
|
+
r.should_respond_to(tag)
|
201
|
+
r.send(tag).should == 2
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context "Router.cache_constant" do
|
206
|
+
specify "should cache a value as a constant inside the Router namespace" do
|
207
|
+
c = rand(100000)
|
208
|
+
tag = R.cache_constant(c)
|
209
|
+
tag.should_be_a_kind_of String
|
210
|
+
tag.should == c.const_tag
|
211
|
+
eval("R::#{tag}").should == c
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context "Router.default_route" do
|
216
|
+
specify "should set the default route" do
|
217
|
+
R.default_route {'mau m'}
|
218
|
+
R.new(StringIO.new).default_handler.should == 'mau m'
|
219
|
+
|
220
|
+
R.default_route {654321}
|
221
|
+
R.new(StringIO.new).default_handler.should == 654321
|
222
|
+
end
|
223
|
+
|
224
|
+
specify "should affect the result of routes_defined?" do
|
225
|
+
R.reset
|
226
|
+
R.routes_defined?.should_be_nil
|
227
|
+
R.default_route {654321}
|
228
|
+
R.routes_defined?.should_not_be_nil
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context "Router.unhandled" do
|
233
|
+
specify "should send a 403 response" do
|
234
|
+
r = R.new(StringIO.new)
|
235
|
+
r.unhandled
|
236
|
+
r.socket.rewind
|
237
|
+
resp = r.socket.read
|
238
|
+
resp.should_match /HTTP\/1.1\s403(.*)\r\n/
|
239
|
+
end
|
240
|
+
end
|