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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +383 -32
- data/bin/brainstem +6 -0
- data/brainstem.gemspec +2 -0
- data/docs/api_doc_generator.markdown +175 -0
- data/docs/brainstem_executable.markdown +32 -0
- data/docs/docgen.png +0 -0
- data/docs/docgen_ascii.txt +63 -0
- data/docs/executable.png +0 -0
- data/docs/executable_ascii.txt +10 -0
- data/lib/brainstem/api_docs.rb +146 -0
- data/lib/brainstem/api_docs/abstract_collection.rb +116 -0
- data/lib/brainstem/api_docs/atlas.rb +158 -0
- data/lib/brainstem/api_docs/builder.rb +167 -0
- data/lib/brainstem/api_docs/controller.rb +122 -0
- data/lib/brainstem/api_docs/controller_collection.rb +40 -0
- data/lib/brainstem/api_docs/endpoint.rb +234 -0
- data/lib/brainstem/api_docs/endpoint_collection.rb +58 -0
- data/lib/brainstem/api_docs/exceptions.rb +8 -0
- data/lib/brainstem/api_docs/formatters/abstract_formatter.rb +64 -0
- data/lib/brainstem/api_docs/formatters/markdown/controller_formatter.rb +76 -0
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter.rb +73 -0
- data/lib/brainstem/api_docs/formatters/markdown/endpoint_formatter.rb +169 -0
- data/lib/brainstem/api_docs/formatters/markdown/helper.rb +76 -0
- data/lib/brainstem/api_docs/formatters/markdown/presenter_formatter.rb +200 -0
- data/lib/brainstem/api_docs/introspectors/abstract_introspector.rb +100 -0
- data/lib/brainstem/api_docs/introspectors/rails_introspector.rb +232 -0
- data/lib/brainstem/api_docs/presenter.rb +225 -0
- data/lib/brainstem/api_docs/presenter_collection.rb +97 -0
- data/lib/brainstem/api_docs/resolver.rb +73 -0
- data/lib/brainstem/api_docs/sinks/abstract_sink.rb +37 -0
- data/lib/brainstem/api_docs/sinks/controller_presenter_multifile_sink.rb +93 -0
- data/lib/brainstem/api_docs/sinks/stdout_sink.rb +44 -0
- data/lib/brainstem/cli.rb +146 -0
- data/lib/brainstem/cli/abstract_command.rb +97 -0
- data/lib/brainstem/cli/generate_api_docs_command.rb +169 -0
- data/lib/brainstem/concerns/controller_dsl.rb +300 -0
- data/lib/brainstem/concerns/controller_param_management.rb +30 -9
- data/lib/brainstem/concerns/formattable.rb +38 -0
- data/lib/brainstem/concerns/inheritable_configuration.rb +3 -2
- data/lib/brainstem/concerns/optional.rb +43 -0
- data/lib/brainstem/concerns/presenter_dsl.rb +76 -15
- data/lib/brainstem/controller_methods.rb +6 -3
- data/lib/brainstem/dsl/association.rb +6 -3
- data/lib/brainstem/dsl/associations_block.rb +6 -3
- data/lib/brainstem/dsl/base_block.rb +2 -4
- data/lib/brainstem/dsl/conditional.rb +7 -3
- data/lib/brainstem/dsl/conditionals_block.rb +4 -4
- data/lib/brainstem/dsl/configuration.rb +184 -8
- data/lib/brainstem/dsl/field.rb +6 -3
- data/lib/brainstem/dsl/fields_block.rb +2 -3
- data/lib/brainstem/help_text.txt +8 -0
- data/lib/brainstem/presenter.rb +27 -6
- data/lib/brainstem/presenter_validator.rb +5 -2
- data/lib/brainstem/time_classes.rb +1 -1
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/api_docs/abstract_collection_spec.rb +156 -0
- data/spec/brainstem/api_docs/atlas_spec.rb +353 -0
- data/spec/brainstem/api_docs/builder_spec.rb +100 -0
- data/spec/brainstem/api_docs/controller_collection_spec.rb +92 -0
- data/spec/brainstem/api_docs/controller_spec.rb +225 -0
- data/spec/brainstem/api_docs/endpoint_collection_spec.rb +144 -0
- data/spec/brainstem/api_docs/endpoint_spec.rb +346 -0
- data/spec/brainstem/api_docs/formatters/abstract_formatter_spec.rb +30 -0
- data/spec/brainstem/api_docs/formatters/markdown/controller_formatter_spec.rb +126 -0
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_collection_formatter_spec.rb +85 -0
- data/spec/brainstem/api_docs/formatters/markdown/endpoint_formatter_spec.rb +261 -0
- data/spec/brainstem/api_docs/formatters/markdown/helper_spec.rb +100 -0
- data/spec/brainstem/api_docs/formatters/markdown/presenter_formatter_spec.rb +485 -0
- data/spec/brainstem/api_docs/introspectors/abstract_introspector_spec.rb +192 -0
- data/spec/brainstem/api_docs/introspectors/rails_introspector_spec.rb +170 -0
- data/spec/brainstem/api_docs/presenter_collection_spec.rb +84 -0
- data/spec/brainstem/api_docs/presenter_spec.rb +519 -0
- data/spec/brainstem/api_docs/resolver_spec.rb +72 -0
- data/spec/brainstem/api_docs/sinks/abstract_sink_spec.rb +16 -0
- data/spec/brainstem/api_docs/sinks/controller_presenter_multifile_sink_spec.rb +56 -0
- data/spec/brainstem/api_docs/sinks/stdout_sink_spec.rb +22 -0
- data/spec/brainstem/api_docs_spec.rb +58 -0
- data/spec/brainstem/cli/abstract_command_spec.rb +91 -0
- data/spec/brainstem/cli/generate_api_docs_command_spec.rb +125 -0
- data/spec/brainstem/cli_spec.rb +67 -0
- data/spec/brainstem/concerns/controller_dsl_spec.rb +471 -0
- data/spec/brainstem/concerns/controller_param_management_spec.rb +36 -16
- data/spec/brainstem/concerns/formattable_spec.rb +30 -0
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +104 -4
- data/spec/brainstem/concerns/optional_spec.rb +48 -0
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +202 -31
- data/spec/brainstem/dsl/association_spec.rb +18 -2
- data/spec/brainstem/dsl/conditional_spec.rb +25 -2
- data/spec/brainstem/dsl/configuration_spec.rb +1 -1
- data/spec/brainstem/dsl/field_spec.rb +18 -2
- data/spec/brainstem/presenter_collection_spec.rb +10 -2
- data/spec/brainstem/presenter_spec.rb +32 -0
- data/spec/brainstem/presenter_validator_spec.rb +12 -7
- data/spec/dummy/rails.rb +49 -0
- data/spec/shared/atlas_taker.rb +18 -0
- data/spec/shared/formattable.rb +14 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/spec_helpers/db.rb +1 -1
- data/spec/spec_helpers/presenters.rb +20 -14
- metadata +106 -6
data/lib/brainstem/dsl/field.rb
CHANGED
|
@@ -5,16 +5,19 @@ module Brainstem
|
|
|
5
5
|
class Field
|
|
6
6
|
include Brainstem::Concerns::Lookup
|
|
7
7
|
|
|
8
|
-
attr_reader :name, :type, :
|
|
8
|
+
attr_reader :name, :type, :conditionals, :options
|
|
9
9
|
|
|
10
|
-
def initialize(name, type,
|
|
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,
|
|
6
|
-
|
|
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)
|
data/lib/brainstem/presenter.rb
CHANGED
|
@@ -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,
|
|
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][
|
|
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][
|
|
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][
|
|
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
|
-
|
|
418
|
+
self.class.presenter_collection
|
|
398
419
|
end
|
|
399
420
|
|
|
400
421
|
# @api protected
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
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
|
data/lib/brainstem/version.rb
CHANGED
|
@@ -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
|