halcyon 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. data/AUTHORS +1 -0
  2. data/LICENSE +20 -0
  3. data/README +107 -0
  4. data/Rakefile +8 -6
  5. data/bin/halcyon +3 -204
  6. data/lib/halcyon.rb +55 -42
  7. data/lib/halcyon/application.rb +247 -0
  8. data/lib/halcyon/application/router.rb +86 -0
  9. data/lib/halcyon/client.rb +187 -35
  10. data/lib/halcyon/client/ssl.rb +38 -0
  11. data/lib/halcyon/controller.rb +154 -0
  12. data/lib/halcyon/exceptions.rb +67 -59
  13. data/lib/halcyon/logging.rb +31 -0
  14. data/lib/halcyon/logging/analogger.rb +31 -0
  15. data/lib/halcyon/logging/helpers.rb +37 -0
  16. data/lib/halcyon/logging/log4r.rb +25 -0
  17. data/lib/halcyon/logging/logger.rb +20 -0
  18. data/lib/halcyon/logging/logging.rb +19 -0
  19. data/lib/halcyon/runner.rb +141 -0
  20. data/lib/halcyon/runner/commands.rb +141 -0
  21. data/lib/halcyon/runner/helpers.rb +9 -0
  22. data/lib/halcyon/runner/helpers/command_helper.rb +71 -0
  23. data/spec/halcyon/application_spec.rb +70 -0
  24. data/spec/halcyon/client_spec.rb +63 -0
  25. data/spec/halcyon/controller_spec.rb +68 -0
  26. data/spec/halcyon/halcyon_spec.rb +63 -0
  27. data/spec/halcyon/logging_spec.rb +31 -0
  28. data/spec/halcyon/router_spec.rb +37 -12
  29. data/spec/halcyon/runner_spec.rb +54 -0
  30. data/spec/spec_helper.rb +75 -9
  31. data/support/generators/halcyon/USAGE +0 -0
  32. data/support/generators/halcyon/halcyon_generator.rb +52 -0
  33. data/support/generators/halcyon/templates/README +26 -0
  34. data/support/generators/halcyon/templates/Rakefile +32 -0
  35. data/support/generators/halcyon/templates/app/application.rb +43 -0
  36. data/support/generators/halcyon/templates/config/config.yml +36 -0
  37. data/support/generators/halcyon/templates/config/init/environment.rb +11 -0
  38. data/support/generators/halcyon/templates/config/init/hooks.rb +39 -0
  39. data/support/generators/halcyon/templates/config/init/requires.rb +10 -0
  40. data/support/generators/halcyon/templates/config/init/routes.rb +50 -0
  41. data/support/generators/halcyon/templates/lib/client.rb +77 -0
  42. data/support/generators/halcyon/templates/runner.ru +8 -0
  43. data/support/generators/halcyon_flat/USAGE +0 -0
  44. data/support/generators/halcyon_flat/halcyon_flat_generator.rb +52 -0
  45. data/support/generators/halcyon_flat/templates/README +26 -0
  46. data/support/generators/halcyon_flat/templates/Rakefile +32 -0
  47. data/support/generators/halcyon_flat/templates/app.rb +49 -0
  48. data/support/generators/halcyon_flat/templates/lib/client.rb +17 -0
  49. data/support/generators/halcyon_flat/templates/runner.ru +8 -0
  50. metadata +73 -20
  51. data/lib/halcyon/client/base.rb +0 -261
  52. data/lib/halcyon/client/exceptions.rb +0 -41
  53. data/lib/halcyon/client/router.rb +0 -106
  54. data/lib/halcyon/server.rb +0 -62
  55. data/lib/halcyon/server/auth/basic.rb +0 -107
  56. data/lib/halcyon/server/base.rb +0 -774
  57. data/lib/halcyon/server/exceptions.rb +0 -41
  58. data/lib/halcyon/server/router.rb +0 -103
  59. data/spec/halcyon/error_spec.rb +0 -55
  60. data/spec/halcyon/server_spec.rb +0 -105
