praxis 2.0.pre.17 → 2.0.pre.21
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/.rubocop.yml +54 -0
- data/.simplecov +3 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +2 -79
- data/Gemfile +5 -1
- data/Guardfile +6 -4
- data/LICENSE +0 -2
- data/MAINTAINERS.md +1 -0
- data/README.md +15 -22
- data/Rakefile +4 -2
- data/bin/praxis +55 -58
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +5 -6
- data/lib/praxis/action_definition.rb +65 -95
- data/lib/praxis/api_definition.rb +21 -29
- data/lib/praxis/api_general_info.rb +55 -66
- data/lib/praxis/application.rb +15 -32
- data/lib/praxis/blueprint.rb +80 -73
- data/lib/praxis/bootloader.rb +24 -33
- data/lib/praxis/bootloader_stages/environment.rb +5 -10
- data/lib/praxis/bootloader_stages/file_loader.rb +3 -6
- data/lib/praxis/bootloader_stages/plugin_config_load.rb +4 -6
- data/lib/praxis/bootloader_stages/plugin_config_prepare.rb +2 -2
- data/lib/praxis/bootloader_stages/plugin_loader.rb +3 -7
- data/lib/praxis/bootloader_stages/plugin_setup.rb +3 -3
- data/lib/praxis/bootloader_stages/routing.rb +5 -8
- data/lib/praxis/bootloader_stages/subgroup_loader.rb +2 -10
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +15 -19
- data/lib/praxis/callbacks.rb +12 -11
- data/lib/praxis/collection.rb +11 -14
- data/lib/praxis/config.rb +17 -28
- data/lib/praxis/config_hash.rb +2 -1
- data/lib/praxis/controller.rb +7 -6
- data/lib/praxis/dispatcher.rb +34 -42
- data/lib/praxis/docs/open_api/info_object.rb +11 -8
- data/lib/praxis/docs/open_api/media_type_object.rb +18 -17
- data/lib/praxis/docs/open_api/operation_object.rb +7 -4
- data/lib/praxis/docs/open_api/parameter_object.rb +17 -14
- data/lib/praxis/docs/open_api/paths_object.rb +11 -9
- data/lib/praxis/docs/open_api/request_body_object.rb +14 -13
- data/lib/praxis/docs/open_api/response_object.rb +24 -18
- data/lib/praxis/docs/open_api/responses_object.rb +3 -1
- data/lib/praxis/docs/open_api/schema_object.rb +61 -29
- data/lib/praxis/docs/open_api/server_object.rb +5 -2
- data/lib/praxis/docs/open_api/tag_object.rb +9 -6
- data/lib/praxis/docs/open_api_generator.rb +114 -150
- data/lib/praxis/endpoint_definition.rb +60 -77
- data/lib/praxis/error_handler.rb +2 -2
- data/lib/praxis/exception.rb +2 -0
- data/lib/praxis/exceptions/config.rb +3 -1
- data/lib/praxis/exceptions/config_load.rb +2 -0
- data/lib/praxis/exceptions/config_validation.rb +3 -1
- data/lib/praxis/exceptions/invalid_configuration.rb +3 -1
- data/lib/praxis/exceptions/invalid_response.rb +3 -1
- data/lib/praxis/exceptions/invalid_trait.rb +3 -1
- data/lib/praxis/exceptions/stage_not_found.rb +3 -1
- data/lib/praxis/exceptions/validation.rb +4 -3
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +163 -149
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +18 -13
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +13 -9
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +14 -11
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +12 -9
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +8 -5
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +89 -65
- data/lib/praxis/extensions/attribute_filtering/filters_parser.rb +68 -62
- data/lib/praxis/extensions/attribute_filtering.rb +3 -1
- data/lib/praxis/extensions/field_expansion.rb +6 -4
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +10 -8
- data/lib/praxis/extensions/field_selection/field_selector.rb +91 -92
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +12 -12
- data/lib/praxis/extensions/field_selection.rb +3 -1
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +6 -4
- data/lib/praxis/extensions/pagination/header_generator.rb +16 -11
- data/lib/praxis/extensions/pagination/ordering_params.rb +29 -28
- data/lib/praxis/extensions/pagination/pagination_handler.rb +44 -42
- data/lib/praxis/extensions/pagination/pagination_params.rb +29 -48
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +8 -7
- data/lib/praxis/extensions/pagination.rb +10 -15
- data/lib/praxis/extensions/rails_compat/request_methods.rb +3 -4
- data/lib/praxis/extensions/rails_compat.rb +2 -0
- data/lib/praxis/extensions/rendering.rb +12 -12
- data/lib/praxis/field_expander.rb +8 -9
- data/lib/praxis/file_group.rb +8 -12
- data/lib/praxis/finalizable.rb +1 -0
- data/lib/praxis/handlers/json.rb +5 -2
- data/lib/praxis/handlers/plain.rb +2 -1
- data/lib/praxis/handlers/www_form.rb +6 -3
- data/lib/praxis/handlers/{xml-sample.rb → xml_sample.rb} +26 -22
- data/lib/praxis/mapper/active_model_compat.rb +13 -10
- data/lib/praxis/mapper/resource.rb +196 -181
- data/lib/praxis/mapper/selector_generator.rb +106 -112
- data/lib/praxis/mapper/sequel_compat.rb +70 -67
- data/lib/praxis/media_type.rb +2 -2
- data/lib/praxis/media_type_identifier.rb +26 -22
- data/lib/praxis/middleware_app.rb +18 -15
- data/lib/praxis/multipart/parser.rb +46 -51
- data/lib/praxis/multipart/part.rb +78 -110
- data/lib/praxis/notifications.rb +2 -4
- data/lib/praxis/plugin.rb +11 -18
- data/lib/praxis/plugin_concern.rb +12 -15
- data/lib/praxis/plugins/mapper_plugin.rb +15 -13
- data/lib/praxis/plugins/pagination_plugin.rb +8 -6
- data/lib/praxis/plugins/rails_plugin.rb +33 -28
- data/lib/praxis/renderer.rb +11 -15
- data/lib/praxis/request.rb +48 -44
- data/lib/praxis/request_stages/action.rb +4 -6
- data/lib/praxis/request_stages/load_request.rb +2 -4
- data/lib/praxis/request_stages/request_stage.rb +19 -23
- data/lib/praxis/request_stages/response.rb +4 -6
- data/lib/praxis/request_stages/validate.rb +3 -5
- data/lib/praxis/request_stages/validate_params_and_headers.rb +15 -22
- data/lib/praxis/request_stages/validate_payload.rb +25 -28
- data/lib/praxis/request_superclassing.rb +3 -3
- data/lib/praxis/resource_definition.rb +1 -0
- data/lib/praxis/response.rb +24 -26
- data/lib/praxis/response_definition.rb +77 -122
- data/lib/praxis/response_template.rb +11 -15
- data/lib/praxis/responses/http.rb +23 -44
- data/lib/praxis/responses/internal_server_error.rb +18 -21
- data/lib/praxis/responses/multipart_ok.rb +4 -9
- data/lib/praxis/responses/validation_error.rb +8 -15
- data/lib/praxis/route.rb +8 -10
- data/lib/praxis/router/rack.rb +13 -7
- data/lib/praxis/router/simple.rb +10 -5
- data/lib/praxis/router.rb +27 -34
- data/lib/praxis/routing_config.rb +52 -29
- data/lib/praxis/simple_media_type.rb +5 -8
- data/lib/praxis/stage.rb +17 -25
- data/lib/praxis/tasks/api_docs.rb +17 -16
- data/lib/praxis/tasks/console.rb +3 -1
- data/lib/praxis/tasks/environment.rb +2 -0
- data/lib/praxis/tasks/routes.rb +26 -24
- data/lib/praxis/tasks.rb +3 -1
- data/lib/praxis/trait.rb +37 -46
- data/lib/praxis/types/fuzzy_hash.rb +13 -14
- data/lib/praxis/types/media_type_common.rb +11 -10
- data/lib/praxis/types/multipart_array/part_definition.rb +14 -17
- data/lib/praxis/types/multipart_array.rb +100 -115
- data/lib/praxis/validation_handler.rb +5 -3
- data/lib/praxis/version.rb +3 -1
- data/lib/praxis.rb +4 -5
- data/praxis.gemspec +22 -21
- data/spec/functional_spec.rb +44 -56
- data/spec/praxis/action_definition_spec.rb +39 -48
- data/spec/praxis/api_definition_spec.rb +45 -47
- data/spec/praxis/api_general_info_spec.rb +28 -29
- data/spec/praxis/application_spec.rb +18 -14
- data/spec/praxis/blueprint_spec.rb +33 -34
- data/spec/praxis/bootloader_spec.rb +32 -30
- data/spec/praxis/callbacks_spec.rb +37 -37
- data/spec/praxis/collection_spec.rb +18 -25
- data/spec/praxis/config_hash_spec.rb +5 -4
- data/spec/praxis/config_spec.rb +27 -26
- data/spec/praxis/controller_spec.rb +8 -9
- data/spec/praxis/endpoint_definition_spec.rb +25 -32
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +171 -114
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +22 -21
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +112 -60
- data/spec/praxis/extensions/attribute_filtering/filters_parser_spec.rb +37 -38
- data/spec/praxis/extensions/field_expansion_spec.rb +8 -10
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +14 -13
- data/spec/praxis/extensions/field_selection/field_selector_spec.rb +9 -16
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +50 -49
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +32 -31
- data/spec/praxis/extensions/rendering_spec.rb +9 -9
- data/spec/praxis/extensions/support/spec_resources_active_model.rb +32 -49
- data/spec/praxis/extensions/support/spec_resources_sequel.rb +48 -48
- data/spec/praxis/field_expander_spec.rb +6 -5
- data/spec/praxis/file_group_spec.rb +3 -1
- data/spec/praxis/handlers/json_spec.rb +6 -5
- data/spec/praxis/mapper/resource_spec.rb +39 -29
- data/spec/praxis/mapper/selector_generator_spec.rb +80 -46
- data/spec/praxis/media_type_identifier_spec.rb +13 -10
- data/spec/praxis/media_type_spec.rb +12 -12
- data/spec/praxis/middleware_app_spec.rb +23 -22
- data/spec/praxis/multipart/parser_spec.rb +7 -9
- data/spec/praxis/notifications_spec.rb +4 -4
- data/spec/praxis/plugin_concern_spec.rb +5 -6
- data/spec/praxis/renderer_spec.rb +10 -9
- data/spec/praxis/request_spec.rb +38 -41
- data/spec/praxis/request_stages/action_spec.rb +14 -15
- data/spec/praxis/request_stages/request_stage_spec.rb +30 -41
- data/spec/praxis/request_stages/validate_spec.rb +3 -1
- data/spec/praxis/response_definition_spec.rb +79 -92
- data/spec/praxis/response_spec.rb +35 -40
- data/spec/praxis/responses/internal_server_error_spec.rb +6 -9
- data/spec/praxis/responses/validation_error_spec.rb +17 -18
- data/spec/praxis/route_spec.rb +4 -7
- data/spec/praxis/router_spec.rb +69 -79
- data/spec/praxis/routing_config_spec.rb +15 -14
- data/spec/praxis/stage_spec.rb +56 -53
- data/spec/praxis/trait_spec.rb +17 -17
- data/spec/praxis/types/fuzzy_hash_spec.rb +11 -9
- data/spec/praxis/types/multipart_array/part_definition_spec.rb +3 -2
- data/spec/praxis/types/multipart_array_spec.rb +33 -48
- data/spec/spec_app/app/concerns/authenticated.rb +5 -5
- data/spec/spec_app/app/concerns/basic_api.rb +3 -1
- data/spec/spec_app/app/concerns/log_wrapper.rb +5 -3
- data/spec/spec_app/app/controllers/base_class.rb +6 -5
- data/spec/spec_app/app/controllers/instances.rb +31 -34
- data/spec/spec_app/app/controllers/volumes.rb +6 -6
- data/spec/spec_app/app/responses/multipart.rb +1 -2
- data/spec/spec_app/app/responses/other_response.rb +2 -2
- data/spec/spec_app/config/environment.rb +19 -6
- data/spec/spec_app/config.ru +4 -3
- data/spec/spec_app/design/api.rb +13 -15
- data/spec/spec_app/design/media_types/instance.rb +6 -6
- data/spec/spec_app/design/media_types/volume.rb +2 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +2 -1
- data/spec/spec_app/design/resources/instances.rb +11 -17
- data/spec/spec_app/design/resources/volume_snapshots.rb +4 -5
- data/spec/spec_app/design/resources/volumes.rb +4 -5
- data/spec/spec_helper.rb +11 -13
- data/spec/support/be_deep_equal_matcher.rb +5 -0
- data/spec/support/spec_authorization_plugin.rb +7 -12
- data/spec/support/spec_blueprints.rb +5 -4
- data/spec/support/spec_complex_authentication_plugin.rb +17 -34
- data/spec/support/spec_endpoint_definitions.rb +2 -3
- data/spec/support/spec_media_types.rb +28 -35
- data/spec/support/spec_resources.rb +22 -16
- data/spec/support/spec_simple_authentication_plugin.rb +5 -9
- data/tasks/loader.thor +4 -2
- data/tasks/thor/app.rb +7 -5
- data/tasks/thor/example.rb +23 -22
- data/tasks/thor/model.rb +7 -7
- data/tasks/thor/scaffold.rb +23 -23
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +0 -8
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -2
- metadata +72 -84
- data/MAINTAINERS +0 -2
- data/TODO.md +0 -25
- data/spec/praxis/api_resource_spec.rb +0 -0
- data/spec/praxis/dispatcher_spec.rb +0 -0
- data/spec/spec_app/app/responses/bulk_response.rb +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'spec_helper'
|
|
3
4
|
|
|
4
5
|
describe Praxis::Responses::ValidationError do
|
|
5
|
-
|
|
6
|
-
let(:summary){ "Something happened here" }
|
|
6
|
+
let(:summary) { 'Something happened here' }
|
|
7
7
|
context 'a basic response' do
|
|
8
8
|
subject(:response) { Praxis::Responses::ValidationError.new(summary: summary) }
|
|
9
9
|
it 'always sets the json content type' do
|
|
@@ -18,10 +18,10 @@ describe Praxis::Responses::ValidationError do
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
context '.format!' do
|
|
21
|
-
let(:errors) { [1,2] }
|
|
22
|
-
let(:cause){ Exception.new(
|
|
23
|
-
let(:exception_message){
|
|
24
|
-
let(:exception){ nil }
|
|
21
|
+
let(:errors) { [1, 2] }
|
|
22
|
+
let(:cause) { Exception.new('cause message') }
|
|
23
|
+
let(:exception_message) { 'exception message' }
|
|
24
|
+
let(:exception) { nil }
|
|
25
25
|
subject(:response) { Praxis::Responses::ValidationError.new(summary: summary, errors: errors, exception: exception) }
|
|
26
26
|
before do
|
|
27
27
|
expect(response.body).to be_nil
|
|
@@ -29,18 +29,18 @@ describe Praxis::Responses::ValidationError do
|
|
|
29
29
|
context 'when errors' do
|
|
30
30
|
it 'it fills the errors key' do
|
|
31
31
|
response.format!
|
|
32
|
-
expect(response.body).to eq({name: 'ValidationError', summary: summary, errors: errors})
|
|
32
|
+
expect(response.body).to eq({ name: 'ValidationError', summary: summary, errors: errors })
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
context 'without errors but with an exception' do
|
|
37
37
|
let(:errors) { nil }
|
|
38
|
-
let(:exception){ double(
|
|
38
|
+
let(:exception) { double('exception', message: exception_message, cause: cause) }
|
|
39
39
|
before do
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
response.format!
|
|
41
|
+
expect(response.body.keys).to include(:name, :summary)
|
|
42
|
+
expect(response.body[:name]).to eq('ValidationError')
|
|
43
|
+
expect(response.body[:summary]).to eq(summary)
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
context 'that has a message' do
|
|
@@ -51,14 +51,14 @@ describe Praxis::Responses::ValidationError do
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
context 'that does not contain a message' do
|
|
54
|
-
let(:exception_message){ nil }
|
|
54
|
+
let(:exception_message) { nil }
|
|
55
55
|
it 'does not touch the errors key' do
|
|
56
56
|
expect(response.body).to_not have_key(:errors)
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
context 'without cause' do
|
|
61
|
-
let(:cause){ nil}
|
|
61
|
+
let(:cause) { nil }
|
|
62
62
|
it 'does not include it in the output' do
|
|
63
63
|
expect(response.body).to_not have_key(:cause)
|
|
64
64
|
end
|
|
@@ -67,15 +67,14 @@ describe Praxis::Responses::ValidationError do
|
|
|
67
67
|
context 'with a cause' do
|
|
68
68
|
it 'it fills the cause too' do
|
|
69
69
|
expect(response.body).to have_key(:cause)
|
|
70
|
-
expect(response.body[:cause]).to eq({name: cause.class.name, message: cause.message })
|
|
70
|
+
expect(response.body[:cause]).to eq({ name: cause.class.name, message: cause.message })
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
|
-
|
|
75
74
|
end
|
|
76
75
|
|
|
77
76
|
context 'its response template' do
|
|
78
|
-
let(:template){ Praxis::ApiDefinition.instance.responses[:validation_error] }
|
|
77
|
+
let(:template) { Praxis::ApiDefinition.instance.responses[:validation_error] }
|
|
79
78
|
it 'is registered with the ApiDefinition' do
|
|
80
79
|
expect(template).to be_kind_of(Praxis::ResponseTemplate)
|
|
81
80
|
end
|
data/spec/praxis/route_spec.rb
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe Praxis::Route do
|
|
4
|
-
|
|
5
6
|
let(:verb) { 'GET' }
|
|
6
7
|
let(:path) { '/base/stuff' }
|
|
7
8
|
let(:prefixed_path) { '/stuff' }
|
|
@@ -23,15 +24,11 @@ describe Praxis::Route do
|
|
|
23
24
|
|
|
24
25
|
context '#describe' do
|
|
25
26
|
subject(:description) { route.describe }
|
|
26
|
-
it { should eq({verb:verb, path:path
|
|
27
|
+
it { should eq({ verb: verb, path: path, version: version }) }
|
|
27
28
|
|
|
28
29
|
context 'with options' do
|
|
29
|
-
let(:options) { {option: 'value'} }
|
|
30
|
+
let(:options) { { option: 'value' } }
|
|
30
31
|
its([:options]) { should eq(options) }
|
|
31
32
|
end
|
|
32
|
-
|
|
33
33
|
end
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
34
|
end
|
data/spec/praxis/router_spec.rb
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe Praxis::Router do
|
|
4
6
|
describe Praxis::Router::VersionMatcher do
|
|
5
|
-
let(:endpoint_definition){ double(
|
|
6
|
-
let(:action){ double(
|
|
7
|
-
let(:target){ double(
|
|
8
|
-
let(:args){ {version:
|
|
9
|
-
subject(:matcher){ Praxis::Router::VersionMatcher.new(target
|
|
7
|
+
let(:endpoint_definition) { double('endpoint_definition', version_options: { using: %i[header params] }) }
|
|
8
|
+
let(:action) { double('action', endpoint_definition: endpoint_definition) }
|
|
9
|
+
let(:target) { double('target', action: action) }
|
|
10
|
+
let(:args) { { version: '1.0' } }
|
|
11
|
+
subject(:matcher) { Praxis::Router::VersionMatcher.new(target, **args) }
|
|
10
12
|
|
|
11
13
|
context '.initialize' do
|
|
12
|
-
let(:args){ {} }
|
|
14
|
+
let(:args) { {} }
|
|
13
15
|
it 'defaults to no version' do
|
|
14
|
-
expect(
|
|
16
|
+
expect(matcher.instance_variable_get(:@version)).to eq('n/a')
|
|
15
17
|
end
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
context '.call' do
|
|
19
|
-
let(:env){ {
|
|
20
|
-
let(:request) {Praxis::Request.new(env)}
|
|
21
|
+
let(:env) { { 'HTTP_X_API_VERSION' => request_version } }
|
|
22
|
+
let(:request) { Praxis::Request.new(env) }
|
|
21
23
|
|
|
22
24
|
context 'with matching versions' do
|
|
23
|
-
let(:request_version) {
|
|
25
|
+
let(:request_version) { '1.0' }
|
|
24
26
|
it 'calls the target' do
|
|
25
27
|
expect(target).to receive(:call).with(request)
|
|
26
28
|
matcher.call(request)
|
|
@@ -28,67 +30,63 @@ describe Praxis::Router do
|
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
context 'with non-matching versions' do
|
|
31
|
-
let(:request_version) {
|
|
33
|
+
let(:request_version) { '4.0' }
|
|
32
34
|
before do
|
|
33
35
|
expect { matcher.call(request) }.to throw_symbol(:pass)
|
|
34
36
|
end
|
|
35
37
|
it 'does not call the target' do
|
|
36
|
-
expect(
|
|
38
|
+
expect(target).not_to receive(:call).with(request)
|
|
37
39
|
end
|
|
38
40
|
it 'saves the unmatched version' do
|
|
39
|
-
expect(
|
|
41
|
+
expect(request.unmatched_versions).to include(args[:version])
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
|
-
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
describe Praxis::Router::RequestRouter do
|
|
48
|
+
let(:request) { double('request', route_params: '', path: 'path') }
|
|
49
|
+
let(:callback) { double('callback') }
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
let(:callback) {double("callback")}
|
|
51
|
+
subject(:request_router) { Praxis::Router::RequestRouter.new }
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
context ".invoke" do
|
|
54
|
-
it "update request and call request for callback" do
|
|
53
|
+
context '.invoke' do
|
|
54
|
+
it 'update request and call request for callback' do
|
|
55
55
|
allow(request).to receive(:route_params=)
|
|
56
56
|
allow(callback).to receive(:call).and_return(1)
|
|
57
57
|
|
|
58
|
-
invoke_call = request_router.invoke(callback, request,
|
|
58
|
+
invoke_call = request_router.invoke(callback, request, 'params', 'pattern')
|
|
59
59
|
expect(invoke_call).to eq(1)
|
|
60
60
|
end
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
context
|
|
64
|
-
it
|
|
63
|
+
context '.string_for' do
|
|
64
|
+
it 'returns request path string' do
|
|
65
65
|
expect(request_router.string_for(request)).to eq('path')
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
let(:application) { instance_double('Praxis::Application')}
|
|
71
|
-
subject(:router) {Praxis::Router.new(application)}
|
|
70
|
+
let(:application) { instance_double('Praxis::Application') }
|
|
71
|
+
subject(:router) { Praxis::Router.new(application) }
|
|
72
72
|
|
|
73
|
-
context
|
|
74
|
-
its(:request_class) {should be(Praxis::Request)}
|
|
73
|
+
context 'attributes' do
|
|
74
|
+
its(:request_class) { should be(Praxis::Request) }
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
-
context
|
|
78
|
-
|
|
79
|
-
let(:
|
|
80
|
-
let(:
|
|
81
|
-
let(:verb_router){ double('verb_router') }
|
|
77
|
+
context '.add_route' do
|
|
78
|
+
let(:route) { double('route', options: [1], version: 1, verb: 'verb', path: 'path') }
|
|
79
|
+
let(:target) { double('target') }
|
|
80
|
+
let(:verb_router) { double('verb_router') }
|
|
82
81
|
|
|
83
|
-
let(:endpoint_definition) { double('endpoint_definition', version_options: {using: [
|
|
82
|
+
let(:endpoint_definition) { double('endpoint_definition', version_options: { using: %i[header params] }) }
|
|
84
83
|
let(:action) { double('action', endpoint_definition: endpoint_definition) }
|
|
85
84
|
let(:target) { double('target', action: action) }
|
|
86
85
|
|
|
87
|
-
|
|
88
86
|
context 'with params or header versioning' do
|
|
89
87
|
it 'wraps the target with a VersionMatcher' do
|
|
90
|
-
router.instance_variable_set(
|
|
91
|
-
expect(verb_router).to receive(:on) do|path, args
|
|
88
|
+
router.instance_variable_set(:@routes, { 'verb' => verb_router }) # Ugly, but no need to have a reader
|
|
89
|
+
expect(verb_router).to receive(:on) do |path, args| # .with(route.path, call: "foo")
|
|
92
90
|
expect(path).to eq(route.path)
|
|
93
91
|
expect(args).to be_kind_of(Hash)
|
|
94
92
|
expect(args[:call]).to be_kind_of(Praxis::Router::VersionMatcher)
|
|
@@ -96,75 +94,72 @@ describe Praxis::Router do
|
|
|
96
94
|
router.add_route(target, route)
|
|
97
95
|
end
|
|
98
96
|
end
|
|
99
|
-
|
|
100
97
|
end
|
|
101
98
|
|
|
102
|
-
context
|
|
103
|
-
let(:env){ {
|
|
99
|
+
context '.call' do
|
|
100
|
+
let(:env) { { 'PATH_INFO' => request_path_info, 'REQUEST_METHOD' => request_verb } }
|
|
104
101
|
let(:request_verb) { 'POST' }
|
|
105
102
|
let(:request_path_info) { '/' }
|
|
106
|
-
let(:request_version){ nil }
|
|
107
|
-
let(:request) {Praxis::Request.new(env)}
|
|
108
|
-
let(:router_response){ 1 }
|
|
109
|
-
let(:router_response_for_post){
|
|
110
|
-
let(:router_response_for_wildcard){
|
|
111
|
-
let(:post_target_router){ double(
|
|
112
|
-
let(:any_target_router){ double(
|
|
113
|
-
|
|
114
|
-
let(:endpoint_definition) { double('endpoint_definition', version_options: {using: [
|
|
103
|
+
let(:request_version) { nil }
|
|
104
|
+
let(:request) { Praxis::Request.new(env) }
|
|
105
|
+
let(:router_response) { 1 }
|
|
106
|
+
let(:router_response_for_post) { 'POST result' }
|
|
107
|
+
let(:router_response_for_wildcard) { '* result' }
|
|
108
|
+
let(:post_target_router) { double('POST target', call: router_response_for_post) }
|
|
109
|
+
let(:any_target_router) { double('ANY target', call: router_response_for_wildcard) }
|
|
110
|
+
|
|
111
|
+
let(:endpoint_definition) { double('endpoint_definition', version_options: { using: %i[header params] }) }
|
|
115
112
|
let(:action) { double('action', endpoint_definition: endpoint_definition) }
|
|
116
113
|
let(:target) { double('target', action: action) }
|
|
117
114
|
|
|
118
115
|
before do
|
|
119
116
|
env['HTTP_X_API_VERSION'] = request_version if request_version
|
|
120
|
-
allow_any_instance_of(Praxis::Router::RequestRouter)
|
|
121
|
-
to receive(:call).with(request).and_return(router_response)
|
|
122
|
-
router.add_route(target,double(
|
|
117
|
+
allow_any_instance_of(Praxis::Router::RequestRouter)
|
|
118
|
+
.to receive(:call).with(request).and_return(router_response)
|
|
119
|
+
router.add_route(target, double('route1', verb: 'POST', path: '/', options: {}, version: request_version))
|
|
123
120
|
# Hijack the callable block in the routes (since there's no way to point back to the registered route object)
|
|
124
121
|
router.instance_variable_get(:@routes)['POST'] = post_target_router
|
|
125
|
-
|
|
126
122
|
end
|
|
127
123
|
|
|
128
124
|
context 'for routes without wildcards (a single POST route)' do
|
|
129
|
-
|
|
130
125
|
context 'and an incoming POST request' do
|
|
131
|
-
it
|
|
126
|
+
it 'finds a match and invokes it the route' do
|
|
132
127
|
expect(router.call(request)).to eq(router_response_for_post)
|
|
133
128
|
end
|
|
134
129
|
end
|
|
135
130
|
context 'and an incoming PUT request' do
|
|
136
131
|
let(:request_verb) { 'PUT' }
|
|
137
|
-
it
|
|
138
|
-
response_code,
|
|
132
|
+
it 'does not find a route' do
|
|
133
|
+
response_code, = router.call(request)
|
|
139
134
|
expect(response_code).to be(404)
|
|
140
135
|
end
|
|
141
136
|
end
|
|
142
137
|
end
|
|
143
138
|
|
|
144
139
|
context 'for routes with wildcards (a POST and a * route)' do
|
|
145
|
-
let(:endpoint_definition) { double('endpoint_definition', version_options: {using: [
|
|
140
|
+
let(:endpoint_definition) { double('endpoint_definition', version_options: { using: %i[header params] }) }
|
|
146
141
|
let(:action) { double('action', endpoint_definition: endpoint_definition) }
|
|
147
142
|
let(:target) { double('target', action: action) }
|
|
148
143
|
|
|
149
144
|
before do
|
|
150
|
-
router.add_route(target,double(
|
|
145
|
+
router.add_route(target, double('route2', verb: 'ANY', path: '/*', options: {}, version: request_version))
|
|
151
146
|
# Hijack the callable block in the routes (since there's no way to point back to the registered route object)
|
|
152
|
-
router.instance_variable_get(
|
|
147
|
+
router.instance_variable_get(:@routes)['ANY'] = any_target_router
|
|
153
148
|
end
|
|
154
149
|
|
|
155
150
|
context 'and an incoming PUT request' do
|
|
156
151
|
let(:request_verb) { 'PUT' }
|
|
157
|
-
it
|
|
152
|
+
it 'it can successfully find a match using the wildcard target' do
|
|
158
153
|
expect(router.call(request)).to eq(router_response_for_wildcard)
|
|
159
154
|
end
|
|
160
155
|
end
|
|
161
156
|
context 'and an incoming POST request' do
|
|
162
|
-
it 'matches the most specific POST route, rather than the wildcard'do
|
|
157
|
+
it 'matches the most specific POST route, rather than the wildcard' do
|
|
163
158
|
expect(router.call(request)).to eq(router_response_for_post)
|
|
164
159
|
end
|
|
165
160
|
end
|
|
166
161
|
context 'and an incoming POST request (but that does not match other route conditions)' do
|
|
167
|
-
let(:router_response_for_post){ :not_found }
|
|
162
|
+
let(:router_response_for_post) { :not_found }
|
|
168
163
|
it 'still matches wildcard verb if that was route conditions-compatible' do
|
|
169
164
|
expect(post_target_router).to receive(:call).once # try the match cause it's a POST
|
|
170
165
|
expect(any_target_router).to receive(:call).once # fallback to wildcard upon a not_found
|
|
@@ -173,13 +168,12 @@ describe Praxis::Router do
|
|
|
173
168
|
end
|
|
174
169
|
end
|
|
175
170
|
|
|
176
|
-
context
|
|
171
|
+
context 'when not_found is returned' do
|
|
177
172
|
let(:request_verb) { 'DELETE' }
|
|
178
|
-
let(:router_response){ :not_found }
|
|
179
|
-
|
|
173
|
+
let(:router_response) { :not_found }
|
|
180
174
|
|
|
181
175
|
it 'sets X-Cascade: pass by default' do
|
|
182
|
-
_, headers,
|
|
176
|
+
_, headers, = router.call(request)
|
|
183
177
|
expect(headers).to have_key('X-Cascade')
|
|
184
178
|
expect(headers['X-Cascade']).to eq('pass')
|
|
185
179
|
end
|
|
@@ -191,10 +185,9 @@ describe Praxis::Router do
|
|
|
191
185
|
end
|
|
192
186
|
|
|
193
187
|
it 'does not set X-Cascade: pass' do
|
|
194
|
-
_, headers,
|
|
195
|
-
expect(headers).to_not have_key(
|
|
188
|
+
_, headers, = router.call(request)
|
|
189
|
+
expect(headers).to_not have_key('X-Cascade')
|
|
196
190
|
end
|
|
197
|
-
|
|
198
191
|
end
|
|
199
192
|
|
|
200
193
|
context 'with versioning' do
|
|
@@ -202,29 +195,28 @@ describe Praxis::Router do
|
|
|
202
195
|
request.instance_variable_set(:@unmatched_versions, unmatched_versions)
|
|
203
196
|
end
|
|
204
197
|
|
|
205
|
-
context
|
|
198
|
+
context 'having passed no version in the request' do
|
|
206
199
|
context 'and no controllers matching the path' do
|
|
207
200
|
let(:unmatched_versions) { Set.new([]) }
|
|
208
201
|
|
|
209
202
|
it 'returns a basic "NotFound" response: 404 status, text/plain content and "NotFound" body' do
|
|
210
|
-
expect(
|
|
203
|
+
expect(router.call(request)).to eq([404, { 'Content-Type' => 'text/plain', 'X-Cascade' => 'pass' }, ['NotFound']])
|
|
211
204
|
end
|
|
212
205
|
end
|
|
213
206
|
|
|
214
207
|
context 'and some controllers matching the path' do
|
|
215
|
-
let(:unmatched_versions) { Set.new([
|
|
208
|
+
let(:unmatched_versions) { Set.new(['1.0']) }
|
|
216
209
|
it 'returns a specific body response noting which request versions would matched if passed in' do
|
|
217
210
|
_, _, body = router.call(request)
|
|
218
|
-
expect(
|
|
211
|
+
expect(body.first).to eq('NotFound. Your request did not specify an API version. Available versions = "1.0".')
|
|
219
212
|
end
|
|
220
213
|
end
|
|
221
214
|
end
|
|
222
215
|
|
|
223
|
-
context
|
|
224
|
-
|
|
216
|
+
context 'having passed a version in the request' do
|
|
225
217
|
context 'but having no controllers matching the path part' do
|
|
226
|
-
let(:request_version) {
|
|
227
|
-
let(:unmatched_versions) { Set.new([
|
|
218
|
+
let(:request_version) { '50.0' }
|
|
219
|
+
let(:unmatched_versions) { Set.new(['1.0', '2.0']) }
|
|
228
220
|
|
|
229
221
|
it 'returns a specific body response noting that the version might be wrong (and which could be right)' do
|
|
230
222
|
code, headers, body = router.call(request)
|
|
@@ -235,8 +227,6 @@ describe Praxis::Router do
|
|
|
235
227
|
end
|
|
236
228
|
end
|
|
237
229
|
end
|
|
238
|
-
|
|
239
|
-
|
|
240
230
|
end
|
|
241
231
|
end
|
|
242
232
|
end
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe Praxis::RoutingConfig do
|
|
4
|
-
|
|
5
6
|
let(:endpoint_definition) do
|
|
6
7
|
Class.new do
|
|
7
8
|
include Praxis::EndpointDefinition
|
|
8
|
-
def self.name
|
|
9
|
+
def self.name
|
|
10
|
+
'MyResource'
|
|
11
|
+
end
|
|
9
12
|
end
|
|
10
13
|
end
|
|
11
14
|
|
|
12
|
-
let(:routing_block) {
|
|
13
|
-
let(:base_path){ '' }
|
|
14
|
-
let(:default_route_prefix) { "
|
|
15
|
+
let(:routing_block) { proc {} }
|
|
16
|
+
let(:base_path) { '' }
|
|
17
|
+
let(:default_route_prefix) { "/#{endpoint_definition.name.split('::').last.underscore}" }
|
|
15
18
|
|
|
16
|
-
subject(:routing_config){ Praxis::RoutingConfig.new(base: base_path, &routing_block) }
|
|
19
|
+
subject(:routing_config) { Praxis::RoutingConfig.new(base: base_path, &routing_block) }
|
|
17
20
|
|
|
18
21
|
its(:version) { should eq('n/a') }
|
|
19
|
-
its(:prefix
|
|
22
|
+
its(:prefix) { should eq('') }
|
|
20
23
|
|
|
21
24
|
context '#prefix' do
|
|
22
25
|
it 'sets the prefix' do
|
|
@@ -35,15 +38,14 @@ describe Praxis::RoutingConfig do
|
|
|
35
38
|
routing_config.prefix '/people'
|
|
36
39
|
expect(routing_config.prefix).to eq('/people')
|
|
37
40
|
end
|
|
38
|
-
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
context '#add_route' do
|
|
42
44
|
let(:path) { '/people' }
|
|
43
45
|
let(:options) { {} }
|
|
44
|
-
let(:base_path){ '/api' }
|
|
45
|
-
let(:route) { routing_config.add_route 'GET', path, **options}
|
|
46
|
-
|
|
46
|
+
let(:base_path) { '/api' }
|
|
47
|
+
let(:route) { routing_config.add_route 'GET', path, **options }
|
|
48
|
+
|
|
47
49
|
it 'returns a corresponding Praxis::Route' do
|
|
48
50
|
expect(route).to be_kind_of(Praxis::Route)
|
|
49
51
|
end
|
|
@@ -53,11 +55,11 @@ describe Praxis::RoutingConfig do
|
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
context 'passing options' do
|
|
56
|
-
let(:options){ { except: '/special' } }
|
|
58
|
+
let(:options) { { except: '/special' } }
|
|
57
59
|
|
|
58
60
|
it 'passes them through the underlying mustermann object (telling it to ignore unknown ones)' do
|
|
59
61
|
expect(Mustermann).to receive(:new).with(base_path + path, hash_including(ignore_unknown_options: true, except: '/special'))
|
|
60
|
-
expect(route.options).to eq(
|
|
62
|
+
expect(route.options).to eq({ except: '/special' })
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
|
|
@@ -82,5 +84,4 @@ describe Praxis::RoutingConfig do
|
|
|
82
84
|
end
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
|
-
|
|
86
87
|
end
|
data/spec/praxis/stage_spec.rb
CHANGED
|
@@ -1,106 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe Praxis::Stage do
|
|
4
|
-
subject(:stage) {Praxis::Stage.new(
|
|
6
|
+
subject(:stage) { Praxis::Stage.new('name', 'context') }
|
|
5
7
|
|
|
6
|
-
context
|
|
7
|
-
its(:name) {should eq(
|
|
8
|
-
its(:context) {should eq(
|
|
9
|
-
its(:stages) {should eq([])}
|
|
10
|
-
its(:before_callbacks) {should eq([])}
|
|
11
|
-
its(:after_callbacks) {should eq([])}
|
|
8
|
+
context 'attributes' do
|
|
9
|
+
its(:name) { should eq('name') }
|
|
10
|
+
its(:context) { should eq('context') }
|
|
11
|
+
its(:stages) { should eq([]) }
|
|
12
|
+
its(:before_callbacks) { should eq([]) }
|
|
13
|
+
its(:after_callbacks) { should eq([]) }
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
context
|
|
15
|
-
it
|
|
16
|
+
context '.run' do
|
|
17
|
+
it 'sets up and execute callbacks' do
|
|
16
18
|
expect(stage).to receive('execute')
|
|
17
19
|
expect(stage).to receive('execute_callbacks').twice
|
|
18
20
|
stage.run
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
context
|
|
24
|
+
context '.setup!' do
|
|
23
25
|
it 'should call setup_deferred_callbacks' do
|
|
24
26
|
expect(stage).to receive('setup_deferred_callbacks!')
|
|
25
27
|
stage.setup!
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
context
|
|
30
|
-
it
|
|
31
|
+
context '.setup_deferred_callbacks!' do
|
|
32
|
+
it 'calls .before and .after for each stage name in @deferred_callbacks' do
|
|
31
33
|
deferred_callbacks = {
|
|
32
|
-
|
|
33
|
-
:
|
|
34
|
-
:
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
expect(stage).to receive(
|
|
34
|
+
'stage' => {
|
|
35
|
+
before: [['before', nil]],
|
|
36
|
+
after: [['after', nil]]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
stage.instance_variable_set('@deferred_callbacks', deferred_callbacks)
|
|
40
|
+
expect(stage).to receive('before')
|
|
41
|
+
expect(stage).to receive('after')
|
|
39
42
|
stage.setup_deferred_callbacks!
|
|
40
43
|
end
|
|
41
44
|
end
|
|
42
45
|
|
|
43
|
-
context
|
|
44
|
-
it
|
|
45
|
-
double_stage = double(
|
|
46
|
+
context '.execute' do
|
|
47
|
+
it 'runs all the stages' do
|
|
48
|
+
double_stage = double('stage')
|
|
46
49
|
expect(double_stage).to receive('run')
|
|
47
|
-
stage.instance_variable_set(
|
|
50
|
+
stage.instance_variable_set('@stages', [double_stage])
|
|
48
51
|
stage.execute
|
|
49
52
|
end
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
context
|
|
53
|
-
let(:callback) {double('callback')}
|
|
54
|
-
it
|
|
55
|
-
expect(callback).to receive(
|
|
55
|
+
context '.execute_callbacks' do
|
|
56
|
+
let(:callback) { double('callback') }
|
|
57
|
+
it 'executes every callback' do
|
|
58
|
+
expect(callback).to receive('call')
|
|
56
59
|
stage.execute_callbacks([callback])
|
|
57
60
|
end
|
|
58
61
|
end
|
|
59
62
|
|
|
60
|
-
context
|
|
61
|
-
# TODO should this method return something else than nil?
|
|
62
|
-
it
|
|
63
|
+
context '.callback_args' do
|
|
64
|
+
# TODO: should this method return something else than nil?
|
|
65
|
+
it 'returns nil' do
|
|
63
66
|
expect(stage.callback_args).to be(nil)
|
|
64
67
|
end
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
context
|
|
68
|
-
it
|
|
69
|
-
stage.before {1}
|
|
70
|
+
context '.before' do
|
|
71
|
+
it 'adds block to @before_callbacks when stage_path is not provided' do
|
|
72
|
+
stage.before { 1 }
|
|
70
73
|
expect(stage.before_callbacks.last.call).to be(1)
|
|
71
74
|
end
|
|
72
75
|
|
|
73
|
-
it
|
|
74
|
-
double_stage = double(
|
|
76
|
+
it 'calls .before for the name matched stage' do
|
|
77
|
+
double_stage = double('stage', name: 'name')
|
|
75
78
|
expect(double_stage).to receive('before')
|
|
76
|
-
stage.instance_variable_set(
|
|
77
|
-
stage.before(
|
|
79
|
+
stage.instance_variable_set('@stages', [double_stage])
|
|
80
|
+
stage.before('name')
|
|
78
81
|
end
|
|
79
82
|
|
|
80
|
-
it
|
|
81
|
-
double_stage = double(
|
|
82
|
-
stage.instance_variable_set(
|
|
83
|
-
expect(stage.before(
|
|
83
|
+
it 'adds to deferred_callbacks if no state name matched' do
|
|
84
|
+
double_stage = double('stage', name: 'name')
|
|
85
|
+
stage.instance_variable_set('@stages', [double_stage])
|
|
86
|
+
expect(stage.before('hello', 'world')).to eq([['world', nil]])
|
|
84
87
|
end
|
|
85
88
|
end
|
|
86
89
|
|
|
87
|
-
context
|
|
88
|
-
it
|
|
89
|
-
stage.after {1}
|
|
90
|
+
context '.after' do
|
|
91
|
+
it 'adds block to @after_callbacks when stage_path is not provided' do
|
|
92
|
+
stage.after { 1 }
|
|
90
93
|
expect(stage.after_callbacks.last.call).to be(1)
|
|
91
94
|
end
|
|
92
95
|
|
|
93
|
-
it
|
|
94
|
-
double_stage = double(
|
|
95
|
-
expect(double_stage).to receive(
|
|
96
|
-
stage.instance_variable_set(
|
|
97
|
-
stage.after(
|
|
96
|
+
it 'calls .after for the name matched stage' do
|
|
97
|
+
double_stage = double('stage', name: 'name')
|
|
98
|
+
expect(double_stage).to receive('after')
|
|
99
|
+
stage.instance_variable_set('@stages', [double_stage])
|
|
100
|
+
stage.after('name')
|
|
98
101
|
end
|
|
99
102
|
|
|
100
|
-
it
|
|
101
|
-
double_stage = double(
|
|
102
|
-
stage.instance_variable_set(
|
|
103
|
-
expect(stage.after(
|
|
103
|
+
it 'adds to deferred_callbacks if no state name matched' do
|
|
104
|
+
double_stage = double('stage', name: 'name')
|
|
105
|
+
stage.instance_variable_set('@stages', [double_stage])
|
|
106
|
+
expect(stage.after('hello', 'world')).to eq([['world', nil]])
|
|
104
107
|
end
|
|
105
108
|
end
|
|
106
109
|
end
|