brainstem 1.0.0.pre.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +383 -32
  5. data/bin/brainstem +6 -0
  6. data/brainstem.gemspec +2 -0
  7. data/docs/api_doc_generator.markdown +175 -0
  8. data/docs/brainstem_executable.markdown +32 -0
  9. data/docs/docgen.png +0 -0
  10. data/docs/docgen_ascii.txt +63 -0
  11. data/docs/executable.png +0 -0
  12. data/docs/executable_ascii.txt +10 -0
  13. data/lib/brainstem/api_docs.rb +146 -0
  14. data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
  15. data/lib/brainstem/api_docs/atlas.rb +158 -0
  16. data/lib/brainstem/api_docs/builder.rb +167 -0
  17. data/lib/brainstem/api_docs/controller.rb +122 -0
  18. data/lib/brainstem/api_docs/controller_collection.rb +40 -0
  19. data/lib/brainstem/api_docs/endpoint.rb +234 -0
  20. data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
  21. data/lib/brainstem/api_docs/exceptions.rb +8 -0
  22. data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
  23. data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
  24. data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
  25. data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
  26. data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
  27. data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
  28. data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
  29. data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
  30. data/lib/brainstem/api_docs/presenter.rb +225 -0
  31. data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
  32. data/lib/brainstem/api_docs/resolver.rb +73 -0
  33. data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
  34. data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
  35. data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
  36. data/lib/brainstem/cli.rb +146 -0
  37. data/lib/brainstem/cli/abstract_command.rb +97 -0
  38. data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
  39. data/lib/brainstem/concerns/controller_dsl.rb +300 -0
  40. data/lib/brainstem/concerns/controller_param_management.rb +30 -9
  41. data/lib/brainstem/concerns/formattable.rb +38 -0
  42. data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
  43. data/lib/brainstem/concerns/optional.rb +43 -0
  44. data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
  45. data/lib/brainstem/controller_methods.rb +6 -3
  46. data/lib/brainstem/dsl/association.rb +6 -3
  47. data/lib/brainstem/dsl/associations_block.rb +6 -3
  48. data/lib/brainstem/dsl/base_block.rb +2 -4
  49. data/lib/brainstem/dsl/conditional.rb +7 -3
  50. data/lib/brainstem/dsl/conditionals_block.rb +4 -4
  51. data/lib/brainstem/dsl/configuration.rb +184 -8
  52. data/lib/brainstem/dsl/field.rb +6 -3
  53. data/lib/brainstem/dsl/fields_block.rb +2 -3
  54. data/lib/brainstem/help_text.txt +8 -0
  55. data/lib/brainstem/presenter.rb +27 -6
  56. data/lib/brainstem/presenter_validator.rb +5 -2
  57. data/lib/brainstem/time_classes.rb +1 -1
  58. data/lib/brainstem/version.rb +1 -1
  59. data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
  60. data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
  61. data/spec/brainstem/api_docs/builder_spec.rb +100 -0
  62. data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
  63. data/spec/brainstem/api_docs/controller_spec.rb +225 -0
  64. data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
  65. data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
  66. data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
  67. data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
  68. data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
  69. data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
  70. data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
  71. data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
  72. data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
  73. data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
  74. data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
  75. data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
  76. data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
  77. data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
  78. data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
  79. data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
  80. data/spec/brainstem/api_docs_spec.rb +58 -0
  81. data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
  82. data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
  83. data/spec/brainstem/cli_spec.rb +67 -0
  84. data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
  85. data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
  86. data/spec/brainstem/concerns/formattable_spec.rb +30 -0
  87. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
  88. data/spec/brainstem/concerns/optional_spec.rb +48 -0
  89. data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
  90. data/spec/brainstem/dsl/association_spec.rb +18 -2
  91. data/spec/brainstem/dsl/conditional_spec.rb +25 -2
  92. data/spec/brainstem/dsl/configuration_spec.rb +1 -1
  93. data/spec/brainstem/dsl/field_spec.rb +18 -2
  94. data/spec/brainstem/presenter_collection_spec.rb +10 -2
  95. data/spec/brainstem/presenter_spec.rb +32 -0
  96. data/spec/brainstem/presenter_validator_spec.rb +12 -7
  97. data/spec/dummy/rails.rb +49 -0
  98. data/spec/shared/atlas_taker.rb +18 -0
  99. data/spec/shared/formattable.rb +14 -0
  100. data/spec/spec_helper.rb +2 -0
  101. data/spec/spec_helpers/db.rb +1 -1
  102. data/spec/spec_helpers/presenters.rb +20 -14
  103. metadata +106 -6