@@ -0,0 +1,63 @@
1
+ #--
2
+ # Start App for Tests
3
+ # and wait for it to be responsive
4
+ #++
5
+
6
+ fork do
7
+ dir = Halcyon.root/'support'/'generators'/'halcyon'/'templates'
8
+ command = "thin start -R runner.ru -p 89981 -c #{dir} > /dev/null 2>&1"
9
+ STDOUT.close
10
+ STDERR.close
11
+ exec command
12
+ end
13
+ client = Halcyon::Client.new('http://localhost:89981')
14
+ begin
15
+ sleep 1.5
16
+ client.get('/time')
17
+ rescue Errno::ECONNREFUSED => e
18
+ retry
19
+ end
20
+
21
+ #--
22
+ # Cleanup
23
+ #++
24
+
25
+ at_exit do
26
+ pids = (`ps auxww | grep support/generators/halcyon/templates`).split("\n").collect{|pid|pid.match(/(\w+)\s+(\w+).+/)[2]}
27
+ pids.each {|pid| Process.kill(9, pid.to_i) rescue nil }
28
+ end
29
+
30
+ #--
31
+ # Tests
32
+ #++
33
+
34
+ describe "Halcyon::Client" do
35
+
36
+ before do
37
+ @client = Halcyon::Client.new('http://localhost:89981')
38
+ end
39
+
40
+ it "should perform requests and return the response values" do
41
+ response = @client.get('/time')[:body]
42
+ response.length.should > 25
43
+ response.include?(Time.now.year.to_s).should.be.true?
44
+ end
45
+
46
+ it "should be able to perform get, post, put, and delete requests" do
47
+ @client.get('/time')[:body].length.should > 25
48
+ @client.post('/time')[:body].length.should > 20
49
+ @client.put('/time')[:body].should == "Not Implemented"
50
+ @client.delete('/time')[:status].should == 501
51
+ end
52
+
53
+ it "should throw exceptions unless an OK response is sent if toggled to" do
54
+ # default behavior is to not raise exceptions
55
+ @client.get('/nonexistent/route')[:status].should == 404
56
+
57
+ # tell it to raise exceptions
58
+ @client.raise_exceptions! true
59
+ should.raise(Halcyon::Exceptions::NotFound) { @client.get('/nonexistent/route') }
60
+ @client.get('/time')[:status].should == 200
61
+ end
62
+
63
+ end
@@ -0,0 +1,68 @@
1
+ describe "Halcyon::Controller" do
2
+
3
+ before do
4
+ @log = ''
5
+ @logger = Logger.new(StringIO.new(@log))
6
+ @config = $config.dup
7
+ @config[:logger] = @logger
8
+ @config[:app] = 'Specs'
9
+ Halcyon.config = @config
10
+ @app = Halcyon::Runner.new
11
+ end
12
+
13
+ it "should provide various shorthand methods for simple responses but take custom response values" do
14
+ controller = Specs.new(Rack::MockRequest.env_for('/'))
15
+
16
+ response = {:status => 200, :body => 'OK'}
17
+ controller.ok.should == response
18
+ controller.success.should == response
19
+
20
+ controller.ok('').should == {:status => 200, :body => ''}
21
+ controller.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto']}
22
+ end
23
+
24
+ it "should provide a quick way to find out what method the request was performed using" do
25
+ %w(GET POST PUT DELETE).each do |m|
26
+ controller = Specs.new(Rack::MockRequest.env_for('/', :method => m))
27
+ controller.method.should == m.downcase.to_sym
28
+ end
29
+ end
30
+
31
+ it "should provide convenient access to GET and POST data" do
32
+ controller = Specs.new(Rack::MockRequest.env_for("/#{rand}?foo=bar"))
33
+ controller.get[:foo].should == 'bar'
34
+
35
+ controller = Specs.new(Rack::MockRequest.env_for("/#{rand}", :method => 'POST', :input => {:foo => 'bar'}.to_params))
36
+ controller.post[:foo].should == 'bar'
37
+ end
38
+
39
+ it "should parse URI query params correctly" do
40
+ controller = Specs.new(Rack::MockRequest.env_for("/?query=value&lang=en-US"))
41
+ controller.get[:query].should == 'value'
42
+ controller.get[:lang].should == 'en-US'
43
+ end
44
+
45
+ it "should parse the URI correctly" do
46
+ controller = Specs.new(Rack::MockRequest.env_for("http://localhost:4000/slaughterhouse/5"))
47
+ controller.uri.should == '/slaughterhouse/5'
48
+
49
+ controller = Specs.new(Rack::MockRequest.env_for("/slaughterhouse/5"))
50
+ controller.uri.should == '/slaughterhouse/5'
51
+
52
+ controller = Specs.new(Rack::MockRequest.env_for(""))
53
+ controller.uri.should == '/'
54
+ end
55
+
56
+ it 'should provide url accessor for resource index route' do
57
+ controller = Resources.new(Rack::MockRequest.env_for("/resources"))
58
+ controller.uri.should == controller.url(:resources)
59
+ end
60
+
61
+ it 'should provide url accessor for resource show route' do
62
+ resource = Model.new
63
+ resource.id = 1
64
+ controller = Resources.new(Rack::MockRequest.env_for("/resources/1"))
65
+ controller.uri.should == controller.url(:resource, resource)
66
+ end
67
+
68
+ end
@@ -0,0 +1,63 @@
1
+ describe "Halcyon" do
2
+
3
+ before do
4
+ @log = ''
5
+ @logger = Logger.new(StringIO.new(@log))
6
+ @config = $config.dup
7
+ @config[:logger] = @logger
8
+ @config[:app] = 'Specs'
9
+ Halcyon.config = @config
10
+ @app = Halcyon::Runner.new
11
+ end
12
+
13
+ it "should provide the path of the application root directory" do
14
+ Halcyon.root.should == Dir.pwd
15
+ end
16
+
17
+ it "should provide quick access to the configuration hash" do
18
+ Halcyon.config.is_a?(Hash).should.be.true?
19
+ end
20
+
21
+ it "should provide environment label" do
22
+ Halcyon.environment.should == :development
23
+ Halcyon.environment.should == Halcyon.config[:environment]
24
+ end
25
+
26
+ it "should provide universal access to a logger" do
27
+ # We assume Logger here because, you know, we're gods of the test
28
+ Halcyon.logger.is_a?(Logger).should.be.true?
29
+ # And this is just a side affect of making the logger universally accessible
30
+ {}.logger.is_a?(Logger).should.be.true?
31
+ end
32
+
33
+ it "should provide the (estimated) application name" do
34
+ # We set this above
35
+ Halcyon.app.should == "Specs"
36
+ end
37
+
38
+ it "should provide sane default paths for essential components" do
39
+ Halcyon.paths.is_a?(Hash).should.be.true?
40
+ Halcyon.paths[:controller].should == Halcyon.root/"app"
41
+ Halcyon.paths[:lib].should == Halcyon.root/"lib"
42
+ Halcyon.paths[:config].should == Halcyon.root/"config"
43
+ Halcyon.paths[:init].should == Halcyon.root/"config"/"{init,initialize}"
44
+ Halcyon.paths[:log].should == Halcyon.root/"log"
45
+ end
46
+
47
+ it "should provide configurable attribute definition for quick access to specific configuration values" do
48
+ test_method = "oracle"
49
+ method_count = Halcyon.methods.length
50
+ Halcyon.configurable("oracle")
51
+ (Halcyon.methods.length - method_count).should == 2
52
+ Halcyon.method(test_method.to_sym).is_a?(Method).should.be.true?
53
+ Halcyon.method("#{test_method}=".to_sym).is_a?(Method).should.be.true?
54
+ Halcyon.send("#{test_method}=".to_sym, 10)
55
+ Halcyon.send(test_method).should == Halcyon.config[test_method.to_sym]
56
+ end
57
+
58
+ it "should predefine quick access to the 'db' configuration value" do
59
+ Halcyon.db = 100
60
+ Halcyon.db.should == Halcyon.config[:db]
61
+ end
62
+
63
+ end
@@ -0,0 +1,31 @@
1
+ describe "Halcyon::Logging" do
2
+
3
+ it "should set the default logger when none specified" do
4
+ Halcyon.send(:remove_const, :Logger)
5
+ Halcyon::Logging.set
6
+ Halcyon::Logger.ancestors.include?(::Logger).should.be.true?
7
+ end
8
+
9
+ it "should set the logger type specified" do
10
+ Halcyon.send(:remove_const, :Logger)
11
+ Halcyon::Logging.set('Logger')
12
+ Halcyon::Logger.ancestors.include?(::Logger).should.be.true?
13
+
14
+ # Not running these because the above test is equivalent as well as
15
+ # throwing errors for folks who do not have Logging, Log4r, and
16
+ # Analogger installed.
17
+
18
+ # Halcyon.send(:remove_const, :Logger)
19
+ # Halcyon::Logging.set('Analogger')
20
+ # Halcyon::Logger.ancestors.include?(::Swiftcore::Analogger::Client).should.be.true?
21
+ #
22
+ # Halcyon.send(:remove_const, :Logger)
23
+ # Halcyon::Logging.set('Log4r')
24
+ # Halcyon::Logger.ancestors.include?(::Log4r::Logger).should.be.true?
25
+ #
26
+ # Halcyon.send(:remove_const, :Logger)
27
+ # Halcyon::Logging.set('Logging')
28
+ # Halcyon::Logger.ancestors.include?(::Logging::Logger).should.be.true?
29
+ end
30
+
31
+ end
@@ -1,32 +1,57 @@
1
- describe "Halcyon::Server::Router" do
1
+ describe "Halcyon::Application::Router" do
2
2
 
