circuit 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/Gemfile +34 -0
  2. data/LICENSE +20 -0
  3. data/README.md +161 -0
  4. data/Rakefile +27 -0
  5. data/config.ru +7 -0
  6. data/description.md +5 -0
  7. data/docs/COMPATIBILITY.md +14 -0
  8. data/docs/ROADMAP.md +29 -0
  9. data/lib/circuit.rb +125 -0
  10. data/lib/circuit/behavior.rb +99 -0
  11. data/lib/circuit/compatibility.rb +73 -0
  12. data/lib/circuit/middleware.rb +6 -0
  13. data/lib/circuit/middleware/rewriter.rb +43 -0
  14. data/lib/circuit/rack.rb +14 -0
  15. data/lib/circuit/rack/behavioral.rb +45 -0
  16. data/lib/circuit/rack/builder.rb +50 -0
  17. data/lib/circuit/rack/multi_site.rb +22 -0
  18. data/lib/circuit/rack/request.rb +81 -0
  19. data/lib/circuit/railtie.rb +24 -0
  20. data/lib/circuit/storage.rb +74 -0
  21. data/lib/circuit/storage/memory_model.rb +70 -0
  22. data/lib/circuit/storage/nodes.rb +56 -0
  23. data/lib/circuit/storage/nodes/memory_store.rb +63 -0
  24. data/lib/circuit/storage/nodes/model.rb +67 -0
  25. data/lib/circuit/storage/nodes/mongoid_store.rb +56 -0
  26. data/lib/circuit/storage/sites.rb +38 -0
  27. data/lib/circuit/storage/sites/memory_store.rb +53 -0
  28. data/lib/circuit/storage/sites/model.rb +29 -0
  29. data/lib/circuit/storage/sites/mongoid_store.rb +43 -0
  30. data/lib/circuit/validators.rb +74 -0
  31. data/lib/circuit/version.rb +3 -0
  32. data/spec/internal/app/behaviors/change_path.rb +7 -0
  33. data/spec/internal/app/controllers/application_controller.rb +5 -0
  34. data/spec/internal/app/helpers/application_helper.rb +2 -0
  35. data/spec/internal/config/initializers/circuit.rb +7 -0
  36. data/spec/internal/config/routes.rb +3 -0
  37. data/spec/internal/db/schema.rb +1 -0
  38. data/spec/lib/circuit/behavior_spec.rb +113 -0
  39. data/spec/lib/circuit/middleware/rewriter_spec.rb +79 -0
  40. data/spec/lib/circuit/rack/behavioral_spec.rb +60 -0
  41. data/spec/lib/circuit/rack/builder_spec.rb +125 -0
  42. data/spec/lib/circuit/rack/multi_site_spec.rb +34 -0
  43. data/spec/lib/circuit/rack/request_spec.rb +80 -0
  44. data/spec/lib/circuit/railtie_spec.rb +34 -0
  45. data/spec/lib/circuit/storage/nodes_spec.rb +62 -0
  46. data/spec/lib/circuit/storage/sites_spec.rb +60 -0
  47. data/spec/lib/circuit/storage_spec.rb +20 -0
  48. data/spec/lib/circuit/validators_spec.rb +69 -0
  49. data/spec/lib/circuit_spec.rb +139 -0
  50. data/spec/spec_helper.rb +79 -0
  51. data/spec/support/blueprints.rb +24 -0
  52. data/spec/support/matchers/be_current_time_matcher.rb +14 -0
  53. data/spec/support/matchers/extended_have_key.rb +31 -0
  54. data/spec/support/matchers/have_accessor_matcher.rb +13 -0
  55. data/spec/support/matchers/have_attribute_matcher.rb +22 -0
  56. data/spec/support/matchers/have_block_matcher.rb +14 -0
  57. data/spec/support/matchers/have_errors_on_matcher.rb +30 -0
  58. data/spec/support/matchers/have_module_matcher.rb +13 -0
  59. data/spec/support/matchers/have_reader_matcher.rb +18 -0
  60. data/spec/support/matchers/have_writer_matcher.rb +18 -0
  61. data/spec/support/matchers/set_instance_variable.rb +32 -0
  62. data/spec/support/spec_helpers/base_behaviors.rb +20 -0
  63. data/spec/support/spec_helpers/base_models.rb +64 -0
  64. data/spec/support/spec_helpers/logger_helpers.rb +58 -0
  65. data/spec/support/spec_helpers/multi_site_helper.rb +48 -0
  66. data/spec/support/spec_helpers/rack_helpers.rb +8 -0
  67. data/spec/support/spec_helpers/shared_examples/node_store.rb +87 -0
  68. data/spec/support/spec_helpers/shared_examples/site_store.rb +76 -0
  69. data/spec/support/spec_helpers/simple_machinable.rb +29 -0
  70. data/spec/support/spec_helpers/stores_cleaner.rb +46 -0
  71. data/vendor/active_support-3.2/core_ext/string/inflections.rb +53 -0
  72. data/vendor/active_support-3.2/inflector/methods.rb +65 -0
  73. data/vendor/rack-1.4/builder.rb +167 -0
  74. data/vendor/rack-1.4/urlmap.rb +96 -0
  75. metadata +238 -0
