serverside 0.2.9 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +56 -0
  2. data/Rakefile +12 -52
  3. data/bin/serverside +1 -1
  4. data/lib/serverside/application.rb +2 -1
  5. data/lib/serverside/caching.rb +62 -50
  6. data/lib/serverside/controllers.rb +91 -0
  7. data/lib/serverside/core_ext.rb +6 -0
  8. data/lib/serverside/daemon.rb +25 -5
  9. data/lib/serverside/request.rb +17 -11
  10. data/lib/serverside/routing.rb +11 -10
  11. data/lib/serverside/server.rb +14 -6
  12. data/lib/serverside/static.rb +7 -18
  13. data/lib/serverside/template.rb +20 -12
  14. data/spec/caching_spec.rb +318 -0
  15. data/spec/cluster_spec.rb +140 -0
  16. data/{test/spec → spec}/connection_spec.rb +4 -4
  17. data/{test/spec/controller_spec.rb → spec/controllers_spec.rb} +15 -12
  18. data/{test/spec → spec}/core_ext_spec.rb +23 -18
  19. data/spec/daemon_spec.rb +99 -0
  20. data/{test/spec → spec}/request_spec.rb +45 -45
  21. data/spec/routing_spec.rb +240 -0
  22. data/spec/server_spec.rb +40 -0
  23. data/spec/static_spec.rb +279 -0
  24. data/spec/template_spec.rb +129 -0
  25. metadata +21 -35
  26. data/lib/serverside/controller.rb +0 -67
  27. data/test/functional/primitive_static_server_test.rb +0 -61
  28. data/test/functional/request_body_test.rb +0 -93
  29. data/test/functional/routing_server.rb +0 -14
  30. data/test/functional/routing_server_test.rb +0 -41
  31. data/test/functional/static_profile.rb +0 -17
  32. data/test/functional/static_rfuzz.rb +0 -31
  33. data/test/functional/static_server.rb +0 -7
  34. data/test/functional/static_server_test.rb +0 -31
  35. data/test/spec/caching_spec.rb +0 -139
  36. data/test/test_helper.rb +0 -2
  37. data/test/unit/cluster_test.rb +0 -129
  38. data/test/unit/connection_test.rb +0 -48
  39. data/test/unit/core_ext_test.rb +0 -46
  40. data/test/unit/daemon_test.rb +0 -75
  41. data/test/unit/request_test.rb +0 -278
  42. data/test/unit/routing_test.rb +0 -171
  43. data/test/unit/server_test.rb +0 -28
  44. data/test/unit/static_test.rb +0 -171
  45. data/test/unit/template_test.rb +0 -78
@@ -1,24 +1,29 @@
1
- require File.join(File.dirname(__FILE__), '../../lib/serverside')
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.should_equal 'a+b+c'
8
- 'a/b#1@6%8K'.uri_escape.should_equal 'a%2Fb%231%406%258K'
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.should_equal 'a b c'
13
- 'a%2Fb%231%406%258K'.uri_unescape.should_equal 'a/b#1@6%8K'
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.should_equal s
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').should_equal 'abc/def'
20
- ('/hello/'/'there').should_equal '/hello/there'
21
- ('touch'/'/me/'/'hold'/'/me').should_equal '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.should_equal 'abc_def'
38
+ :abc_def.to_s.should == 'abc_def'
34
39
  :def_ghi.to_s.should_be_instance_of String
35
- :ghi_jkl.to_s.should_equal :ghi_jkl.id2name
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.should_equal :kwantz_mit_krantz.id2name
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.should_equal @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.should_equal 'proc_' + @l1.object_id.to_s(36).sub('-', '_')
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.should_equal @o1.const_tag
80
- @o2.const_tag.should_equal @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.should_equal 'C' + @o1.object_id.to_s(36).upcase.sub('-', '_')
85
- @o2.const_tag.should_equal 'C' + @o2.object_id.to_s(36).upcase.sub('-', '_')
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
@@ -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__), '../../lib/serverside')
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.should_equal [:parse]
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.should_equal [:parse]
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.should_equal [:parse, :respond]
35
+ r.calls.should == [:parse, :respond]
36
36
 
37
37
  r.calls = []
38
38
  r.persistent = 'mau'
39
- r.process.should_equal 'mau'
40
- r.calls.should_equal [:parse, :respond]
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.should_equal :post
73
- r.path.should_equal '/test'
72
+ r.method.should == :post
73
+ r.path.should == '/test'
74
74
  r.query.should_be_nil
75
- r.version.should_equal '1.1'
76
- r.parameters.should_equal({})
77
- r.headers.should_equal({'Content-Type' => 'text/html'})
78
- r.cookies.should_equal({})
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.should_equal true
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.should_equal '/test/asdf/qw'
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.should_equal '/test/asdf/qw'
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.should_equal 2
101
- r.parameters[:time].should_equal '24 hours'
102
- r.parameters[:q].should_equal 'node_history'
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.should_equal '1.0'
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.should_equal '3.2'
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.should_equal false
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.should_equal true
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.should_equal false
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.should_equal false
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.should_equal true
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'].should_equal 'abc=1342; def=7%2f4'
142
- r.cookies.size.should_equal 2
143
- r.cookies[:abc].should_equal '1342'
144
- r.cookies[:def].should_equal '7/4'
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.should_equal 2
153
- r.parameters[:q].should_equal 'node_history'
154
- r.parameters[:time].should_equal '24 hours'
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.should_equal "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!"
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.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nContent-Type: text\r\nContent-Length: 12\r\n\r\nHello there!"
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.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\n"
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.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\n\r\nhey there"
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.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nXXX: Test\r\n\r\n"
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.should_equal "HTTP/1.1 200\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nContent-Type: text\r\nYYY: TTT\r\n\r\n"
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.should_equal false
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.should_equal "Set-Cookie: session=ABCDEFG; path=/; expires=#{t.rfc2822}\r\n"
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.should_equal "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!"
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.should_equal "Set-Cookie: session=; path=/; expires=#{Time.at(0).rfc2822}\r\n"
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.should_equal "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!"
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.should_equal "HTTP/1.1 302\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
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.should_equal "HTTP/1.1 301\r\nDate: #{Time.now.httpdate}\r\nConnection: close\r\nLocation: http://mau.com/132\r\n\r\n"
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