brainstem 1.0.0.pre.1 → 1.0.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 (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