@@ -0,0 +1,3 @@
1
+ module Circuit
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ module ChangePath
2
+ include Circuit::Behavior
3
+
4
+ use Circuit::Middleware::Rewriter do |script_name, path_info|
5
+ ["/site/5#{script_name}", path_info]
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ respond_to :html
3
+
4
+ protect_from_forgery
5
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -0,0 +1,7 @@
1
+ if defined?(::Mongoid)
2
+ Circuit.set_site_store :mongoid_store
3
+ Circuit.set_node_store :mongoid_store
4
+ else
5
+ Circuit.set_site_store :memory_store
6
+ Circuit.set_node_store :memory_store
7
+ end
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ #
3
+ end
@@ -0,0 +1 @@
1
+ # make it empty?
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Behavior do
4
+ class Middleware1
5
+ def initialize(app); end
6
+ end
7
+ class Middleware2
8
+ def initialize(app); end
9
+ end
10
+ class Middleware3
11
+ def initialize(app); end
12
+ end
13
+
14
+ class BaseClass
15
+ include Circuit::Behavior
16
+ use Middleware1
17
+ use Middleware2
18
+ end
19
+
20
+ class InheritedClass < BaseClass
21
+ use Middleware3
22
+ end
23
+
24
+ module NoBuilderBehavior
25
+ include Circuit::Behavior
26
+ end
27
+
28
+ let(:base_class) { BaseClass }
29
+ let(:inherited_class) { InheritedClass }
30
+ subject { BaseClass }
31
+
32
+ it { lambda{Circuit::Behavior}.should_not raise_error }
33
+ it { lambda{ subject }.should_not raise_error }
34
+ it { subject.builder.should be_true }
35
+
36
+ context 'is listed in ancestors' do
37
+ subject { base_class.ancestors }
38
+ it { should include Circuit::Behavior }
39
+ end
40
+
41
+ context 'is listed in inherited ancestors' do
42
+ subject { inherited_class.ancestors }
43
+ it { should include Circuit::Behavior }
44
+ end
45
+
46
+ context 'when mixed class is configured' do
47
+ subject { base_class.builder.instance_variable_get(:@use) }
48
+ it { subject.each { |u| u.should be_instance_of(Proc) } }
49
+ it { should have(2).procs}
50
+ it { subject[0].call(Object.new).class.should == Middleware1 }
51
+ it { subject[1].call(Object.new).class.should == Middleware2 }
52
+ end
53
+
54
+ context 'inherited stack is configured properly' do
55
+ subject { inherited_class.builder.instance_variable_get(:@use) }
56
+ it { subject.each { |u| u.should be_instance_of(Proc) } }
57
+ it { should have(3).procs}
58
+ it { subject[0].call(Object.new).class.should == Middleware1 }
59
+ it { subject[1].call(Object.new).class.should == Middleware2 }
60
+ it { subject[2].call(Object.new).class.should == Middleware3 }
61
+ end
62
+
63
+ context "without a builder" do
64
+ subject { NoBuilderBehavior }
65
+ it { subject.builder?.should be_false }
66
+ end
67
+
68
+ context "without a cru path" do
69
+ before do
70
+ @prev_cru_path = Circuit.cru_path
71
+ Circuit.cru_path = nil
72
+ NoBuilderBehavior.instance_variable_set(:@cru_path, nil)
73
+ end
74
+ subject { NoBuilderBehavior }
75
+ it { subject.builder?.should be_false }
76
+ it do
77
+ expect { subject.cru_path }.
78
+ to raise_error(Circuit::Behavior::RackupPathError,
79
+ "Rackup path cannot be determined for NoBuilderBehavior")
80
+ end
81
+ after { Circuit.cru_path = @prev_cru_path }
82
+ end
83
+
84
+ describe "get constants" do
85
+ context "already loaded" do
86
+ before { ChangePath }
87
+ subject { Circuit::Behavior.get("ChangePath") }
88
+ it { should == lambda{ChangePath}.call }
89
+ it { should have_module(Circuit::Behavior) }
90
+ it { subject.to_s.should == "ChangePath" }
91
+ it { subject.builder?.should be_true }
92
+ it "should not dynamically set a constant" do
93
+ Object.expects(:const_set).never
94
+ subject
95
+ end
96
+ after { Object.unstub(:const_set) }
97
+ end
98
+
99
+ context "load from .cru" do
100
+ before { Object.send(:remove_const, :RenderOk) if Object.const_defined?(:RenderOk) }
101
+ subject { Circuit::Behavior.get("RenderOk") }
102
+ it { should == lambda{RenderOk}.call }
103
+ it { should have_module(Circuit::Behavior) }
104
+ it { subject.to_s.should == "RenderOk" }
105
+ it { subject.builder?.should be_true }
106
+ it "should dynamically set a constant" do
107
+ Object.expects(:const_set).once
108
+ subject
109
+ end
110
+ after { Object.unstub(:const_set) }
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Middleware::Rewriter do
4
+ include SpecHelpers::LoggerHelpers
5
+ let(:request) { mock_request() } # http://www.example.com/foo/bar/baz
6
+
7
+ def self.run_app(&block)
8
+ before do
9
+ @response = ::Rack::Builder.new do
10
+ use Circuit::Middleware::Rewriter, &block
11
+ run lambda { |env| [ 200, {}, %w[SCRIPT_NAME PATH_INFO].collect {|k| env[k]} ] }
12
+ end.to_app.call(mock_request.env)
13
+ end
14
+ subject { @response }
15
+ end
16
+
17
+ context "should be able to not rewrite" do
18
+ run_app { |*args| args }
19
+ subject { @response }
20
+ it { should == [ 200, {}, ["", "/foo/bar/baz"] ] }
21
+
22
+ context "logger" do
23
+ subject { logger_output }
24
+ it { should be_blank }
25
+ end
26
+ end
27
+
28
+ context "rewrite script_name and path_info" do
29
+ run_app do |script_name, path_info|
30
+ ["/site/5#{script_name}", path_info+"/1"]
31
+ end
32
+ it { should == [ 200, {}, %w[/site/5 /foo/bar/baz/1] ] }
33
+
34
+ context "logger" do
35
+ subject { logger_output }
36
+ it { should == "[CIRCUIT] Rewriting: '/foo/bar/baz'->'/site/5/foo/bar/baz/1'\n" }
37
+ end
38
+ end
39
+
40
+ context "rewrite script_name and not path_info" do
41
+ run_app do |script_name, path_info|
42
+ ["/site/5#{script_name}", path_info]
43
+ end
44
+ it { should == [ 200, {}, %w[/site/5 /foo/bar/baz] ] }
45
+
46
+ context "logger" do
47
+ subject { logger_output }
48
+ it { should == "[CIRCUIT] Rewriting: '/foo/bar/baz'->'/site/5/foo/bar/baz'\n" }
49
+ end
50
+ end
51
+
52
+ context "rewrite path_info and not script_name" do
53
+ run_app do |script_name, path_info|
54
+ [script_name, path_info+"/1"]
55
+ end
56
+ it { should == [ 200, {}, ["", "/foo/bar/baz/1"] ] }
57
+
58
+ context "logger" do
59
+ subject { logger_output }
60
+ it { should == "[CIRCUIT] Rewriting: '/foo/bar/baz'->'/foo/bar/baz/1'\n" }
61
+ end
62
+ end
63
+
64
+ context "should catch and log rewriter errors" do
65
+ run_app do |*args|
66
+ raise Circuit::Middleware::RewriteError, "an error occurred"
67
+ end
68
+ it { should == [ 200, {}, ["", "/foo/bar/baz"] ] }
69
+
70
+ context "logger" do
71
+ subject { logger_output.split(/\n/) }
72
+ it { should have(2).lines }
73
+ it { subject.first.should == "[CIRCUIT] Rewrite Error: an error occurred" }
74
+ it "second line should have the backtrace" do
75
+ subject.last.should match(/\A\s+(.+)#{Regexp.quote("rewriter_spec.rb")}\:(\d+)(\:in|$)/)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Rack::Behavioral do
4
+ include Rack::Test::Methods
5
+ include SpecHelpers::MultiSiteHelper
6
+
7
+ class RenderMyMiddlewareBehavior
8
+ include ::Circuit::Behavior
9
+
10
+ use(Class.new do
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ [200, {"Content-Type" => "test/html"}, ["RenderMyMiddlewareBehavior"]]
17
+ end
18
+ end)
19
+ end
20
+
21
+ def app
22
+ stub_app_with_circuit_site setup_site!(root.site, RenderMyMiddlewareBehavior)
23
+ end
24
+
25
+ context 'GET /' do
26
+ get "/"
27
+
28
+ context "status" do
29
+ subject { last_response.body }
30
+ it { should include("RenderMyMiddlewareBehavior") }
31
+ end
32
+ end
33
+
34
+ context 'GET / for site with no root' do
35
+ def app
36
+ stub_app_with_circuit_site dup_site_1
37
+ end
38
+
39
+ it "should raise a path not found error" do
40
+ expect { get '/' }.to raise_error(Circuit::Storage::Nodes::NotFoundError, "Path not found")
41
+ end
42
+ end
43
+
44
+ context "GET / without site" do
45
+ def no_site_middleware
46
+ (Class.new do
47
+ def initialize(app, site=nil) @app = app; end
48
+ def call(env) @app.call(env); end
49
+ end)
50
+ end
51
+
52
+ def app
53
+ stub_app_with_circuit_site nil, no_site_middleware
54
+ end
55
+
56
+ it "should raise a missing site error" do
57
+ expect { get "/" }.to raise_error(Circuit::Rack::MissingSiteError, "Rack variable rack.circuit.site is missing")
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Rack::Builder do
4
+ context "without an app" do
5
+ subject { Circuit::Rack::Builder.new }
6
+ it { should respond_to(:app?) }
7
+ it { subject.app?.should be_false }
8
+ end
9
+
10
+ context "with an app" do
11
+ subject { Circuit::Rack::Builder.new(lambda { |env| [200, {}, %w[OK]]}) }
12
+ it { should respond_to(:app?) }
13
+ it { subject.app?.should be_true }
14
+ end
15
+
16
+ context "defers to ::Rack::Builder for .ru files" do
17
+ subject { Circuit::Rack::Builder.parse_file(Circuit.cru_path.join("render_not_found.ru")).first }
18
+ it { should be_instance_of(Circuit::Rack::Builder) }
19
+ it do
20
+ ::Rack::Builder.expects(:parse_file).once
21
+ subject
22
+ end
23
+ after { ::Rack::Builder.unstub(:parse_file) }
24
+ end
25
+
26
+ context "parses .cru files" do
27
+ subject { Circuit::Rack::Builder.parse_file(Circuit.cru_path.join("render_ok.cru")).first }
28
+ it { should be_instance_of(Circuit::Rack::Builder) }
29
+ it do
30
+ ::Rack::Builder.expects(:parse_file).never
31
+ subject
32
+ end
33
+ after { ::Rack::Builder.unstub(:parse_file) }
34
+ end
35
+
36
+ context "dup without map" do
37
+ let(:use) { [mock()] }
38
+ let(:run) { mock() }
39
+
40
+ subject do
41
+ ::Rack::Builder.new.tap do |b|
42
+ b.instance_variable_set(:@use, use)
43
+ b.instance_variable_set(:@run, run)
44
+ end.dup
45
+ end
46
+
47
+ it { subject.instance_variable_get(:@run).should equal(run) }
48
+ it { subject.instance_variable_get(:@use).should eql(use) }
49
+ it { subject.instance_variable_get(:@use).should_not equal(use) }
50
+ it { subject.instance_variable_get(:@map).should be_nil }
51
+ it { subject.dup.instance_variable_get(:@map).should be_nil }
52
+ end
53
+
54
+ context "dup with map" do
55
+ let(:use) { [mock()] }
56
+ let(:map) { {"/" => mock()} }
57
+ let(:run) { mock() }
58
+
59
+ subject do
60
+ ::Rack::Builder.new.tap do |b|
61
+ b.instance_variable_set(:@use, use)
62
+ b.instance_variable_set(:@map, map)
63
+ b.instance_variable_set(:@run, run)
64
+ end.dup
65
+ end
66
+
67
+ it { subject.instance_variable_get(:@run).should equal(run) }
68
+ it { subject.instance_variable_get(:@use).should eql(use) }
69
+ it { subject.instance_variable_get(:@use).should_not equal(use) }
70
+ it { subject.instance_variable_get(:@map).should eql(map) }
71
+ it { subject.instance_variable_get(:@map).should_not equal(map) }
72
+ end
73
+ end
74
+
75
+ describe ::Rack::Builder do
76
+ context "without an app" do
77
+ subject { ::Rack::Builder.new }
78
+ it { should respond_to(:app?) }
79
+ it { subject.app?.should be_false }
80
+ end
81
+
82
+ context "with an app" do
83
+ subject { ::Rack::Builder.new(lambda { |env| [200, {}, %w[OK]]}) }
84
+ it { should respond_to(:app?) }
85
+ it { subject.app?.should be_true }
86
+ end
87
+
88
+ context "dup without map" do
89
+ let(:use) { [mock()] }
90
+ let(:run) { mock() }
91
+
92
+ subject do
93
+ ::Rack::Builder.new.tap do |b|
94
+ b.instance_variable_set(:@use, use)
95
+ b.instance_variable_set(:@run, run)
96
+ end.dup
97
+ end
98
+
99
+ it { subject.instance_variable_get(:@run).should equal(run) }
100
+ it { subject.instance_variable_get(:@use).should eql(use) }
101
+ it { subject.instance_variable_get(:@use).should_not equal(use) }
102
+ it { subject.instance_variable_get(:@map).should be_nil }
103
+ it { subject.dup.instance_variable_get(:@map).should be_nil }
104
+ end
105
+
106
+ context "dup with map" do
107
+ let(:use) { [mock()] }
108
+ let(:map) { {"/" => mock()} }
109
+ let(:run) { mock() }
110
+
111
+ subject do
112
+ ::Rack::Builder.new.tap do |b|
113
+ b.instance_variable_set(:@use, use)
114
+ b.instance_variable_set(:@map, map)
115
+ b.instance_variable_set(:@run, run)
116
+ end.dup
117
+ end
118
+
119
+ it { subject.instance_variable_get(:@run).should equal(run) }
120
+ it { subject.instance_variable_get(:@use).should eql(use) }
121
+ it { subject.instance_variable_get(:@use).should_not equal(use) }
122
+ it { subject.instance_variable_get(:@map).should eql(map) }
123
+ it { subject.instance_variable_get(:@map).should_not equal(map) }
124
+ end
125
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Rack::MultiSite do
4
+ include Rack::Test::Methods
5
+ include SpecHelpers::MultiSiteHelper
6
+
7
+ def app
8
+ Rack::Builder.app do
9
+ use Circuit::Rack::MultiSite
10
+ run Proc.new {|env| [200, {}, ["ok"]] }
11
+ end
12
+ end
13
+
14
+ context 'GET example.com' do
15
+ before do
16
+ get "http://#{site.host}/"
17
+ end
18
+
19
+ context "status" do
20
+ subject { last_response.status }
21
+ it { should == 200 }
22
+ end
23
+ end
24
+
25
+ context "GET baddomain.com" do
26
+ before do
27
+ get "http://baddomain.com/"
28
+ end
29
+
30
+ subject { last_response }
31
+ it { subject.status.should == 404 }
32
+ it { subject.body.should == "Not Found"}
33
+ end
34
+ end