circuit 0.2.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 (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