@@ -5,16 +5,19 @@ module Brainstem
5
5
  class Field
6
6
  include Brainstem::Concerns::Lookup
7
7
 
8
- attr_reader :name, :type, :description, :conditionals, :options
8
+ attr_reader :name, :type, :conditionals, :options
9
9
 
10
- def initialize(name, type, description, options)
10
+ def initialize(name, type, options)
11
11
  @name = name.to_s
12
12
  @type = type
13
- @description = description
14
13
  @conditionals = [options[:if]].flatten.compact
15
14
  @options = options
16
15
  end
17
16
 
17
+ def description
18
+ options[:info].presence
19
+ end
20
+
18
21
  def conditional?
19
22
  conditionals.length > 0
20
23
  end
@@ -2,9 +2,8 @@ module Brainstem
2
2
  module Concerns
3
3
  module PresenterDSL
4
4
  class FieldsBlock < BaseBlock
5
- def field(name, type, *args)
6
- description, options = parse_args(args)
7
- configuration[name] = DSL::Field.new(name, type, description, smart_merge(block_options, options))
5
+ def field(name, type, options = {})
6
+ configuration[name] = DSL::Field.new(name, type, smart_merge(block_options, format_options(options)))
8
7
  end
9
8
 
10
9
  def fields(name, &block)
@@ -0,0 +1,8 @@
1
+
2
+ Usage: EXECUTABLE_NAME [command [options]]
3
+
4
+ Commonly used commands are:
5
+
6
+ generate: generates API documentation.
7
+
8
+ See EXECUTABLE_NAME COMMAND --help for more information on a specific command.
@@ -1,7 +1,9 @@
1
1
  require 'date'
2
+ require 'set' # For possible brainstem keys
2
3
  require 'brainstem/time_classes'
3
4
  require 'brainstem/preloader'
4
5
  require 'brainstem/concerns/presenter_dsl'
6
+ require 'active_support/core_ext/hash/except'
5
7
 
6
8
  module Brainstem
7
9
  # @abstract Subclass and override {#present} to implement a presenter.
@@ -24,6 +26,20 @@ module Brainstem
24
26
  @presents
25
27
  end
26
28
 
29
+
30
+ #
31
+ # Returns the set of possible brainstem keys for the classes presented.
32
+ #
33
+ # If the presenter specifies a key, that will be returned as the only
34
+ # member of the set.
35
+ #
36
+ def self.possible_brainstem_keys
37
+ @possible_brainstem_keys ||= begin
38
+ Set.new(presents.map(&presenter_collection.method(:brainstem_key_for!)))
39
+ end
40
+ end
41
+
42
+
27
43
  # Return the second-to-last module in the name of this presenter, which Brainstem considers to be the 'namespace'.
28
44
  # E.g., Api::V1::FooPresenter has a namespace of "V1".
29
45
  # @return [String] The name of the second-to-last module containing this presenter.
@@ -140,10 +156,9 @@ module Brainstem
140
156
 
141
157
  apply_default_filters = options.fetch(:apply_default_filters) { true }
142
158
 
