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.
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