halcyon 0.3.18 → 0.3.22

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -75,42 +75,39 @@ task :uninstall => [:clean] do
75
75
  sh %{sudo gem uninstall #{project[:name]}}
76
76
  end
77
77
 
78
- task 'run-spec' do
79
- require 'spec'
80
- $:.unshift(File.dirname(__FILE__))
81
- stdout = []
82
- class << stdout
83
- def print(*e) concat(e); Kernel.print(*e); end
84
- def puts(*e) concat(e); Kernel.puts(*e); end
85
- def flush; end
78
+ namespace 'spec' do
79
+ desc "generate spec"
80
+ task :gen do
81
+ sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/* --format s:spec/SPEC --format h:spec/SPEC.html"
86
82
  end
87
- stderr = []
88
- class << stderr
89
- alias print <<
90
- def print(*e) concat(e); Kernel.print(*e); end
91
- def puts(*e) concat(e); Kernel.puts(*e); end
92
- def flush; end
83
+
84
+ desc "run rspec"
85
+ task :run do
86
+ sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/*"
87
+ end
88
+
89
+ desc "run rspec verbosely"
90
+ task :verb do
91
+ sh "spec -c -rlib/halcyon -rspec/spec_helper spec/**/* --format s"
93
92
  end
94
- ::Spec::Runner::CommandLine.run(['spec'], stderr, stdout, false, true)
95
- exit_status = stdout.last.strip[/(\d+) failures?/, 1].to_i
96
- at_exit{
97
- exit(exit_status == 0 ? 0 : 1)
98
- }
99
- end
100
-
101
- desc "run rspec"
102
- task :spec do
103
- run = Rake::Task['run-spec']
104
- run.execute
105
93
  end
106
94
 
107
- task :default => :spec
108
-
109
95
  desc "Do predistribution stuff"
110
- task :predist => [:chmod, :changelog, :rdoc]
96
+ task :predist => [:chmod, :changelog, :manifest, :rdoc]
111
97
 
112
98
  def manifest
113
- `darcs query manifest`.split("\n").map { |f| f.gsub(/\A\.\//, '') }
99
+ require 'find'
100
+ paths = []
101
+ manifest = File.new('MANIFEST', 'w+')
102
+ Find.find('.') do |path|
103
+ path.gsub!(/\A\.\//, '')
104
+ next if path =~ /(\.svn|doc|pkg|^\.|MANIFEST)/
105
+ paths << path
106
+ end
107
+ paths.sort.each do |path|
108
+ manifest.puts path
109
+ end
110
+ manifest.close
114
111
  end
115
112
 
116
113
  desc "Make binaries executable"
@@ -119,9 +116,14 @@ task :chmod do
119
116
  Dir["test/cgi/test*"].each { |binary| File.chmod(0775, binary) }
120
117
  end
121
118
 
122
- desc "Generate a ChangeLog"
119
+ desc "Generate a MANIFEST"
120
+ task :manifest do
121
+ manifest
122
+ end
123
+
124
+ desc "Generate a CHANGELOG"
123
125
  task :changelog do
124
- sh "svn log >ChangeLog"
126
+ sh "svn log > CHANGELOG"
125
127
  end
126
128
 
127
129
  desc "Generate RDoc documentation"
@@ -146,3 +148,5 @@ desc "find . -name \"*.rb\" | xargs wc -l | grep total"
146
148
  task :loc do
147
149
  sh "find . -name \"*.rb\" | xargs wc -l | grep total"
148
150
  end
151
+
152
+ task :default => Rake::Task['spec:run']
data/bin/halcyon CHANGED
@@ -19,6 +19,7 @@
19
19
  #++
20
20
 
21
21
  $debug = false
22
+ $test = false
22
23
  options = Halcyon::Server::DEFAULT_OPTIONS
23
24
 
24
25
  #--
data/lib/halcyon.rb CHANGED
@@ -21,7 +21,7 @@ end
21
21
  #++
22
22
 
23
23
  module Halcyon
24
- VERSION = [0,3,18]
24
+ VERSION = [0,3,22]
25
25
  def self.version
26
26
  VERSION.join('.')
27
27
  end
@@ -40,7 +40,7 @@ module Halcyon
40
40
  # For documentation on using Halcyon, check out the Halcyon::Server::Base and
41
41
  # Halcyon::Client::Base classes which contain much more usage documentation.
42
42
  def introduction
43
- abort "READ THE DAMNED RDOCS FOO"
43
+ abort "READ THE DAMNED RDOCS!"
44
44
  end
45
45
 
46
46
  end
@@ -221,8 +221,8 @@ module Halcyon
221
221
  # If the server responds with any kind of failure (anything with a status
222
222
  # that isn't 200), Halcyon will in turn raise the respective exception
223
223
  # (defined in Halcyon::Exceptions) which all inherit from
224
- # +Halcyon::Client::Exceptions::Base+. It is up to the client to handle
225
- # these exceptions specifically.
224
+ # +Halcyon::Exceptions::Base+. It is up to the client to handle these
225
+ # exceptions specifically.
226
226
  def request(req)
227
227
  # prepare and send HTTP request
228
228
  res = Net::HTTP.start(@uri.host, @uri.port) {|http|http.request(req)}
@@ -236,7 +236,7 @@ module Halcyon
236
236
 
237
237
  # return response
238
238
  body
239
- rescue Halcyon::Client::Base::Exceptions::Base => e
239
+ rescue Halcyon::Exceptions::Base => e
240
240
  # log exception if logger is in place
241
241
  raise
242
242
  end
@@ -12,18 +12,6 @@ module Halcyon
12
12
  class Base
13
13
  module Exceptions #:nodoc:
14
14
 
15
- #--
16
- # Base Halcyon Exception
17
- #++
18
-
19
- class Base < StandardError #:nodoc:
20
- attr_accessor :status, :error
21
- def initialize(status, error)
22
- @status = status
23
- @error = error
24
- end
25
- end
26
-
27
15
  #--
28
16
  # Exception classes
29
17
  #++
@@ -31,7 +19,7 @@ module Halcyon
31
19
  Halcyon::Exceptions::HTTP_ERROR_CODES.to_a.each do |http_error|
32
20
  status, body = http_error
33
21
  class_eval(
34
- "class #{body.gsub(/( |\-)/,'')} < Base\n"+
22
+ "class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base\n"+
35
23
  " def initialize(s=#{status}, e='#{body}')\n"+
36
24
  " super s, e\n"+
37
25
  " end\n"+
@@ -9,6 +9,23 @@
9
9
 
10
10
  module Halcyon
11
11
  module Exceptions #:nodoc:
12
+
13
+ #--
14
+ # Base Halcyon Exception
15
+ #++
16
+
17
+ class Base < StandardError #:nodoc:
18
+ attr_accessor :status, :error
19
+ def initialize(status, error)
20
+ @status = status
21
+ @error = error
22
+ end
23
+ end
24
+
25
+ #--
26
+ # HTTP Error Codes and Errors
27
+ #++
28
+
12
29
  HTTP_ERROR_CODES = {
13
30
  400 => 'Bad Request',
14
31
  401 => 'Unauthorized',
@@ -219,9 +219,6 @@ module Halcyon
219
219
  # instance
220
220
  @env['halcyon.logger'] = @logger
221
221
 
222
- # set the acceptable remotes to include the remote IP if debugging is enabled
223
- @config[:acceptable_remotes] << @env["REMOTE_ADDR"] if $debug
224
-
225
222
  # pre run hook
226
223
  before_run(Time.now - @time_started) if respond_to? :before_run
227
224
 
@@ -289,6 +286,11 @@ module Halcyon
289
286
  # primarily only speaking with other apps on the same machine, though
290
287
  # your specific requirements may differ and change that.
291
288
  #
289
+ # When in debug mode or in testing mode, the request filtering test is
290
+ # not fired, so all requests from all User Agents and locations will
291
+ # succeed. This is important to know if you plan on testing this specific
292
+ # feature while in debugging or testing modes.
293
+ #
292
294
  # == Hooks, Callbacks, and Authentication
293
295
  #
294
296
  # There is no Authentication mechanism built in to Halcyon (for the time
@@ -310,10 +312,7 @@ module Halcyon
310
312
  # (or one of its inheriters) instead of handling them manually.
311
313
  def run(route)
312
314
  # make sure the request meets our expectations
313
- @config[:acceptable_requests].each do |req|
314
- raise Exceptions::Base.new(req[2], req[3]) unless @env[req[0]] =~ req[1]
315
- end
316
- raise Exceptions::Forbidden.new unless @config[:acceptable_remotes].member? @env["REMOTE_ADDR"]
315
+ acceptable_request! unless $debug || $test
317
316
 
318
317
  # pull params
319
318
  params = route.reject{|key, val| [:action, :module].include? key}
@@ -337,13 +336,21 @@ module Halcyon
337
336
  after_call if respond_to? :after_call
338
337
 
339
338
  res
340
- rescue Exceptions::Base => e
339
+ rescue Halcyon::Exceptions::Base => e
341
340
  @logger.warn "#{uri} => #{e.error}"
342
341
  # handles all content error exceptions
343
342
  @res.status = e.status
344
343
  {:status => e.status, :body => e.error}
345
344
  end
346
345
 
346
+ # Tests for acceptable requests if +$debug+ and +$test+ are not set.
347
+ def acceptable_request!
348
+ @config[:acceptable_requests].each do |req|
349
+ raise Halcyon::Exceptions::Base.new(req[2], req[3]) unless @env[req[0]] =~ req[1]
350
+ end
351
+ raise Exceptions::Forbidden.new unless @config[:acceptable_remotes].member? @env["REMOTE_ADDR"]
352
+ end
353
+
347
354
  #--
348
355
  # Initialization and setup
349
356
  #++
@@ -373,18 +380,20 @@ module Halcyon
373
380
  def initialize(options = {})
374
381
  # save configuration options
375
382
  @config = DEFAULT_OPTIONS.merge(options)
383
+ @config[:app] ||= self.class.to_s.downcase
376
384
 
377
385
  # apply name options to log_file and pid_file configs
378
386
  apply_log_and_pid_file_name_options
379
387
 
380
- # debug mode handling
388
+ # debug and test mode handling
381
389
  enable_debugging if $debug
390
+ enable_testing if $test
382
391
 
383
392
  # setup logging
384
- setup_logging unless $debug
393
+ setup_logging unless $debug || $test
385
394
 
386
395
  # setup request filtering
387
- setup_request_filters unless $debug
396
+ setup_request_filters unless $debug || $test
388
397
 
389
398
  # create PID file
390
399
  @pid = File.new(@config[:pid_file].gsub('{n}', server_cluster_number), "w", 0644)
@@ -397,8 +406,7 @@ module Halcyon
397
406
  # trap signals to die (when killed by the user) gracefully
398
407
  finalize = Proc.new do
399
408
  @logger.info "Shutting down #{$$}."
400
- @logger.close
401
- File.delete(@pid.path)
409
+ clean_up
402
410
  exit
403
411
  end
404
412
  # http://en.wikipedia.org/wiki/Signal_%28computing%29
@@ -414,6 +422,14 @@ module Halcyon
414
422
  end
415
423
  end
416
424
 
425
+ # Closes the logger and deletes the PID file.
426
+ def clean_up
427
+ return if defined? @cleaned_up
428
+ @logger.close
429
+ File.delete(@pid.path) if File.exist?(@pid.path)
430
+ @cleaned_up = true
431
+ end
432
+
417
433
  # Retreives the server cluster sequence number for the PID file.
418
434
  #
419
435
  # This is deprecated and will be removed soon, probably for the 0.4.0
@@ -448,23 +464,41 @@ module Halcyon
448
464
  # debugging.
449
465
  def enable_debugging
450
466
  $debug = true
467
+
468
+ # set the PID file name to /tmp/ unless PID file already exists
469
+ @config[:pid_file] = '/tmp/halcyon.{server}.{app}.{port}.pid' unless defined? @pid
470
+ apply_log_and_pid_file_name_options # reapply for {server}, {app}, and {port} to be set
471
+
451
472
  # setup logger to STDOUT and log entering debugging mode
452
473
  @logger = Logger.new(STDOUT)
453
474
  @logger.progname = "#{self.class}#debug"
454
475
  @logger.level = Logger::DEBUG
455
476
  @logger.formatter = @config[:log_format]
456
477
  @logger.info "Entering debugging mode..."
457
-
478
+ rescue Errno::EACCES
479
+ abort "Can't access #{@config[:pid_file]}, try 'sudo #{$0}'"
480
+ end
481
+
482
+ # This method is used to setup logging and the request handling methods
483
+ # for debugging.
484
+ def enable_testing
458
485
  # set the PID file name to /tmp/ unless PID file already exists
459
- @config[:pid_file] = '/tmp/halcyon.{server}.{app}.{port}.pid' unless @pid.is_a? File
486
+ @config[:pid_file] = '/tmp/halcyon.testing.{app}.{port}.pid' unless defined? @pid
487
+ @config[:log_file] = '/tmp/halcyon.testing.{app}.log'
460
488
  apply_log_and_pid_file_name_options # reapply for {server}, {app}, and {port} to be set
461
489
 
462
- # modify acceptable request's profiles
463
- @config[:acceptable_requests] = [
464
- ["HTTP_USER_AGENT", /.*/, 406, 'Not Acceptable'],
465
- ["HTTP_USER_AGENT", /.*/, 415, 'Unsupported Media Type'] # content type isn't set when navigating via browser
466
- ]
467
- @logger.debug "ACCEPTABLE_REQUESTS modified to accept all User Agents (browsers)"
490
+ # setup logger and log entering testing mode
491
+ @logger = Logger.new(@config[:log_file])
492
+ @logger.progname = "#{self.class}#test"
493
+ @logger.level = Logger::DEBUG
494
+ @logger.formatter = @config[:log_format]
495
+ @logger.info "Entering testing mode..."
496
+
497
+ # make sure we clean up after ourselves since we're in testing mode
498
+ at_exit {
499
+ clean_up
500
+ File.delete(@config[:log_file]) if File.exist?(@config[:log_file])
501
+ }
468
502
  rescue Errno::EACCES
469
503
  abort "Can't access #{@config[:pid_file]}, try 'sudo #{$0}'"
470
504
  end
@@ -669,7 +703,7 @@ module Halcyon
669
703
  def uri
670
704
  # special parsing is done to remove the protocol, host, and port that
671
705
  # some Handlers leave in there. (Fixes inconsistencies.)
672
- URI.parse(@env['REQUEST_URI']).path
706
+ URI.parse(@env['REQUEST_URI'] || @env['PATH_INFO']).path
673
707
  end
674
708
 
675
709
  # Returns the Request Method as a lowercase symbol
@@ -12,18 +12,6 @@ module Halcyon
12
12
  class Base
13
13
  module Exceptions #:nodoc:
14
14
 
15
- #--
16
- # Base Halcyon Exception
17
- #++
18
-
19
- class Base < StandardError #:nodoc:
20
- attr_accessor :status, :error
21
- def initialize(status, error)
22
- @status = status
23
- @error = error
24
- end
25
- end
26
-
27
15
  #--
28
16
  # Exception classes
29
17
  #++
@@ -31,7 +19,7 @@ module Halcyon
31
19
  Halcyon::Exceptions::HTTP_ERROR_CODES.to_a.each do |http_error|
32
20
  status, body = http_error
33
21
  class_eval(
34
- "class #{body.gsub(/( |\-)/,'')} < Base\n"+
22
+ "class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base\n"+
35
23
  " def initialize(s=#{status}, e='#{body}')\n"+
36
24
  " super s, e\n"+
37
25
  " end\n"+
@@ -76,7 +76,8 @@ module Halcyon
76
76
  # default route if no match is made.
77
77
  def self.route(env)
78
78
  # pull out the path requested (WEBrick keeps the host and port and protocol in REQUEST_URI)
79
- uri = URI.parse(env['REQUEST_URI']).path
79
+ # PATH_INFO is failover if REQUEST_URI is blank (like what Rack::MockRequest does)
80
+ uri = URI.parse(env['REQUEST_URI'] || env['PATH_INFO']).path
80
81
 
81
82
  # prepare request
82
83
  path = (uri ? uri.split('?').first : '').sub(/\/+/, '/')
@@ -89,7 +90,7 @@ module Halcyon
89
90
  # make sure a route is returned even if no match is found
90
91
  if route[0].nil?
91
92
  #return default route
92
- env['halcyon.logger'].debug "No route found. Using default."
93
+ env['halcyon.logger'].debug "No route found. Using default." if env['halcyon.logger'].is_a? Logger
93
94
  @@default_route
94
95
  else
95
96
  # params (including action and module if set) for the matching route
data/spec/SPEC ADDED
@@ -0,0 +1,29 @@
1
+
2
+ Halcyon::Server Errors
3
+ - should provide shorthand methods for errors which should throw an appropriate exception
4
+ - supports numerous standard HTTP request error exceptions with lookup by status code
5
+ - should have a short inheritence chain to make catching generically simple
6
+
7
+ Halcyon::Server::Router
8
+ - should prepares routes correctly when written correctly
9
+ - should match URIs to the correct route
10
+ - should use the default route if no matching route is found
11
+ - should map params in routes to parameters
12
+ - should supply arbitrary routing param values included as a param even if not in the URI
13
+
14
+ Halcyon::Server
15
+ - should dispatch methods according to their respective routes
16
+ - should provide various shorthand methods for simple responses but take custom response values
17
+ - should handle requests and respond with JSON
18
+ - should handle requests with param values in the URL
19
+ - should route unmatchable requests to the default route and return JSON with appropriate status
20
+ - should log activity
21
+ - should create a PID file while running with the correct process ID
22
+ - should parse URI query params correctly
23
+ - should parse the URI correctly
24
+ - should provide a quick way to find out what method the request was performed using
25
+ - should deny all unacceptable requests
26
+
27
+ Finished in 0.211129 seconds
28
+
29
+ 19 examples, 0 failures
data/spec/SPEC.html ADDED
@@ -0,0 +1,237 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
5
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
6
+ <head>
7
+ <title>RSpec results</title>
8
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
9
+ <meta http-equiv="Expires" content="-1" />
10
+ <meta http-equiv="Pragma" content="no-cache" />
11
+ <style type="text/css">
12
+ body {
13
+ margin: 0;
14
+ padding: 0;
15
+ background: #fff;
16
+ font-size: 80%;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <div class="rspec-report">
22
+ <script type="text/javascript">
23
+ // <![CDATA[
24
+ function moveProgressBar(percentDone) {
25
+ document.getElementById("rspec-header").style.width = percentDone +"%";
26
+ }
27
+ function makeRed(element_id) {
28
+ document.getElementById(element_id).style.background = '#C40D0D';
29
+ document.getElementById(element_id).style.color = '#FFFFFF';
30
+ }
31
+
32
+ function makeYellow(element_id) {
33
+ if (element_id == "rspec-header" && document.getElementById(element_id).style.background != '#C40D0D')
34
+ {
35
+ document.getElementById(element_id).style.background = '#FAF834';
36
+ document.getElementById(element_id).style.color = '#000000';
37
+ }
38
+ else
39
+ {
40
+ document.getElementById(element_id).style.background = '#FAF834';
41
+ document.getElementById(element_id).style.color = '#000000';
42
+ }
43
+ }
44
+
45
+ // ]]>
46
+ </script>
47
+ <style type="text/css">
48
+ #rspec-header {
49
+ background: #65C400; color: #fff;
50
+ }
51
+
52
+ .rspec-report h1 {
53
+ margin: 0px 10px 0px 10px;
54
+ padding: 10px;
55
+ font-family: "Lucida Grande", Helvetica, sans-serif;
56
+ font-size: 1.8em;
57
+ }
58
+
59
+ #summary {
60
+ margin: 0; padding: 5px 10px;
61
+ font-family: "Lucida Grande", Helvetica, sans-serif;
62
+ text-align: right;
63
+ position: absolute;
64
+ top: 0px;
65
+ right: 0px;
66
+ }
67
+
68
+ #summary p {
69
+ margin: 0 0 0 2px;
70
+ }
71
+
72
+ #summary #totals {
73
+ font-size: 1.2em;
74
+ }
75
+
76
+ .example_group {
77
+ margin: 0 10px 5px;
78
+ background: #fff;
79
+ }
80
+
81
+ dl {
82
+ margin: 0; padding: 0 0 5px;
83
+ font: normal 11px "Lucida Grande", Helvetica, sans-serif;
84
+ }
85
+
86
+ dt {
87
+ padding: 3px;
88
+ background: #65C400;
89
+ color: #fff;
90
+ font-weight: bold;
91
+ }
92
+
93
+ dd {
94
+ margin: 5px 0 5px 5px;
95
+ padding: 3px 3px 3px 18px;
96
+ }
97
+
98
+ dd.spec.passed {
99
+ border-left: 5px solid #65C400;
100
+ border-bottom: 1px solid #65C400;
101
+ background: #DBFFB4; color: #3D7700;
102
+ }
103
+
104
+ dd.spec.failed {
105
+ border-left: 5px solid #C20000;
106
+ border-bottom: 1px solid #C20000;
107
+ color: #C20000; background: #FFFBD3;
108
+ }
109
+
110
+ dd.spec.not_implemented {
111
+ border-left: 5px solid #FAF834;
112
+ border-bottom: 1px solid #FAF834;
113
+ background: #FCFB98; color: #131313;
114
+ }
115
+
116
+ dd.spec.pending_fixed {
117
+ border-left: 5px solid #0000C2;
118
+ border-bottom: 1px solid #0000C2;
119
+ color: #0000C2; background: #D3FBFF;
120
+ }
121
+
122
+ .backtrace {
123
+ color: #000;
124
+ font-size: 12px;
125
+ }
126
+
127
+ a {
128
+ color: #BE5C00;
129
+ }
130
+
131
+ /* Ruby code, style similar to vibrant ink */
132
+ .ruby {
133
+ font-size: 12px;
134
+ font-family: monospace;
135
+ color: white;
136
+ background-color: black;
137
+ padding: 0.1em 0 0.2em 0;
138
+ }
139
+
140
+ .ruby .keyword { color: #FF6600; }
141
+ .ruby .constant { color: #339999; }
142
+ .ruby .attribute { color: white; }
143
+ .ruby .global { color: white; }
144
+ .ruby .module { color: white; }
145
+ .ruby .class { color: white; }
146
+ .ruby .string { color: #66FF00; }
147
+ .ruby .ident { color: white; }
148
+ .ruby .method { color: #FFCC00; }
149
+ .ruby .number { color: white; }
150
+ .ruby .char { color: white; }
151
+ .ruby .comment { color: #9933CC; }
152
+ .ruby .symbol { color: white; }
153
+ .ruby .regex { color: #44B4CC; }
154
+ .ruby .punct { color: white; }
155
+ .ruby .escape { color: white; }
156
+ .ruby .interp { color: white; }
157
+ .ruby .expr { color: white; }
158
+
159
+ .ruby .offending { background-color: gray; }
160
+ .ruby .linenum {
161
+ width: 75px;
162
+ padding: 0.1em 1em 0.2em 0;
163
+ color: #000000;
164
+ background-color: #FFFBD3;
165
+ }
166
+
167
+ </style>
168
+
169
+ <div id="rspec-header">
170
+ <h1>RSpec Results</h1>
171
+
172
+ <div id="summary">
173
+ <p id="totals">&nbsp;</p>
174
+ <p id="duration">&nbsp;</p>
175
+ </div>
176
+ </div>
177
+
178
+ <div class="results">
179
+ <div class="example_group">
180
+ <dl>
181
+ <dt id="example_group_1">Halcyon::Server Errors</dt>
182
+ <script type="text/javascript">moveProgressBar('5.2');</script>
183
+ <dd class="spec passed"><span class="passed_spec_name">should provide shorthand methods for errors which should throw an appropriate exception</span></dd>
184
+ <script type="text/javascript">moveProgressBar('10.5');</script>
185
+ <dd class="spec passed"><span class="passed_spec_name">supports numerous standard HTTP request error exceptions with lookup by status code</span></dd>
186
+ <script type="text/javascript">moveProgressBar('15.7');</script>
187
+ <dd class="spec passed"><span class="passed_spec_name">should have a short inheritence chain to make catching generically simple</span></dd>
188
+ </dl>
189
+ </div>
190
+ <div class="example_group">
191
+ <dl>
192
+ <dt id="example_group_2">Halcyon::Server::Router</dt>
193
+ <script type="text/javascript">moveProgressBar('21.0');</script>
194
+ <dd class="spec passed"><span class="passed_spec_name">should prepares routes correctly when written correctly</span></dd>
195
+ <script type="text/javascript">moveProgressBar('26.3');</script>
196
+ <dd class="spec passed"><span class="passed_spec_name">should match URIs to the correct route</span></dd>
197
+ <script type="text/javascript">moveProgressBar('31.5');</script>
198
+ <dd class="spec passed"><span class="passed_spec_name">should use the default route if no matching route is found</span></dd>
199
+ <script type="text/javascript">moveProgressBar('36.8');</script>
200
+ <dd class="spec passed"><span class="passed_spec_name">should map params in routes to parameters</span></dd>
201
+ <script type="text/javascript">moveProgressBar('42.1');</script>
202
+ <dd class="spec passed"><span class="passed_spec_name">should supply arbitrary routing param values included as a param even if not in the URI</span></dd>
203
+ </dl>
204
+ </div>
205
+ <div class="example_group">
206
+ <dl>
207
+ <dt id="example_group_3">Halcyon::Server</dt>
208
+ <script type="text/javascript">moveProgressBar('47.3');</script>
209
+ <dd class="spec passed"><span class="passed_spec_name">should dispatch methods according to their respective routes</span></dd>
210
+ <script type="text/javascript">moveProgressBar('52.6');</script>
211
+ <dd class="spec passed"><span class="passed_spec_name">should provide various shorthand methods for simple responses but take custom response values</span></dd>
212
+ <script type="text/javascript">moveProgressBar('57.8');</script>
213
+ <dd class="spec passed"><span class="passed_spec_name">should handle requests and respond with JSON</span></dd>
214
+ <script type="text/javascript">moveProgressBar('63.1');</script>
215
+ <dd class="spec passed"><span class="passed_spec_name">should handle requests with param values in the URL</span></dd>
216
+ <script type="text/javascript">moveProgressBar('68.4');</script>
217
+ <dd class="spec passed"><span class="passed_spec_name">should route unmatchable requests to the default route and return JSON with appropriate status</span></dd>
218
+ <script type="text/javascript">moveProgressBar('73.6');</script>
219
+ <dd class="spec passed"><span class="passed_spec_name">should log activity</span></dd>
220
+ <script type="text/javascript">moveProgressBar('78.9');</script>
221
+ <dd class="spec passed"><span class="passed_spec_name">should create a PID file while running with the correct process ID</span></dd>
222
+ <script type="text/javascript">moveProgressBar('84.2');</script>
223
+ <dd class="spec passed"><span class="passed_spec_name">should parse URI query params correctly</span></dd>
224
+ <script type="text/javascript">moveProgressBar('89.4');</script>
225
+ <dd class="spec passed"><span class="passed_spec_name">should parse the URI correctly</span></dd>
226
+ <script type="text/javascript">moveProgressBar('94.7');</script>
227
+ <dd class="spec passed"><span class="passed_spec_name">should provide a quick way to find out what method the request was performed using</span></dd>
228
+ <script type="text/javascript">moveProgressBar('100.0');</script>
229
+ <dd class="spec passed"><span class="passed_spec_name">should deny all unacceptable requests</span></dd>
230
+ </dl>
231
+ </div>
232
+ <script type="text/javascript">document.getElementById('duration').innerHTML = "Finished in <strong>0.211129 seconds</strong>";</script>
233
+ <script type="text/javascript">document.getElementById('totals').innerHTML = "19 examples, 0 failures";</script>
234
+ </div>
235
+ </div>
236
+ </body>
237
+ </html>
@@ -0,0 +1,55 @@
1
+ context "Halcyon::Server Errors" do
2
+
3
+ before(:each) do
4
+ @app = Specr.new :port => 4000
5
+ end
6
+
7
+ specify "should provide shorthand methods for errors which should throw an appropriate exception" do
8
+ begin
9
+ @app.not_found
10
+ rescue Halcyon::Exceptions::Base => e
11
+ e.status.should == 404
12
+ e.error.should == 'Not Found'
13
+ end
14
+
15
+ begin
16
+ @app.not_found('Missing')
17
+ rescue Halcyon::Exceptions::Base => e
18
+ e.status.should == 404
19
+ e.error.should == 'Missing'
20
+ end
21
+ end
22
+
23
+ specify "supports numerous standard HTTP request error exceptions with lookup by status code" do
24
+ begin
25
+ Halcyon::Server::Base::Exceptions::NotFound.new
26
+ rescue Halcyon::Exceptions::Base => e
27
+ e.status.should == 404
28
+ e.error.should == 'Not Found'
29
+ end
30
+
31
+ Halcyon::Exceptions::HTTP_ERROR_CODES.each do |code, error|
32
+ begin
33
+ Halcyon::Server::Base::Exceptions.const_get(error.gsub(/( |\-)/,'')).new
34
+ rescue Halcyon::Exceptions::Base => e
35
+ e.status.should == code
36
+ e.error.should == error
37
+ end
38
+ begin
39
+ Halcyon::Server::Base::Exceptions.lookup(code).new
40
+ rescue Halcyon::Exceptions::Base => e
41
+ e.status.should == code
42
+ e.error.should == error
43
+ end
44
+ end
45
+ end
46
+
47
+ specify "should have a short inheritence chain to make catching generically simple" do
48
+ begin
49
+ Halcyon::Server::Base::Exceptions::NotFound.new
50
+ rescue Halcon::Exceptions::Base => e
51
+ e.class.to_s.should == 'NotFound'
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,32 @@
1
+ context "Halcyon::Server::Router" do
2
+
3
+ before(:each) do
4
+ @app = Specr.new :port => 4000
5
+ end
6
+
7
+ specify "should prepares routes correctly when written correctly" do
8
+ # routes have been defined for Specr
9
+ Halcyon::Server::Router.routes.should_not == []
10
+ Halcyon::Server::Router.routes.length.should > 0
11
+ end
12
+
13
+ specify "should match URIs to the correct route" do
14
+ Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:action].should == 'index'
15
+ end
16
+
17
+ specify "should use the default route if no matching route is found" do
18
+ Halcyon::Server::Router.route(Rack::MockRequest.env_for('/erroneous/path'))[:action].should == 'not_found'
19
+ Halcyon::Server::Router.route(Rack::MockRequest.env_for("/random/#{rand}"))[:action].should == 'not_found'
20
+ end
21
+
22
+ specify "should map params in routes to parameters" do
23
+ response = Halcyon::Server::Router.route(Rack::MockRequest.env_for('/hello/Matt'))
24
+ response[:action].should == 'greeter'
25
+ response[:name].should == 'Matt'
26
+ end
27
+
28
+ specify "should supply arbitrary routing param values included as a param even if not in the URI" do
29
+ Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:arbitrary].should == 'random'
30
+ end
31
+
32
+ end
@@ -0,0 +1,93 @@
1
+ context "Halcyon::Server" do
2
+
3
+ before(:each) do
4
+ @app = Specr.new :port => 4000
5
+ end
6
+
7
+ specify "should dispatch methods according to their respective routes" do
8
+ Rack::MockRequest.new(@app).get("/hello/Matt")
9
+ last_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
10
+ last_line.should =~ /INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specr#test :: \[200\] .* => greeter \(.+\)/
11
+ end
12
+
13
+ specify "should provide various shorthand methods for simple responses but take custom response values" do
14
+ response = {:status => 200, :body => 'OK'}
15
+ @app.ok.should == response
16
+ @app.success.should == response
17
+ @app.standard_response.should == response
18
+
19
+ @app.ok('').should == {:status => 200, :body => ''}
20
+ @app.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto']}
21
+ end
22
+
23
+ specify "should handle requests and respond with JSON" do
24
+ body = JSON.parse(Rack::MockRequest.new(@app).get("/").body)
25
+ body['status'].should == 200
26
+ body['body'].should == "Found"
27
+ end
28
+
29
+ specify "should handle requests with param values in the URL" do
30
+ body = JSON.parse(Rack::MockRequest.new(@app).get("/hello/Matt").body)
31
+ body['status'].should == 200
32
+ body['body'].should == "Hello Matt"
33
+ end
34
+
35
+ specify "should route unmatchable requests to the default route and return JSON with appropriate status" do
36
+ body = JSON.parse(Rack::MockRequest.new(@app).get("/garbage/request/url").body)
37
+ body['status'].should == 404
38
+ body['body'].should == "Not Found"
39
+ end
40
+
41
+ specify "should log activity" do
42
+ prev_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
43
+ Rack::MockRequest.new(@app).get("/url/that/will/not/be/found/#{rand}")
44
+ last_line = File.new(@app.instance_variable_get("@config")[:log_file]).readlines.last
45
+ last_line.should =~ /INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specr#test :: \[404\] .* => not_found \(.+\)/
46
+ prev_line.should_not == last_line
47
+ end
48
+
49
+ specify "should create a PID file while running with the correct process ID" do
50
+ pid_file = @app.instance_variable_get("@config")[:pid_file]
51
+ File.exist?(pid_file).should be_true
52
+ File.open(pid_file){|file|file.read.should == "#{$$}\n"}
53
+ end
54
+
55
+ specify "should parse URI query params correctly" do
56
+ Rack::MockRequest.new(@app).get("/?query=value&lang=en-US")
57
+ @app.query_params.should == {'query' => 'value', 'lang' => 'en-US'}
58
+ end
59
+
60
+ specify "should parse the URI correctly" do
61
+ Rack::MockRequest.new(@app).get("http://localhost:4000/slaughterhouse/5")
62
+ @app.uri.should == '/slaughterhouse/5'
63
+
64
+ Rack::MockRequest.new(@app).get("/slaughterhouse/5")
65
+ @app.uri.should == '/slaughterhouse/5'
66
+
67
+ Rack::MockRequest.new(@app).get("")
68
+ @app.uri.should == '/'
69
+ end
70
+
71
+ specify "should provide a quick way to find out what method the request was performed using" do
72
+ Rack::MockRequest.new(@app).get("/#{rand}")
73
+ @app.method.should == :get
74
+
75
+ Rack::MockRequest.new(@app).post("/#{rand}")
76
+ @app.method.should == :post
77
+
78
+ Rack::MockRequest.new(@app).put("/#{rand}")
79
+ @app.method.should == :put
80
+
81
+ Rack::MockRequest.new(@app).delete("/#{rand}")
82
+ @app.method.should == :delete
83
+ end
84
+
85
+ specify "should deny all unacceptable requests" do
86
+ conf = @app.instance_variable_get("@config")
87
+ conf[:acceptable_requests] = Halcyon::Server::ACCEPTABLE_REQUESTS
88
+
89
+ Rack::MockRequest.new(@app).put("/#{rand}")
90
+ @app.acceptable_request! rescue Halcyon::Exceptions::Base
91
+ end
92
+
93
+ end
@@ -0,0 +1,21 @@
1
+ require 'halcyon/server'
2
+ require 'rack/mock'
3
+
4
+ $test = true
5
+
6
+ class Specr < Halcyon::Server::Base
7
+
8
+ route do |r|
9
+ r.match('/hello/:name').to(:action => 'greeter')
10
+ r.match('/').to(:action => 'index', :arbitrary => 'random')
11
+ end
12
+
13
+ def index(params)
14
+ ok('Found')
15
+ end
16
+
17
+ def greeter(params)
18
+ ok("Hello #{params[:name]}")
19
+ end
20
+
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: halcyon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.18
4
+ version: 0.3.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Todd
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2007-12-31 00:00:00 -05:00
12
+ date: 2008-01-07 00:00:00 -05:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -50,6 +50,13 @@ extra_rdoc_files:
50
50
  files:
51
51
  - lib
52
52
  - Rakefile
53
+ - spec/halcyon
54
+ - spec/halcyon/error_spec.rb
55
+ - spec/halcyon/router_spec.rb
56
+ - spec/halcyon/server_spec.rb
57
+ - spec/SPEC
58
+ - spec/SPEC.html
59
+ - spec/spec_helper.rb
53
60
  - lib/halcyon
54
61
  - lib/halcyon/client
55
62
  - lib/halcyon/client/base.rb