3
3
  before do
4
- @app = Specr.new :port => 4000
4
+ @log = ''
5
+ @logger = Logger.new(StringIO.new(@log))
6
+ @config = $config.dup
7
+ @config[:logger] = @logger
8
+ Halcyon.config = @config
9
+ @app = Halcyon::Runner.new
5
10
  end
6
11
 
7
- it "should prepares routes correctly when written correctly" do
12
+ it "should prepare routes correctly when written correctly" do
8
13
  # routes have been defined for Specr
9
- Halcyon::Server::Router.routes.should.not == []
10
- Halcyon::Server::Router.routes.length.should > 0
14
+ Halcyon::Application::Router.routes.should.not == []
15
+ Halcyon::Application::Router.routes.length.should > 0
11
16
  end
12
17
 
13
18
  it "should match URIs to the correct route" do
14
- Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:action].should == 'index'
19
+ request = Rack::Request.new(Rack::MockRequest.env_for('/'))
20
+ Halcyon::Application::Router.route(request)[:action].should == 'index'
15
21
  end
16
22
 
17
23
  it "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'
24
+ # missing instead of not_found because we gave a different default route
25
+ request = Rack::Request.new(Rack::MockRequest.env_for("/erroneous/path/#{rand}/#{rand}"))
26
+ Halcyon::Application::Router.route(request)[:action].should == 'missing'
27
+
28
+ request = Rack::Request.new(Rack::MockRequest.env_for("/random/#{rand}/#{rand}"))
29
+ Halcyon::Application::Router.route(request)[:action].should == 'missing'
20
30
  end
