serverside 0.2.9 → 0.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.
- 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
|