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.
- 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
|