143
- configuration[:filters].each do |filter_name, filter|
159
+ configuration[:filters].each do |filter_name, filter_options|
144
160
  user_value = format_filter_value(user_params[filter_name])
145
161
 
146
- filter_options = filter[0]
147
162
  filter_arg = apply_default_filters && user_value.nil? ? filter_options[:default] : user_value
148
163
  filters_hash[filter_name] = filter_arg unless filter_arg.nil?
149
164
  end
@@ -175,10 +190,10 @@ module Brainstem
175
190
 
176
191
  requested_filters = extract_filters(user_params, options)
177
192
  requested_filters.each do |filter_name, filter_arg|
178
- filter_lambda = configuration[:filters][filter_name][1]
193
+ filter_lambda = configuration[:filters][filter_name][:value]
179
194
 
180
195
  args_for_filter_lambda = [filter_arg]
181
- args_for_filter_lambda << requested_filters if configuration[:filters][filter_name][0][:include_params]
196
+ args_for_filter_lambda << requested_filters if configuration[:filters][filter_name][:include_params]
182
197
 
183
198
  if filter_lambda
184
199
  scope = helper_instance.instance_exec(scope, *args_for_filter_lambda, &filter_lambda)
@@ -193,7 +208,7 @@ module Brainstem
193
208
  # Given user params, apply a validated sort order to the given scope.
194
209
  def apply_ordering_to_scope(scope, user_params)
195
210
  sort_name, direction = calculate_sort_name_and_direction(user_params)
196
- order = configuration[:sort_orders][sort_name]
211
+ order = configuration[:sort_orders].fetch(sort_name, {})[:value]
197
212
 
198
213
  ordered_scope = case order
199
214
  when Proc
@@ -391,10 +406,16 @@ module Brainstem
391
406
  end
392
407
  end
393
408
 
409
+
410
+ def self.presenter_collection
411
+ Brainstem.presenter_collection(namespace)
412
+ end
413
+
414
+
394
415
  # @api protected
395
416
  # Find the global presenter collection for our namespace.
396
417
  def presenter_collection
397
- Brainstem.presenter_collection(self.class.namespace)
418
+ self.class.presenter_collection
398
419
  end
399
420
 
400
421
  # @api protected
@@ -1,5 +1,8 @@
1
- # Brainstem::PresenterValidator is a helper class that performs validity checks on a given presenter class.
1
+ require 'active_model'
2
2
 
3
+ #
4
+ # Brainstem::PresenterValidator is a helper class that performs validity checks on a given presenter class.
5
+ #
3
6
  module Brainstem
4
7
  class PresenterValidator
5
8
  include ActiveModel::Validations
@@ -93,4 +96,4 @@ module Brainstem
93
96
  end
94
97
  end
95
98
  end
96
- end
99
+ end
@@ -11,4 +11,4 @@ module Brainstem
11
11
  rescue LoadError
12
12
  end
13
13
  end