21
31
 
22
32
  it "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'
33
+ request = Rack::Request.new(Rack::MockRequest.env_for('/hello/Matt'))
34
+ response = Halcyon::Application::Router.route(request)
35
+ response[:action].should == 'greeter'
36
+ response[:name].should == 'Matt'
26
37
  end
27
38
 
28
39
  it "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'
40
+ request = Rack::Request.new(Rack::MockRequest.env_for('/'))
41
+ request.env['rack.input'] << "arbitrary=random"
42
+ Halcyon::Application::Router.route(request)[:arbitrary].should == 'random'
43
+ end
44
+
45
+ it "should match index method of resources" do
46
+ index_req = Rack::Request.new(Rack::MockRequest.env_for('/resources'))
47
+ Halcyon::Application::Router.route(index_req)[:controller].should == 'resources'
48
+ Halcyon::Application::Router.route(index_req)[:action].should == 'index'
49
+ end
50
+
51
+ it "should match show method of resource" do
52
+ show_req = Rack::Request.new(Rack::MockRequest.env_for('/resources/id'))
53
+ Halcyon::Application::Router.route(show_req)[:controller].should == 'resources'
54
+ Halcyon::Application::Router.route(show_req)[:action].should == 'show'
30
55
  end
31
56
 
32
57
  end
