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,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Rack::Request do
4
+ context "Rack::Request should have module" do
5
+ it { ::Rack::Request.included_modules.should include(::Circuit::Rack::Request) }
6
+ end
7
+
8
+ describe "site" do
9
+ let(:site) { mock() }
10
+
11
+ context "get" do
12
+ subject { mock_request(Circuit::Rack::Request::ENV_SITE => site).site }
13
+ it { should == site }
14
+ end
15
+
16
+ context "set" do
17
+ subject { mock_request.tap { |r| r.site = site } }
18
+ it { subject.site.should == site }
19
+ it { subject.env[Circuit::Rack::Request::ENV_SITE].should == site }
20
+ end
21
+ end
22
+
23
+ describe "path segments" do
24
+ context "splits by slashes" do
25
+ subject { mock_request.path_segments }
26
+ it { should == [nil]+%w[foo bar baz] }
27
+ end
28
+
29
+ context "path_segments with blanks and double slashes" do
30
+ subject { mock_request(:path => "/foo//bar/baz///").path_segments }
31
+ it { should == [nil]+%w[foo bar baz] }
32
+ end
33
+
34
+ context "class method" do
35
+ it do
36
+ ::Rack::Request.expects(:path_segments).once.with("/foo/bar/baz")
37
+ mock_request.path_segments
38
+ end
39
+ after { ::Rack::Request.unstub(:path_segments) }
40
+ end
41
+ end
42
+
43
+ describe "route" do
44
+ let(:route) { mock() }
45
+
46
+ context "none" do
47
+ subject { mock_request.route }
48
+ it { should be_nil }
49
+ end
50
+
51
+ context "get" do
52
+ subject { mock_request(Circuit::Rack::Request::ENV_ROUTE => route).route }
53
+ it { should == route }
54
+ end
55
+
56
+ context "set" do
57
+ let(:path) { "/foo/bar/baz" }
58
+ let(:route) { mock(:last => mock(:path => path), :length => 4) }
59
+ subject { mock_request.tap { |r| r.route = route } }
60
+ it { subject.route.should == route }
61
+ it { subject.env["SCRIPT_NAME"].should == "/foo/bar/baz" }
62
+ it { subject.env["PATH_INFO"].should == "" }
63
+ it { subject.path.should == "/foo/bar/baz" }
64
+ it { subject.circuit_originals["SCRIPT_NAME"].should == "" }
65
+ it { subject.circuit_originals["PATH_INFO"].should == "/foo/bar/baz" }
66
+ end
67
+
68
+ context "set partial path" do
69
+ let(:path) { "/foo/bar" }
70
+ let(:route) { mock(:last => mock(:path => path), :length => 3) }
71
+ subject { mock_request.tap { |r| r.route = route } }
72
+ it { subject.route.should == route }
73
+ it { subject.env["SCRIPT_NAME"].should == "/foo/bar" }
74
+ it { subject.env["PATH_INFO"].should == "/baz" }
75
+ it { subject.path.should == "/foo/bar/baz" }
76
+ it { subject.circuit_originals["SCRIPT_NAME"].should == "" }
77
+ it { subject.circuit_originals["PATH_INFO"].should == "/foo/bar/baz" }
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Railtie do
4
+ context "middlewares" do
5
+ subject { Rails.application.config.middleware }
6
+ it { subject[0].klass.should == Circuit::Rack::MultiSite }
7
+ it { subject[1].klass.should == Circuit::Rack::Behavioral }
8
+ end
9
+
10
+ context "default cru_path" do
11
+ subject { Circuit.cru_path }
12
+ it { should == Rails.root.join("app", "behaviors") }
13
+ end
14
+
15
+ context "application paths" do
16
+ subject { Rails.application.config.paths.all_paths }
17
+ it { should include(["app/behaviors"]) }
18
+ end
19
+
20
+ context "eager load paths" do
21
+ subject { Rails.application.config.paths.eager_load }
22
+ it { should include(Circuit.cru_path.to_s) }
23
+ it do
24
+ Dir.glob(Circuit.cru_path.join("**", "*.rb")).each do |path|
25
+ subject.should include(path)
26
+ end
27
+ end
28
+ end
29
+
30
+ context "logger" do
31
+ subject { Circuit.logger }
32
+ it { should equal(Rails.logger) }
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Storage::Nodes do
4
+ include SpecHelpers::StoresCleaner
5
+ include SpecHelpers::BaseModels
6
+
7
+ describe Circuit::Storage::Nodes::BaseStore do
8
+ context "unimplemented store" do
9
+ class Circuit::Storage::Nodes::UnimplementedStore < Circuit::Storage::Nodes::BaseStore
10
+ end
11
+
12
+ subject { Circuit::Storage::Nodes::UnimplementedStore.new }
13
+
14
+ it do
15
+ expect { subject.get("foo", "bar") }.
16
+ to raise_error(Circuit::Storage::Nodes::UnimplementedError, "Circuit::Storage::Nodes::UnimplementedStore#get not implemented.")
17
+ end
18
+
19
+ it do
20
+ expect { subject.get!("foo", "bar") }.
21
+ to raise_error(Circuit::Storage::Nodes::UnimplementedError, "Circuit::Storage::Nodes::UnimplementedStore#get not implemented.")
22
+ end
23
+ end
24
+
25
+ context "empty store" do
26
+ class Circuit::Storage::Nodes::EmptyStore < Circuit::Storage::Nodes::BaseStore
27
+ def get(site, path) nil; end
28
+ end
29
+
30
+ subject { Circuit::Storage::Nodes::EmptyStore.new }
31
+
32
+ it { subject.get("foo", "bar").should be_nil }
33
+
34
+ it do
35
+ expect { subject.get!("foo", "bar").
36
+ to raise_error(Circuit::Storage::Nodes::NotFoundError, "Host not found")
37
+ }
38
+ end
39
+ end
40
+ end
41
+
42
+ describe Circuit::Storage::Nodes::MemoryStore do
43
+ use_storage :memory_store
44
+ let!(:store) { :memory_store }
45
+ include_examples "node store"
46
+
47
+ describe Circuit::Storage::Nodes::MemoryStore::Node do
48
+ subject { child }
49
+ it { should have_attribute(:slug) }
50
+ it { should have_attribute(:behavior_klass) }
51
+ it { should have_attribute(:site) }
52
+ it { should have_attribute(:parent) }
53
+ it { should have_attribute(:children) }
54
+ end
55
+ end
56
+
57
+ describe "Circuit::Storage::Nodes::MongoidStore", :if => $mongo_tests do
58
+ use_storage :mongoid_store
59
+ let!(:store) { :mongoid_store }
60
+ include_examples "node store"
61
+ end
62
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Storage::Sites do
4
+ include SpecHelpers::StoresCleaner
5
+ include SpecHelpers::BaseModels
6
+
7
+ describe Circuit::Storage::Sites::BaseStore do
8
+ context "unimplemented store" do
9
+ class Circuit::Storage::Sites::UnimplementedStore < Circuit::Storage::Sites::BaseStore
10
+ end
11
+
12
+ subject { Circuit::Storage::Sites::UnimplementedStore.new }
13
+
14
+ it do
15
+ expect { subject.get("foo") }.
16
+ to raise_error(Circuit::Storage::Sites::UnimplementedError, "Circuit::Storage::Sites::UnimplementedStore#get not implemented.")
17
+ end
18
+
19
+ it do
20
+ expect { subject.get!("foo") }.
21
+ to raise_error(Circuit::Storage::Sites::UnimplementedError, "Circuit::Storage::Sites::UnimplementedStore#get not implemented.")
22
+ end
23
+ end
24
+
25
+ context "empty store" do
26
+ class Circuit::Storage::Sites::EmptyStore < Circuit::Storage::Sites::BaseStore
27
+ def get(host) nil; end
28
+ end
29
+
30
+ subject { Circuit::Storage::Sites::EmptyStore.new }
31
+
32
+ it { subject.get("foo").should be_nil }
33
+
34
+ it do
35
+ expect { subject.get!("foo").
36
+ to raise_error(Circuit::Storage::Sites::NotFoundError, "Host not found")
37
+ }
38
+ end
39
+ end
40
+ end
41
+
42
+ describe Circuit::Storage::Sites::MemoryStore do
43
+ use_storage :memory_store
44
+ let!(:store) { :memory_store }
45
+ include_examples "site store"
46
+
47
+ describe Circuit::Storage::Sites::MemoryStore::Site do
48
+ subject { site }
49
+ it { should have_attribute(:host) }
50
+ it { should have_attribute(:aliases) }
51
+ it { should have_attribute(:route) }
52
+ end
53
+ end
54
+
55
+ describe "Circuit::Storage::Sites::MongoidStore", :if => $mongo_tests do
56
+ use_storage :mongoid_store
57
+ let!(:store) { :mongoid_store }
58
+ include_examples "site store"
59
+ end
60
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Circuit::Storage do
4
+ include SpecHelpers::StoresCleaner
5
+
6
+ context "undefined site store" do
7
+ it do
8
+ expect { Circuit.site_store }.
9
+ to raise_error(Circuit::Storage::InstanceUndefinedError, "Storage instance is undefined.")
10
+ end
11
+ end
12
+
13
+ context "undefined node store" do
14
+ it do
15
+ expect { Circuit.node_store }.
16
+ to raise_error(Circuit::Storage::InstanceUndefinedError, "Storage instance is undefined.")
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'circuit/validators'
3
+
4
+ describe Circuit::Validators do
5
+ describe Circuit::Validators::DomainValidator do
6
+ subject { DomainValidator.new(:attributes => :domain) }
7
+ let(:record) { stub(:errors => ActiveModel::Errors.new(nil)) }
8
+
9
+ context "with a top level domain" do
10
+ before { subject.validate_each(record, :domain, "localhost") }
11
+ it { record.errors.should be_empty }
12
+ end
13
+
14
+ context "with a real domain name" do
15
+ before { subject.validate_each(record, :domain, "google.com") }
16
+ it { record.errors.should be_empty }
17
+ end
18
+
19
+ context "with a subdomain" do
20
+ before { subject.validate_each(record, :domain, "plus.google.com") }
21
+ it { record.errors.should be_empty }
22
+ end
23
+
24
+ context "with a invalid characters" do
25
+ before { subject.validate_each(record, :domain, "bad%domain.com") }
26
+ it { record.errors.to_hash.should == {:domain => ["is not a valid domain."]} }
27
+ end
28
+ end
29
+
30
+ describe Circuit::Validators::DomainArrayValidator do
31
+ subject { DomainArrayValidator.new(:attributes => :aliases) }
32
+ let(:record) { stub(:errors => ActiveModel::Errors.new(nil)) }
33
+
34
+ context "with a String" do
35
+ before { subject.validate_each(record, :domain, "localhost") }
36
+ it { record.errors.should be_empty }
37
+ end
38
+
39
+ context "with an empty array" do
40
+ before { subject.validate_each(record, :domain, []) }
41
+ it { record.errors.should be_empty }
42
+ end
43
+
44
+ context "with multiple bad domains" do
45
+ before { subject.validate_each(record, :domain, %w[bad%domain1.com bad%domain2.com]) }
46
+ it { record.errors.to_hash.should == {:domain => ["has an invalid domain."]} }
47
+ end
48
+
49
+ context "with all good domains" do
50
+ before { subject.validate_each(record, :domain, %w[google.com plus.google.com www.google.com]) }
51
+ it { record.errors.should be_empty }
52
+ end
53
+ end
54
+
55
+ describe Circuit::Validators::SlugValidator do
56
+ subject { SlugValidator.new(:attributes => :slug) }
57
+ let(:record) { stub(:errors => ActiveModel::Errors.new(nil)) }
58
+
59
+ context "with a valid slug" do
60
+ before { subject.validate_each(record, :slug, "a-path-segment") }
61
+ it { record.errors.should be_empty }
62
+ end
63
+
64
+ context "with an invalid slug" do
65
+ before { subject.validate_each(record, :slug, "%") }
66
+ it { record.errors.to_hash.should == {:slug => ["is not a valid path segment."]} }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe Circuit do
5
+ include SpecHelpers::LoggerHelpers
6
+ include SpecHelpers::StoresCleaner
7
+
8
+ context 'object' do
9
+ subject { lambda { Circuit } }
10
+ it { should_not raise_error }
11
+ end
12
+
13
+ context "logger" do
14
+ context "default" do
15
+ subject { Circuit.logger }
16
+ it { should be_instance_of(::Logger) }
17
+ end
18
+
19
+ context "set" do
20
+ before &:clean_logger!
21
+ let(:new_logger) { ::Logger.new(StringIO.new) }
22
+ before { Circuit.logger = new_logger }
23
+ subject { Circuit.logger }
24
+ it { should == new_logger }
25
+ end
26
+
27
+ context "change" do
28
+ let(:new_logger) { ::Logger.new(StringIO.new) }
29
+ before { Circuit.logger = new_logger }
30
+ subject { Circuit.logger }
31
+ it { should == new_logger }
32
+ it { should_not == default_logger }
33
+ end
34
+ end
35
+
36
+ context "site store" do
37
+ let(:klass) { $mongo_tests ? Circuit::Storage::Sites::MongoidStore : Circuit::Storage::Sites::MemoryStore }
38
+
39
+ context "get" do
40
+ let(:mock_instance) { mock() }
41
+ before { Circuit::Storage::Sites.expects(:instance).returns(mock_instance) }
42
+ after { Circuit::Storage::Sites.unstub(:instance) }
43
+ it { Circuit.site_store.should == mock_instance}
44
+ end
45
+
46
+ context "not set" do
47
+ it do
48
+ expect { Circuit.site_store }.
49
+ to raise_error(Circuit::Storage::InstanceUndefinedError, "Storage instance is undefined.")
50
+ end
51
+ it { expect { Circuit::Site }.to raise_error(NameError) }
52
+ end
53
+
54
+ context "set by class" do
55
+ before { Circuit.set_site_store klass }
56
+ it { Circuit.site_store.should be_instance_of klass }
57
+ it { Circuit::Site.should == klass.const_get(:Site) }
58
+ end
59
+
60
+ context "set by instance" do
61
+ let(:instance) { klass.new }
62
+ before { Circuit.set_site_store instance }
63
+ it { Circuit.site_store.should == instance }
64
+ it { Circuit::Site.should == klass.const_get(:Site) }
65
+ end
66
+
67
+ context "set by symbol" do
68
+ before { Circuit.set_site_store($mongo_tests ? :mongoid_store : :memory_store) }
69
+ it { Circuit.site_store.should be_instance_of(klass) }
70
+ it { Circuit::Site.should == klass.const_get(:Site) }
71
+ end
72
+
73
+ context "set wrong type" do
74
+ it do
75
+ expect { Circuit.set_site_store Object.new }.
76
+ to raise_error(ArgumentError, "Unexpected type for storage instance: Object")
77
+ end
78
+ end
79
+
80
+ context "set with no Site class" do
81
+ it do
82
+ expect { Circuit.set_site_store Object }.
83
+ to raise_error(ArgumentError, "Cannot determine a Site or Node class for storage type: Object")
84
+ end
85
+ end
86
+ end
87
+
88
+ context "node store" do
89
+ let(:klass) { $mongo_tests ? Circuit::Storage::Nodes::MongoidStore : Circuit::Storage::Nodes::MemoryStore }
90
+
91
+ context "get" do
92
+ let(:mock_instance) { mock() }
93
+ before { Circuit::Storage::Nodes.expects(:instance).returns(mock_instance) }
94
+ after { Circuit::Storage::Nodes.unstub(:instance) }
95
+ it { Circuit.node_store.should == mock_instance}
96
+ end
97
+
98
+ context "not set" do
99
+ it do
100
+ expect { Circuit.node_store }.
101
+ to raise_error(Circuit::Storage::InstanceUndefinedError, "Storage instance is undefined.")
102
+ end
103
+ it { expect { Circuit::Node }.to raise_error(NameError) }
104
+ end
105
+
106
+ context "set by class" do
107
+ before { Circuit.set_node_store klass }
108
+ it { Circuit.node_store.should be_instance_of klass }
109
+ it { Circuit::Node.should == klass.const_get(:Node) }
110
+ end
111
+
112
+ context "set by instance" do
113
+ let(:instance) { klass.new }
114
+ before { Circuit.set_node_store instance }
115
+ it { Circuit.node_store.should == instance }
116
+ it { Circuit::Node.should == klass.const_get(:Node) }
117
+ end
118
+
119
+ context "set by symbol" do
120
+ before { Circuit.set_node_store($mongo_tests ? :mongoid_store : :memory_store) }
121
+ it { Circuit.node_store.should be_instance_of klass }
122
+ it { Circuit::Node.should == klass.const_get(:Node) }
123
+ end
124
+
125
+ context "set wrong type" do
126
+ it do
127
+ expect { Circuit.set_node_store Object.new }.
128
+ to raise_error(ArgumentError, "Unexpected type for storage instance: Object")
129
+ end
130
+ end
131
+
132
+ context "set with no Node class" do
133
+ it do
134
+ expect { Circuit.set_node_store Object }.
135
+ to raise_error(ArgumentError, "Cannot determine a Site or Node class for storage type: Object")
136
+ end
137
+ end
138
+ end
139
+ end