14
- end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Brainstem
2
- VERSION = "1.0.0.pre.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/abstract_collection'
3
+
4
+ module Brainstem
5
+ module ApiDocs
6
+ describe AbstractCollection do
7
+ let(:atlas) { Object.new }
8
+ let(:member) { Object.new }
9
+ let(:member_2) { Object.new }
10
+ let(:members) { [ member, member_2 ] }
11
+
12
+ subject { described_class.new(atlas) }
13
+
14
+ describe "#last" do
15
+ it "retrieves the last member" do
16
+ subject << members
17
+ expect(subject.last).to eq member_2
18
+ end
19
+ end
20
+
21
+
22
+ describe "#<<" do
23
+ it "adds a member to the collection" do
24
+ subject << member
25
+ expect(subject.count).to eq 1
26
+ end
27
+
28
+ it "adds multiple members to the collection" do
29
+ subject << members
30
+ expect(subject.count).to eq 2
31
+ end
32
+ end
33
+
34
+
35
+ describe "#each" do
36
+ it "iterates over each member" do
37
+ subject << members
38
+ expect { |block| subject.each(&block) }.to yield_successive_args(*members)
39
+ end
40
+ end
41
+
42
+
43
+ describe "iteration" do
44
+ before do
45
+ subject << member
46
+ end
47
+
48
+
49
+ describe "#filenames" do
50
+ it "maps all its members" do
51
+ stub(member).formatted_as(:markdown, {}) { "blah" }
52
+ mock(member).suggested_filename(:markdown) { "member.markdown" }
53
+ expect(subject.filenames(:markdown)).to eq [ "member.markdown" ]
54
+ end
55
+ end
56
+
57
+
58
+ describe "#each_filename" do
59
+ it "maps all its controllers and yields each in turn" do
60
+ stub(member).formatted_as(:markdown, {}) { "blah" }
61
+ mock(member).suggested_filename(:markdown) { "member.markdown" }
62
+ expect { |block| subject.each_filename(:markdown, &block) }.to \
63
+ yield_with_args("member.markdown")
64
+ end
65
+ end
66
+
67
+
68
+ describe "#formatted" do
69
+ it "maps all its members" do
70
+ mock(member).formatted_as(:markdown, {}) { "blah" }
71
+ expect(subject.formatted(:markdown)).to eq [ "blah" ]
72
+ end
73
+
74
+ it "passes options on to the formatter" do
75
+ mock(member).formatted_as(:markdown, blah: true) { "blah" }
76
+ subject.formatted(:markdown, blah: true)
77
+ end
78
+
79
+ it "rejects empty" do
80
+ mock(member).formatted_as(:markdown, {}) { "" }
81
+ expect(subject.formatted(:markdown)).to eq []
82
+ end
83
+ end
84
+
85
+
86
+ describe "#formatted_with_filename" do
87
+ it "maps all its members and filenames" do
88
+ mock(member).formatted_as(:markdown, {}) { "blah" }
89
+ mock(member).suggested_filename(:markdown) { "member.markdown" }
90
+
91
+ expect(subject.formatted_with_filename(:markdown)).to eq [ [ "blah", "member.markdown" ] ]
92
+ end
93
+
94
+ it "passes options on to the formatter" do
95
+ mock(member).formatted_as(:markdown, blah: true) { "blah" }
96
+ stub(member).suggested_filename(:markdown) { "member.markdown" }
97
+
98
+ subject.formatted_with_filename(:markdown, blah: true)
99
+ end
100
+
101
+ it "rejects empty" do
102
+ mock(member).formatted_as(:markdown, {}) { "" }
103
+ stub(member).suggested_filename(:markdown) { "member.markdown" }
104
+ expect(subject.formatted_with_filename(:markdown)).to eq []
105
+ end
106
+ end
107
+
108
+
109
+ describe "#each_formatted" do
110
+ it "maps all its controllers and yields each in turn" do
111
+ mock(member).formatted_as(:markdown, {}) { "blah" }
112
+ expect { |block| subject.each_formatted(:markdown, &block) }.to \
113
+ yield_with_args("blah")
114
+ end
115
+
116
+ it "passes options on to the formatter" do
117
+ mock(member).formatted_as(:markdown, blah: true) { "blah" }
118
+
119
+ subject.each_formatted(:markdown, blah: true) {|_, _| nil }
120
+ end
121
+ end
122
+
123
+
124
+ describe "#each_formatted_with_filename" do
125
+ it "maps all its controllers and filenames and yields each in turn" do
126
+ mock(member).formatted_as(:markdown, {}) { "blah" }
127
+ mock(member).suggested_filename(:markdown) { "member.markdown" }
128
+
129
+ expect { |block| subject.each_formatted_with_filename(:markdown, &block) }.to \
130
+ yield_with_args("blah", "member.markdown")
131
+ end
132
+
133
+ it "passes options on to the formatter" do
134
+ mock(member).formatted_as(:markdown, blah: true) { "blah" }
135
+ stub(member).suggested_filename(:markdown) { "member.markdown" }
136
+
137
+ subject.each_formatted_with_filename(:markdown, blah: true) {|_, _| nil }
138
+ end
139
+ end
140
+ end
141
+
142
+
143
+ describe ".with_members" do
144
+ subject { described_class.with_members(atlas, member) }
145
+
146
+ it "creates a new instance with passed members" do
147
+ expect(subject).to be_a described_class
148
+ expect(subject.send(:members)).to eq [ member ]
149
+ end
150
+
151
+ end
152
+
153
+ it_behaves_like "atlas taker"
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,353 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/api_docs/atlas'
3
+ require 'dummy/rails'
4
+
5
+ module Brainstem
6
+ module ApiDocs
7
+ describe Atlas do
8
+ let(:introspector) { Object.new }
9
+
10
+ subject { Atlas.new(introspector) }
11
+
12
+ describe "#initialize" do
13
+ describe "mapping" do
14
+ before do
15
+ stub(introspector).valid? { true }
16
+ end
17
+
18
+ it "parses routes" do
19
+ any_instance_of(Atlas) do |instance|
20
+ mock(instance).parse_routes!
21
+ stub(instance).extract_presenters!
22
+ stub(instance).validate!
23
+ end
24
+
25
+ subject
26
+ end
27
+
28
+ it "extracts presenters" do
29
+ any_instance_of(Atlas) do |instance|
30
+ stub(instance).parse_routes!
31
+ mock(instance).extract_presenters!
32
+ stub(instance).validate!
33
+ end
34
+
35
+ subject
36
+ end
37
+ end
38
+
39
+
40
+ describe "validation" do
41
+ before do
42
+ any_instance_of(Atlas) do |instance|
43
+ stub(instance).parse_routes!
44
+ stub(instance).extract_presenters!
45
+ end
46
+ end
47
+
48
+ describe "when atlas is invalid" do
49
+ before do
50
+ stub.any_instance_of(Atlas).valid? { false }
51
+ end
52
+
53
+ it "raises an error" do
54
+ expect { subject }.to raise_error InvalidAtlasError
55
+ end
56
+ end
57
+
58
+ describe "when atlas is valid" do
59
+ before do
60
+ stub.any_instance_of(Atlas).valid? { true }
61
+ end
62
+
63
+ it "does not raise an error" do
64
+ expect { subject }.not_to raise_error
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+
71
+ describe "#parse_routes!" do
72
+ let(:route_1) { {
73
+ path: "/endpoint1",
74
+ http_methods: ["POST"],
75
+ controller: FakeDescendantController,
76
+ controller_name: "fake_descendant",
77
+ action: "create" } }
78
+
79
+ let(:route_2) { {
80
+ path: "/endpoint2",
81
+ http_methods: ["GET"],
82
+ controller: FakeDescendantController,
83
+ controller_name: "fake_descendant",
84
+ action: "show" } }
85
+
86
+ before do
87
+ any_instance_of(Atlas) do |instance|
88
+ stub(instance).extract_presenters!
89
+ stub(instance).validate!
90
+ end
91
+
92
+ stub(introspector) do |i|
93
+ i.routes { [ route_1, route_2 ] }
94
+ i.controllers { [ FakeDescendantController ] }
95
+ i.presenters { }
96
+ i.valid? { true }
97
+ end
98
+
99
+ end
100
+
101
+ context "when route is valid" do
102
+ it "constructs an Endpoint" do
103
+ expect(subject.endpoints.count).to eq 2
104
+ expect(subject.endpoints).to all(be_an(Endpoint))
105
+ end
106
+
107
+ it "adds the endpoint to the controller" do
108
+ expect(subject.controllers.first.endpoints.count).to eq 2
109
+ expect(subject.controllers.first.endpoints).to all(be_an(Endpoint))
110
+ end
111
+
112
+ context "when controller not in controllers list" do
113
+ let(:route_2) { {
114
+ path: "/endpoint2",
115
+ http_methods: ["GET"],
116
+ controller: TrueClass,
117
+ controller_name: "true_class",
118
+ action: "show" } }
119
+
120
+ it "discards the route" do
121
+ expect(subject.endpoints.count).to eq 1
122
+ end
123
+ end
124
+
125
+ context "when controller does not match all passed matches" do
126
+ let(:route_2) { {
127
+ path: "/endpoint2",
128
+ http_methods: ["GET"],
129
+ controller: TrueClass,
130
+ controller_name: "true_class",
131
+ action: "show" } }
132
+
133
+ before do
134
+ stub.any_instance_of(Atlas)
135
+ .controller_matches { [ Regexp.new('FakeDescendant', 'i') ] }
136
+ end
137
+
138
+ it "discards the route" do
139
+ expect(subject.endpoints.count).to eq 1
140
+ end
141
+ end
142
+
143
+ context "when controller does match all passed matches" do
144
+ before do
145
+ stub.any_instance_of(Atlas)
146
+ .controller_matches { [ Regexp.new('FakeDescendant', 'i') ] }
147
+ end
148
+
149
+ it "keeps the route" do
150
+ expect(subject.endpoints.count).to eq 2
151
+ end
152
+ end
153
+
154
+ context "when an Endpoint for that route already exists, but with a different HTTP verb" do
155
+ let(:route_2) { {
156
+ path: "/endpoint1",
157
+ http_methods: ["PATCH"],
158
+ controller: FakeDescendantController,
159
+ controller_name: "fake_descendant",
160
+ action: "create" } }
161
+
162
+ it "merges the route" do
163
+ expect(subject.endpoints.count).to eq 1
164
+ expect(subject.endpoints.first.http_methods).to eq ["POST", "PATCH"]
165
+ end
166
+ end
167
+ end
168
+
169
+
170
+ context "when a route is invalid" do
171
+ let(:route_2) { {
172
+ path: "/endpoint2",
173
+ http_methods: ["GET"],
174
+ controller: TrueClass,
175
+ controller_name: "plainly_erroneous",
176
+ action: "show" } }
177
+
178
+ it "skips that route" do
179
+ expect(subject.endpoints.count).to eq 1
180
+ expect(subject.endpoints).to all(be_an(Endpoint))
181
+ end
182
+ end
183
+
184
+ context "when all routes are invalid" do
185
+ let(:route_1) { {
186
+ path: "/endpoint1",
187
+ http_methods: ["POST"],
188
+ controller: TrueClass,
189
+ controller_name: "true_class",
190
+ action: "create" } }
191
+
192
+ let(:route_2) { {
193
+ path: "/endpoint2",
194
+ http_methods: ["GET"],
195
+ controller: TrueClass,
196
+ controller_name: "true_class",
197
+ action: "show" } }
198
+
199
+ it "has an empty endpoints" do
200
+ expect(subject.endpoints.count).to eq 0
201
+ end
202
+ end
203
+ end
204
+
205
+
206
+ describe "#extract_presenters!" do
207
+ let(:endpoint_1) { Object.new }
208
+ let(:endpoint_2) { Object.new }
209
+ let(:presenter) { Object.new }
210
+ let(:target_class) { Class.new }
211
+ let(:presenter_collection) { Object.new }
212
+
213
+ before do
214
+ # This set-up is a bit of a smell.
215
+ stub(endpoint_1).declared_presented_class { target_class }
216
+ stub(endpoint_2).declared_presented_class { Class.new }
217
+
218
+ stub(target_class).to_s { "TargetClass" }
219
+
220
+ any_instance_of(described_class) do |instance|
221
+ stub(instance).parse_routes!
222
+ stub(instance).validate!
223
+
224
+ stub(instance).valid_presenter_pairs { {
225
+ "TargetClass" => target_class
226
+ } }
227
+
228
+ stub(instance).presenters { presenter_collection }
229
+ stub(instance).endpoints { [ endpoint_1, endpoint_2 ] }
230
+ end
231
+ end
232
+
233
+ it "creates a presenter for each valid presenter pair" do
234
+ stub(endpoint_1).presenter=(presenter)
235
+ mock(presenter_collection)
236
+ .find_or_create_from_presenter_collection("TargetClass", target_class) { presenter }
237
+
238
+ subject
239
+ end
240
+
241
+ it "sets the presenter on each endpoint that presents the same" do
242
+ mock(endpoint_1).presenter=(presenter)
243
+ dont_allow(endpoint_2).presenter=(presenter)
244
+
245
+ stub(presenter_collection)
246
+ .find_or_create_from_presenter_collection("TargetClass", target_class) { presenter }
247
+
248
+ subject
249
+ end
250
+ end
251
+
252
+
253
+ describe "#valid?" do
254
+ before do
255
+ any_instance_of(Atlas) do |instance|
256
+ stub(instance) do |i|
257
+ i.parse_routes!
258
+ i.extract_presenters!
259
+ i.validate!
260
+ end
261
+ end
262
+ end
263
+
264
+ context "when has at least one endpoint" do
265
+ before do
266
+ stub(subject).endpoints { [ Object.new ] }
267
+ end
268
+
269
+ it "is valid" do
270
+ expect(subject.send(:valid?)).to eq true
271
+ end
272
+ end
273
+
274
+ context "When has no endpoints" do
275
+ before do
276
+ stub(subject).endpoints { [ ] }
277
+ end
278
+
279
+ it "is invalid" do
280
+ expect(subject.send(:valid?)).to eq false
281
+ end
282
+ end
283
+ end
284
+
285
+
286
+ describe "#allow_route?" do
287
+ before do
288
+ any_instance_of(Atlas) do |instance|
289
+ stub(instance) do |i|
290
+ i.parse_routes!
291
+ i.extract_presenters!
292
+ i.validate!
293
+ end
294
+ end
295
+
296
+ stub(introspector) do |instance|
297
+ instance.controllers { [ FakeDescendantController ] }
298
+ end
299
+ end
300
+
301
+ context "when controller not in the controllers list" do
302
+ it "is false" do
303
+ expect(subject.send(:allow_route?, controller: FakeNonDescendantController)).to eq false
304
+ end
305
+ end
306
+
307
+ context "when controller in the controllers list" do
308
+ context "when matches all controller_matches" do
309
+ before do
310
+ stub.any_instance_of(Atlas).controller_matches do
311
+ [ Regexp.new('descendant', 'i') ]
312
+ end
313
+ end
314
+
315
+ it "is true" do
316
+ expect(subject.send(:allow_route?, controller: FakeDescendantController)).to eq true
317
+ end
318
+ end
319
+
320
+ context "when controller does not match all controller_matches" do
321
+ before do
322
+ stub.any_instance_of(Atlas).controller_matches do
323
+ [ Regexp.new('wildly_inaccurate_regexp_match', 'i') ]
324
+ end
325
+ end
326
+
327
+ it "is false" do
328
+ expect(subject.send(:allow_route?, controller: FakeDescendantController)).to eq false
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+
335
+ describe "#find_by_class" do
336
+ before do
337
+ any_instance_of(Atlas) do |instance|
338
+ stub(instance) do |i|
339
+ i.parse_routes!
340
+ i.extract_presenters!
341
+ i.validate!
342
+ end
343
+ end
344
+ end
345
+
346
+ it "delegates to the resolver" do
347
+ mock(subject.resolver).find_by_class(nil)
348
+ subject.find_by_class(nil)
349
+ end
350
+ end
351
+ end
352
+ end
353
+ end