@@ -0,0 +1,54 @@
1
+ module Kernel
2
+ alias_method :__warn, :warn
3
+ def warn(msg)
4
+ $warning = msg
5
+ __warn(msg) if $do_warns
6
+ end
7
+ end
8
+
9
+ describe "Halcyon::Runner" do
10
+
11
+ before do
12
+ @log = ''
13
+ @logger = Logger.new(StringIO.new(@log))
14
+ @config = $config.dup
15
+ @config[:logger] = @logger
16
+ Halcyon.config = @config
17
+ @app = Halcyon::Runner.new
18
+ end
19
+
20
+ it "should warn if a non-existent config file is loaded" do
21
+ $do_warns = false
22
+ path = Halcyon.root/'config'/'config.yml'
23
+ Halcyon::Runner.load_config(path).nil?.should == true
24
+ $warning.should =~ %r{#{path} not found}
25
+ $do_warns = true
26
+ end
27
+
28
+ it "should set up logging according to configuration" do
29
+ time = Time.now.to_s
30
+ @app.logger.debug "Test message for #{time}"
31
+ @log.should =~ /Test message for #{time}/
32
+ end
33
+
34
+ it "should recognize what application it is running as" do
35
+ # Without setting explicitly in config
36
+ Halcyon.app.should == Halcyon.root.split('/').last.camel_case
37
+
38
+ # With setting explicitly in config
39
+ Halcyon.config[:app] = 'Specr'
40
+ Halcyon::Runner.new
41
+ Halcyon.app.should == 'Specr'
42
+
43
+ # Setting directly
44
+ Halcyon.app = 'Specr2'
45
+ Halcyon.app.should == 'Specr2'
46
+ end
47
+
48
+ it "should proxy calls to Halcyon::Application" do
49
+ status, headers, body = @app.call(Rack::MockRequest.env_for('/'))
50
+ status.should == 200
51
+ body.body[0].should == Specs.new(Rack::MockRequest.env_for('/')).send(:index).to_json
52
+ end
53
+
54
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,21 +1,87 @@
1
- require 'halcyon/server'
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'halcyon')
2
2
  require 'rack/mock'
3
+ require 'logger'
3
4
 
4
- $test = true
5
+ # Default Settings
5
6
 
6
- class Specr < Halcyon::Server::Base
7
+ $config = {
8
+ :allow_from => :all,
9
+ :environment => :development,
10
+ :logger => nil,
11
+ :logging => {
12
+ :level => 'debug'
13
+ }
14
+ }
15
+
16
+ # Testing Application
17
+
18
+ # Default controller
19
+ class Application < Halcyon::Controller; end
20
+
21
+ # Weird edge-case controller
22
+ class Specs < Application
7
23
 
8
- route do |r|
9
- r.match('/hello/:name').to(:action => 'greeter')
10
- r.match('/').to(:action => 'index', :arbitrary => 'random')
24
+ def greeter
25
+ ok("Hello #{params[:name]}")
11
26
  end
12
27
 
13
- def index(params)
28
+ def index
14
29
  ok('Found')
15
30
  end
16
31
 
17
- def greeter(params)
18
- ok("Hello #{params[:name]}")
32
+ def cause_exception
33
+ raise Exception.new("Oops!")
34
+ end
35
+
36
+ def call_nonexistent_method
37
+ hash = Hash.new
38
+ hash.please_dont_exist_and_please_throw_no_method_error
39
+ ok
19
40
  end
20
41
 
42
+ private
43
+
44
+ def undispatchable_private_method
45
+ "it's private, so it won't be found by the dispatcher"
46
+ end
47
+
48
+ end
49
+
50
+ # Resources controller
51
+ class Resources < Application
52
+
53
+ def index
54
+ ok('List of resources')
55
+ end
56
+
57
+ def show
58
+ ok("One resource: #{params[:id]}")
59
+ end
60
+ end
61
+
62
+ # Models
63
+
64
+ class Model
65
+ attr_accessor :id
66
+ end
67
+
68
+ # Environment
69
+
70
+ Halcyon.configurable_attr(:environment)
71
+
72
+ # Testing routes
73
+
74
+ Halcyon::Application.route do |r|
75
+ r.resources :resources
76
+
77
+ r.match('/hello/:name').to(:controller => 'specs', :action => 'greeter')
78
+ r.match('/:action').to(:controller => 'specs')
79
+ r.match('/:controller/:action').to()
80
+ r.match('/').to(:controller => 'specs', :action => 'index', :arbitrary => 'random')
81
+ # r.default_routes
82
+ {:action => 'missing'}
83
+ end
84
+
85
+ Halcyon::Application.startup do |config|
86
+ $started = true
21
87
  end