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,21 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
require 'praxis/extensions/attribute_filtering'
|
|
4
6
|
|
|
5
7
|
describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
6
|
-
|
|
7
8
|
context '.load' do
|
|
8
9
|
subject { described_class.load(filters_string) }
|
|
9
10
|
|
|
10
11
|
context 'unescapes the URL encoded values' do
|
|
11
12
|
it 'for single values' do
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
str = "one=#{CGI.escape('*')}&two>#{CGI.escape('^%$#!st_uff')}|three<normal"
|
|
14
|
+
parsed = [
|
|
15
|
+
{ name: :one, op: '=', value: '*' },
|
|
16
|
+
{ name: :two, op: '>', value: '^%$#!st_uff' },
|
|
17
|
+
{ name: :three, op: '<', value: 'normal' }
|
|
18
|
+
]
|
|
19
|
+
expect(described_class.load(str).parsed_array.map { |i| i.slice(:name, :op, :value) }).to eq(parsed)
|
|
19
20
|
end
|
|
20
21
|
it 'each of the multi-values' do
|
|
21
22
|
escaped_one = [
|
|
@@ -25,55 +26,55 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
25
26
|
].join(',')
|
|
26
27
|
str = "one=#{escaped_one}&two>normal"
|
|
27
28
|
parsed = [
|
|
28
|
-
{ name: :one, op: '=', value: ['fun!','Times','~!@#$%^&*()_+-={}|[]\:";\'<>?,./`']},
|
|
29
|
-
{ name: :two, op: '>', value: 'normal'}
|
|
29
|
+
{ name: :one, op: '=', value: ['fun!', 'Times', '~!@#$%^&*()_+-={}|[]\:";\'<>?,./`'] },
|
|
30
|
+
{ name: :two, op: '>', value: 'normal' }
|
|
30
31
|
]
|
|
31
|
-
expect(described_class.load(str).parsed_array.map{|i| i.slice(:name
|
|
32
|
+
expect(described_class.load(str).parsed_array.map { |i| i.slice(:name, :op, :value) }).to eq(parsed)
|
|
32
33
|
end
|
|
33
34
|
it 'does not handle badly escaped values that contain reserved chars ()|&,' do
|
|
34
35
|
badly_escaped = 'val('
|
|
35
36
|
str = "one=#{badly_escaped}&(two>normal|three!)"
|
|
36
|
-
expect
|
|
37
|
+
expect do
|
|
37
38
|
described_class.load(str)
|
|
38
|
-
|
|
39
|
+
end.to raise_error(Parslet::ParseFailed)
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
context 'parses for operator' do
|
|
42
43
|
described_class::VALUE_OPERATORS.each do |op|
|
|
43
|
-
it
|
|
44
|
+
it op.to_s do
|
|
44
45
|
str = "thename#{op}thevalue"
|
|
45
|
-
parsed = [{ name: :thename, op: op, value: 'thevalue'}]
|
|
46
|
-
expect(described_class.load(str).parsed_array.map{|i| i.slice(:name
|
|
46
|
+
parsed = [{ name: :thename, op: op, value: 'thevalue' }]
|
|
47
|
+
expect(described_class.load(str).parsed_array.map { |i| i.slice(:name, :op, :value) }).to eq(parsed)
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
described_class::NOVALUE_OPERATORS.each do |op|
|
|
50
|
-
it
|
|
51
|
+
it op.to_s do
|
|
51
52
|
str = "thename#{op}"
|
|
52
|
-
parsed = [{ name: :thename, op: op, value: nil}]
|
|
53
|
-
expect(described_class.load(str).parsed_array.map{|i| i.slice(:name
|
|
53
|
+
parsed = [{ name: :thename, op: op, value: nil }]
|
|
54
|
+
expect(described_class.load(str).parsed_array.map { |i| i.slice(:name, :op, :value) }).to eq(parsed)
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
it 'can parse multiple values for filter' do
|
|
57
|
-
str=
|
|
58
|
-
parsed = [{ name: :filtername, op: '=', value: [
|
|
59
|
-
expect(described_class.load(str).parsed_array.map{|i| i.slice(:name
|
|
58
|
+
str = 'filtername=1,2,3'
|
|
59
|
+
parsed = [{ name: :filtername, op: '=', value: %w[1 2 3] }]
|
|
60
|
+
expect(described_class.load(str).parsed_array.map { |i| i.slice(:name, :op, :value) }).to eq(parsed)
|
|
60
61
|
end
|
|
61
62
|
end
|
|
62
63
|
context 'with all value operators at once for the same AND group' do
|
|
63
|
-
let(:filters_string) { 'one=11&two!=22&three>=33&four<=4&five<5&six>6&seven!&eight!!'}
|
|
64
|
+
let(:filters_string) { 'one=11&two!=22&three>=33&four<=4&five<5&six>6&seven!&eight!!' }
|
|
64
65
|
it do
|
|
65
|
-
expect(subject.parsed_array.map{|i| i.slice(:name
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
expect(subject.parsed_array.map { |i| i.slice(:name, :op, :value) }).to eq([
|
|
67
|
+
{ name: :one, op: '=', value: '11' },
|
|
68
|
+
{ name: :two, op: '!=', value: '22' },
|
|
69
|
+
{ name: :three, op: '>=', value: '33' },
|
|
70
|
+
{ name: :four, op: '<=', value: '4' },
|
|
71
|
+
{ name: :five, op: '<', value: '5' },
|
|
72
|
+
{ name: :six, op: '>', value: '6' },
|
|
73
|
+
{ name: :seven, op: '!', value: nil },
|
|
74
|
+
{ name: :eight, op: '!!', value: nil }
|
|
75
|
+
])
|
|
75
76
|
# And all have the same parent, which is an AND group
|
|
76
|
-
parent = subject.parsed_array.map{|i|i[:node_object].parent_group}.uniq
|
|
77
|
+
parent = subject.parsed_array.map { |i| i[:node_object].parent_group }.uniq
|
|
77
78
|
expect(parent.size).to eq(1)
|
|
78
79
|
expect(parent.first.type).to eq(:and)
|
|
79
80
|
expect(parent.first.parent_group).to be_nil
|
|
@@ -81,17 +82,17 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
81
82
|
end
|
|
82
83
|
|
|
83
84
|
context 'with with nested precedence groups' do
|
|
84
|
-
let(:filters_string) { '(one=11)&(two!=22|three!!)&four<=4&five>5|six!'}
|
|
85
|
+
let(:filters_string) { '(one=11)&(two!=22|three!!)&four<=4&five>5|six!' }
|
|
85
86
|
it do
|
|
86
87
|
parsed = subject.parsed_array
|
|
87
|
-
expect(parsed.map{|i| i.slice(:name
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
expect(parsed.map { |i| i.slice(:name, :op, :value) }).to eq([
|
|
89
|
+
{ name: :one, op: '=', value: '11' },
|
|
90
|
+
{ name: :two, op: '!=', value: '22' },
|
|
91
|
+
{ name: :three, op: '!!', value: nil },
|
|
92
|
+
{ name: :four, op: '<=', value: '4' },
|
|
93
|
+
{ name: :five, op: '>', value: '5' },
|
|
94
|
+
{ name: :six, op: '!', value: nil }
|
|
95
|
+
])
|
|
95
96
|
# Grouped appropriately
|
|
96
97
|
parent_of = parsed.each_with_object({}) do |item, hash|
|
|
97
98
|
hash[item[:name]] = item[:node_object].parent_group
|
|
@@ -106,9 +107,9 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
106
107
|
# two and 3 are grouped together by an OR
|
|
107
108
|
expect(parent_of[:two]).to be(parent_of[:three])
|
|
108
109
|
expect(parent_of[:two].type).to eq(:or)
|
|
109
|
-
|
|
110
|
+
|
|
110
111
|
# one, two, four and the or from two/three are grouped together by an AND
|
|
111
|
-
expect([parent_of[:one],parent_of[:two].parent_group,parent_of[:four],parent_of[:five]]).to all(be(parent_of[:one]))
|
|
112
|
+
expect([parent_of[:one], parent_of[:two].parent_group, parent_of[:four], parent_of[:five]]).to all(be(parent_of[:one]))
|
|
112
113
|
expect(parent_of[:one].type).to eq(:and)
|
|
113
114
|
|
|
114
115
|
# six and the whole group above are grouped together with an OR
|
|
@@ -119,11 +120,12 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
119
120
|
|
|
120
121
|
context 'value coercing when associated to a MediaType' do
|
|
121
122
|
let(:parsed) do
|
|
122
|
-
# Note wrap the filter_params (.for) type in an attribute (which then we discard), so it will
|
|
123
|
-
# construct it propertly by applying the block. Seems easier than creating the type alone, and
|
|
123
|
+
# Note wrap the filter_params (.for) type in an attribute (which then we discard), so it will
|
|
124
|
+
# construct it propertly by applying the block. Seems easier than creating the type alone, and
|
|
124
125
|
# then manually apply the block
|
|
125
126
|
Attributor::Attribute.new(described_class.for(Post)) do
|
|
126
127
|
filter 'id', using: ['=', '!=', '!']
|
|
128
|
+
any 'updated_at', using: ['>', '<', '=']
|
|
127
129
|
end.type.load(str).parsed_array
|
|
128
130
|
end
|
|
129
131
|
|
|
@@ -151,19 +153,41 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
151
153
|
expect(parsed.first[:value]).to be_nil
|
|
152
154
|
end
|
|
153
155
|
end
|
|
154
|
-
end
|
|
155
156
|
|
|
157
|
+
context 'for an ANY matcher' do
|
|
158
|
+
let(:str) { 'blog.timestamps.updated_at>=2001-01-01' }
|
|
159
|
+
it 'coerces its value to the associated mediatype attribute type' do
|
|
160
|
+
expect(parsed.first[:value]).to eq(DateTime.parse('2001-01-01'))
|
|
161
|
+
nested_updated_at_attr = Post.attributes[:blog].attributes[:timestamps].attributes[:updated_at]
|
|
162
|
+
expect(nested_updated_at_attr.type.valid_type?(parsed.first[:value])).to be_truthy
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
context 'for both normal and ANY type filters' do
|
|
167
|
+
let(:str) { 'blog.timestamps.updated_at>=2001-01-01&id=42' }
|
|
168
|
+
it 'properly parsed them both, and into their own types' do
|
|
169
|
+
expect(parsed.first[:value]).to eq(DateTime.parse('2001-01-01'))
|
|
170
|
+
nested_updated_at_attr = Post.attributes[:blog].attributes[:timestamps].attributes[:updated_at]
|
|
171
|
+
expect(nested_updated_at_attr.type.valid_type?(parsed.first[:value])).to be_truthy
|
|
172
|
+
|
|
173
|
+
expect(parsed.second[:value]).to eq(42)
|
|
174
|
+
id_attr = Post.attributes[:blog].attributes[:id]
|
|
175
|
+
expect(id_attr.type.valid_type?(parsed.second[:value])).to be_truthy
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
156
179
|
end
|
|
157
180
|
|
|
158
181
|
context '.validate' do
|
|
159
182
|
let(:filtering_params_type) do
|
|
160
|
-
# Note wrap the filter_params (.for) type in an attribute (which then we discard), so it will
|
|
161
|
-
# construct it propertly by applying the block. Seems easier than creating the type alone, and
|
|
183
|
+
# Note wrap the filter_params (.for) type in an attribute (which then we discard), so it will
|
|
184
|
+
# construct it propertly by applying the block. Seems easier than creating the type alone, and
|
|
162
185
|
# then manually apply the block
|
|
163
186
|
Attributor::Attribute.new(described_class.for(Post)) do
|
|
164
187
|
filter 'id', using: ['=', '!=', '!']
|
|
165
188
|
filter 'title', using: ['=', '!='], fuzzy: true
|
|
166
189
|
filter 'content', using: ['=', '!=', '!']
|
|
190
|
+
any 'updated_at', using: ['>', '<', '=']
|
|
167
191
|
end.type
|
|
168
192
|
end
|
|
169
193
|
let(:loaded_params) { filtering_params_type.load(filters_string) }
|
|
@@ -171,23 +195,23 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
171
195
|
|
|
172
196
|
context 'errors' do
|
|
173
197
|
context 'given attributes that do not exist in the type' do
|
|
174
|
-
let(:filters_string) { 'NotAnExistingAttribute=Foobar*'}
|
|
198
|
+
let(:filters_string) { 'NotAnExistingAttribute=Foobar*' }
|
|
175
199
|
it 'raises an error' do
|
|
176
|
-
expect{subject}.to raise_error(/NotAnExistingAttribute.*does not exist/)
|
|
200
|
+
expect { subject }.to raise_error(/NotAnExistingAttribute.*does not exist/)
|
|
177
201
|
end
|
|
178
202
|
end
|
|
179
203
|
|
|
180
204
|
context 'given unallowed attributes' do
|
|
181
|
-
let(:filters_string) { 'href=Foobar*'}
|
|
205
|
+
let(:filters_string) { 'href=Foobar*' }
|
|
182
206
|
it 'raises an error' do
|
|
183
207
|
expect(subject).to_not be_empty
|
|
184
|
-
matches_error = subject.any? {|err| err =~ /Filtering by href is not allowed/}
|
|
208
|
+
matches_error = subject.any? { |err| err =~ /Filtering by href is not allowed.*leaf attributes matching updated_at/ }
|
|
185
209
|
expect(matches_error).to be_truthy
|
|
186
210
|
end
|
|
187
211
|
end
|
|
188
212
|
|
|
189
213
|
context 'given unallowed operator' do
|
|
190
|
-
let(:filters_string) { 'title>Foobar*'}
|
|
214
|
+
let(:filters_string) { 'title>Foobar*' }
|
|
191
215
|
it 'raises an error' do
|
|
192
216
|
expect(subject).to_not be_empty
|
|
193
217
|
expect(subject.first).to match(/Operator > not allowed for filter title/)
|
|
@@ -195,15 +219,43 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
195
219
|
end
|
|
196
220
|
end
|
|
197
221
|
|
|
222
|
+
context 'for any-type leaf filters' do
|
|
223
|
+
context 'with the correct operator' do
|
|
224
|
+
let(:filters_string) { 'timestamps.updated_at>2001-01-01' }
|
|
225
|
+
it 'succeeds' do
|
|
226
|
+
expect(subject).to be_empty
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
context 'with the correct operator on a nested mediatype' do
|
|
230
|
+
let(:filters_string) { 'blog.timestamps.updated_at>2001-01-01' }
|
|
231
|
+
it 'succeeds' do
|
|
232
|
+
expect(subject).to be_empty
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
context 'with an incorrect operator' do
|
|
236
|
+
let(:filters_string) { 'blog.timestamps.updated_at>=2001-01-01' }
|
|
237
|
+
it 'complains about it' do
|
|
238
|
+
expect(subject).to_not be_empty
|
|
239
|
+
expect(subject.first).to match(/Operator >= not allowed for filter blog.timestamps.updated_at/)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
context 'with an correct operator but passing an unexistent attribute path' do
|
|
243
|
+
let(:filters_string) { 'some.not.true.path.updated_at>=2001-01-01' }
|
|
244
|
+
it 'complains about it' do
|
|
245
|
+
expect { subject }.to raise_error(/Error, you've requested to filter by field 'some'/)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
198
250
|
context 'non-valued operators' do
|
|
199
251
|
context 'for string typed fields' do
|
|
200
|
-
let(:filters_string) { 'content!'}
|
|
252
|
+
let(:filters_string) { 'content!' }
|
|
201
253
|
it 'validates properly' do
|
|
202
254
|
expect(subject).to be_empty
|
|
203
255
|
end
|
|
204
256
|
end
|
|
205
257
|
context 'for non-string typed fields' do
|
|
206
|
-
let(:filters_string) { 'id!'}
|
|
258
|
+
let(:filters_string) { 'id!' }
|
|
207
259
|
it 'validates properly' do
|
|
208
260
|
expect(subject).to be_empty
|
|
209
261
|
end
|
|
@@ -212,7 +264,7 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
212
264
|
context 'fuzzy matches' do
|
|
213
265
|
context 'when allowed' do
|
|
214
266
|
context 'given a fuzzy string' do
|
|
215
|
-
let(:filters_string) { 'title=IAmAString*'}
|
|
267
|
+
let(:filters_string) { 'title=IAmAString*' }
|
|
216
268
|
it 'validates properly' do
|
|
217
269
|
expect(subject).to be_empty
|
|
218
270
|
end
|
|
@@ -220,14 +272,14 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams do
|
|
|
220
272
|
end
|
|
221
273
|
context 'when NOT allowed' do
|
|
222
274
|
context 'given a fuzzy string' do
|
|
223
|
-
let(:filters_string) { 'content=IAmAString*'}
|
|
275
|
+
let(:filters_string) { 'content=IAmAString*' }
|
|
224
276
|
it 'errors out' do
|
|
225
277
|
expect(subject).to_not be_empty
|
|
226
278
|
expect(subject.first).to match(/Fuzzy matching for content is not allowed/)
|
|
227
279
|
end
|
|
228
280
|
end
|
|
229
281
|
context 'given a non-fuzzy string' do
|
|
230
|
-
let(:filters_string) { 'content=IAmAString'}
|
|
282
|
+
let(:filters_string) { 'content=IAmAString' }
|
|
231
283
|
it 'validates properly' do
|
|
232
284
|
expect(subject).to be_empty
|
|
233
285
|
end
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'praxis/extensions/attribute_filtering/filters_parser'
|
|
2
4
|
|
|
3
5
|
describe Praxis::Extensions::AttributeFiltering::FilteringParams::Condition do
|
|
4
|
-
end
|
|
6
|
+
end
|
|
5
7
|
|
|
6
8
|
describe Praxis::Extensions::AttributeFiltering::FilteringParams::ConditionGroup do
|
|
7
|
-
end
|
|
9
|
+
end
|
|
8
10
|
|
|
9
11
|
describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
10
|
-
|
|
11
12
|
context 'testing' do
|
|
12
13
|
let(:expectations) do
|
|
13
|
-
{
|
|
14
|
-
'one=11|two=22' =>
|
|
14
|
+
{
|
|
15
|
+
'one=11|two=22' => '( one=11 OR two=22 )'
|
|
15
16
|
}
|
|
16
17
|
end
|
|
17
18
|
it 'parses and loads the parsed result into the tree objects' do
|
|
18
19
|
expectations.each do |filters, dump_result|
|
|
19
|
-
|
|
20
20
|
parsed = described_class.new.parse(filters)
|
|
21
21
|
tree = Praxis::Extensions::AttributeFiltering::FilteringParams::ConditionGroup.load(parsed)
|
|
22
22
|
|
|
@@ -24,8 +24,7 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
context 'parses the grammar' do
|
|
29
28
|
# Takes a hash with keys containing literal filters string, and values being the "dump format for Condition/Group"
|
|
30
29
|
shared_examples 'round-trip-properly' do |expectations|
|
|
31
30
|
it description do
|
|
@@ -38,24 +37,24 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
context 'single expression' do
|
|
41
|
-
it_behaves_like 'round-trip-properly', {
|
|
40
|
+
it_behaves_like 'round-trip-properly', {
|
|
42
41
|
'one=11' => 'one=11',
|
|
43
42
|
'(one=11)' => 'one=11',
|
|
44
|
-
'one!' =>
|
|
43
|
+
'one!' => 'one!'
|
|
45
44
|
}
|
|
46
45
|
end
|
|
47
46
|
context 'same expression operator' do
|
|
48
|
-
it_behaves_like 'round-trip-properly', {
|
|
47
|
+
it_behaves_like 'round-trip-properly', {
|
|
49
48
|
'one=11&two=22' => '( one=11 AND two=22 )',
|
|
50
49
|
'one=11&two=22&three=3' => '( one=11 AND two=22 AND three=3 )',
|
|
51
50
|
'one=1,2,3&two=4,5' => '( one=[1,2,3] AND two=[4,5] )',
|
|
52
51
|
'one=11|two=22' => '( one=11 OR two=22 )',
|
|
53
|
-
'one=11|two=22|three=3' => '( one=11 OR two=22 OR three=3 )'
|
|
52
|
+
'one=11|two=22|three=3' => '( one=11 OR two=22 OR three=3 )'
|
|
54
53
|
}
|
|
55
54
|
end
|
|
56
55
|
|
|
57
56
|
context 'respects and/or precedence and parenthesis grouping' do
|
|
58
|
-
it_behaves_like 'round-trip-properly', {
|
|
57
|
+
it_behaves_like 'round-trip-properly', {
|
|
59
58
|
'a=1&b=2&z=9|c=3' => '( ( a=1 AND b=2 AND z=9 ) OR c=3 )',
|
|
60
59
|
'a=1|b=2&c=3' => '( a=1 OR ( b=2 AND c=3 ) )',
|
|
61
60
|
'a=1|b=2&c=3&d=4' => '( a=1 OR ( b=2 AND c=3 AND d=4 ) )',
|
|
@@ -65,56 +64,56 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
|
65
64
|
'one=11&two=2|three=3' => '( ( one=11 AND two=2 ) OR three=3 )', # AND has higer precedence
|
|
66
65
|
'one=11|two=2&three=3' => '( one=11 OR ( two=2 AND three=3 ) )', # AND has higer precedence
|
|
67
66
|
'one=11&two=2|three=3&four=4' => '( ( one=11 AND two=2 ) OR ( three=3 AND four=4 ) )',
|
|
68
|
-
'(one=11)&(two!=2|three=3)&four=4&five=5|six=6' =>
|
|
67
|
+
'(one=11)&(two!=2|three=3)&four=4&five=5|six=6' =>
|
|
69
68
|
'( ( one=11 AND ( two!=2 OR three=3 ) AND four=4 AND five=5 ) OR six=6 )',
|
|
70
69
|
'(one=11)&three=3' => '( one=11 AND three=3 )',
|
|
71
70
|
'(one=11|two=2)&(three=3|four=4)' => '( ( one=11 OR two=2 ) AND ( three=3 OR four=4 ) )',
|
|
72
71
|
'(category_uuid=deadbeef1|category_uuid=deadbeef2)&(name=Book1|name=Book2)' =>
|
|
73
72
|
'( ( category_uuid=deadbeef1 OR category_uuid=deadbeef2 ) AND ( name=Book1 OR name=Book2 ) )',
|
|
74
73
|
'(category_uuid=deadbeef1&name=Book1)|(category_uuid=deadbeef2&name=Book2)' =>
|
|
75
|
-
'( ( category_uuid=deadbeef1 AND name=Book1 ) OR ( category_uuid=deadbeef2 AND name=Book2 ) )'
|
|
74
|
+
'( ( category_uuid=deadbeef1 AND name=Book1 ) OR ( category_uuid=deadbeef2 AND name=Book2 ) )'
|
|
76
75
|
}
|
|
77
76
|
end
|
|
78
77
|
|
|
79
78
|
context 'empty values get converted to empty strings' do
|
|
80
|
-
it_behaves_like 'round-trip-properly', {
|
|
79
|
+
it_behaves_like 'round-trip-properly', {
|
|
81
80
|
'one=' => 'one=""',
|
|
82
|
-
'one=&two=2' => '( one="" AND two=2 )'
|
|
81
|
+
'one=&two=2' => '( one="" AND two=2 )'
|
|
83
82
|
}
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
context 'no value operands' do
|
|
87
|
-
it_behaves_like 'round-trip-properly', {
|
|
88
|
-
'one!' =>
|
|
89
|
-
'one!!' =>
|
|
86
|
+
it_behaves_like 'round-trip-properly', {
|
|
87
|
+
'one!' => 'one!',
|
|
88
|
+
'one!!' => 'one!!'
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
it 'fails if passing a value' do
|
|
93
|
-
expect
|
|
92
|
+
expect do
|
|
94
93
|
described_class.new.parse('one!val')
|
|
95
|
-
|
|
96
|
-
expect
|
|
94
|
+
end.to raise_error(Parslet::ParseFailed)
|
|
95
|
+
expect do
|
|
97
96
|
described_class.new.parse('one!!val')
|
|
98
|
-
|
|
99
|
-
end
|
|
97
|
+
end.to raise_error(Parslet::ParseFailed)
|
|
98
|
+
end
|
|
100
99
|
end
|
|
101
100
|
|
|
102
101
|
context 'csv values result in multiple values for the operation' do
|
|
103
|
-
it_behaves_like 'round-trip-properly', {
|
|
104
|
-
'multi=1,2' =>
|
|
105
|
-
'multi=1,2,valuehere' =>
|
|
102
|
+
it_behaves_like 'round-trip-properly', {
|
|
103
|
+
'multi=1,2' => 'multi=[1,2]',
|
|
104
|
+
'multi=1,2,valuehere' => 'multi=[1,2,valuehere]'
|
|
106
105
|
}
|
|
107
106
|
end
|
|
108
107
|
|
|
109
108
|
context 'supports [a-zA-Z0-9_\.] for filter names' do
|
|
110
|
-
it_behaves_like 'round-trip-properly', {
|
|
111
|
-
'normal=1'
|
|
112
|
-
'cOmBo=1'
|
|
113
|
-
'1=2'
|
|
109
|
+
it_behaves_like 'round-trip-properly', {
|
|
110
|
+
'normal=1' => 'normal=1',
|
|
111
|
+
'cOmBo=1' => 'cOmBo=1',
|
|
112
|
+
'1=2' => '1=2',
|
|
114
113
|
'aFew42Things=1' => 'aFew42Things=1',
|
|
115
114
|
'under_scores=1' => 'under_scores=1',
|
|
116
115
|
'several.dots.in.here=1' => 'several.dots.in.here=1',
|
|
117
|
-
'cOrN.00copia.of_thinGs.42_here=1' => 'cOrN.00copia.of_thinGs.42_here=1'
|
|
116
|
+
'cOrN.00copia.of_thinGs.42_here=1' => 'cOrN.00copia.of_thinGs.42_here=1'
|
|
118
117
|
}
|
|
119
118
|
end
|
|
120
119
|
context 'supports everything (except &|(),) for values (even without encoding..not allowed, but just to ensure the parser does not bomb)' do
|
|
@@ -123,7 +122,7 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
|
123
122
|
'v=*foo*' => 'v={*}foo{*}',
|
|
124
123
|
'v=*^%$#@!foo' => 'v={*}^%$#@!foo',
|
|
125
124
|
'v=_-=\{}"?:><' => 'v=_-=\{}"?:><',
|
|
126
|
-
'v=_-=\{}"?:><,another_value!' => 'v=[_-=\{}"?:><,another_value!]'
|
|
125
|
+
'v=_-=\{}"?:><,another_value!' => 'v=[_-=\{}"?:><,another_value!]'
|
|
127
126
|
}
|
|
128
127
|
end
|
|
129
128
|
context 'properly detects and handles fuzzy matching encoded as {*} in the dump' do
|
|
@@ -132,7 +131,7 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
|
132
131
|
'v=*foo*' => 'v={*}foo{*}',
|
|
133
132
|
'v=foo*' => 'v=foo{*}',
|
|
134
133
|
'v=*start,end*,*both*' => 'v=[{*}start,end{*},{*}both{*}]',
|
|
135
|
-
"v=*#{CGI.escape('***')},#{CGI.escape('*')}" => 'v=[{*}***,*]'
|
|
134
|
+
"v=*#{CGI.escape('***')},#{CGI.escape('*')}" => 'v=[{*}***,*]' # Simple exact match on 2nd
|
|
136
135
|
}
|
|
137
136
|
end
|
|
138
137
|
context 'properly handles url-encoded values' do
|
|
@@ -141,8 +140,8 @@ describe Praxis::Extensions::AttributeFiltering::FilteringParams::Parser do
|
|
|
141
140
|
"v=*#{CGI.escape('foo')}*" => 'v={*}foo{*}',
|
|
142
141
|
"v=*#{CGI.escape('^%$#@!foo')}" => 'v={*}^%$#@!foo',
|
|
143
142
|
"v=#{CGI.escape('~!@#$%^&*()_+-={}|[]\:";\'<>?,./`')}" => 'v=~!@#$%^&*()_+-={}|[]\:";\'<>?,./`',
|
|
144
|
-
"v=#{CGI.escape('_-+=\{}"?:><')},#{CGI.escape('another_value!')}" => 'v=[_-+=\{}"?:><,another_value!]'
|
|
143
|
+
"v=#{CGI.escape('_-+=\{}"?:><')},#{CGI.escape('another_value!')}" => 'v=[_-+=\{}"?:><,another_value!]'
|
|
145
144
|
}
|
|
146
|
-
end
|
|
145
|
+
end
|
|
147
146
|
end
|
|
148
|
-
end
|
|
147
|
+
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
require 'praxis/extensions/field_selection'
|
|
4
6
|
|
|
5
7
|
describe Praxis::Extensions::FieldExpansion do
|
|
6
|
-
|
|
7
8
|
# include the ActionDefinitionExtension module directly, as that's where the
|
|
8
9
|
# bulk of lies, and by including this instead of the base FieldExpansion module
|
|
9
10
|
# we avoid the side-effect of injecting the ActionDefinitionExtension into
|
|
@@ -24,8 +25,7 @@ describe Praxis::Extensions::FieldExpansion do
|
|
|
24
25
|
|
|
25
26
|
let(:request_params) do
|
|
26
27
|
double('params',
|
|
27
|
-
|
|
28
|
-
)
|
|
28
|
+
fields: Praxis::Extensions::FieldSelection::FieldSelector.for(Person).load(fields))
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
let(:request) { double('Praxis::Request', params: request_params) }
|
|
@@ -33,26 +33,25 @@ describe Praxis::Extensions::FieldExpansion do
|
|
|
33
33
|
|
|
34
34
|
let(:fields) { nil }
|
|
35
35
|
|
|
36
|
-
let(:test_attributes) {
|
|
36
|
+
let(:test_attributes) {}
|
|
37
37
|
let(:test_params) { double('test_params', attributes: test_attributes) }
|
|
38
38
|
|
|
39
|
-
subject(:expansion) { test_instance.expanded_fields(request, media_type)}
|
|
39
|
+
subject(:expansion) { test_instance.expanded_fields(request, media_type) }
|
|
40
40
|
|
|
41
41
|
context '#expanded_fields' do
|
|
42
|
-
|
|
43
42
|
context 'with fields and view params defined' do
|
|
44
43
|
let(:test_attributes) { {} }
|
|
45
44
|
|
|
46
45
|
context 'with no fields provided' do
|
|
47
46
|
it 'returns the fields for the default view' do
|
|
48
|
-
expect(expansion).to eq({id: true, name: true})
|
|
47
|
+
expect(expansion).to eq({ id: true, name: true })
|
|
49
48
|
end
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
context 'with a set of fields provided' do
|
|
53
52
|
let(:fields) { 'id,name,owner{name}' }
|
|
54
53
|
it 'returns the subset of fields' do
|
|
55
|
-
expected = {id: true, name: true }
|
|
54
|
+
expected = { id: true, name: true }
|
|
56
55
|
expect(expansion).to eq expected
|
|
57
56
|
end
|
|
58
57
|
end
|
|
@@ -61,9 +60,8 @@ describe Praxis::Extensions::FieldExpansion do
|
|
|
61
60
|
context 'with an action with no params' do
|
|
62
61
|
let(:test_params) { nil }
|
|
63
62
|
it 'ignores incoming parameters and expands for the default fieldset' do
|
|
64
|
-
expect(expansion).to eq({id: true, name: true})
|
|
63
|
+
expect(expansion).to eq({ id: true, name: true })
|
|
65
64
|
end
|
|
66
65
|
end
|
|
67
66
|
end
|
|
68
|
-
|
|
69
67
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
|
-
require_relative '../support/spec_resources_active_model
|
|
5
|
+
require_relative '../support/spec_resources_active_model'
|
|
4
6
|
|
|
5
7
|
describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
6
8
|
let(:selector_fields) do
|
|
7
|
-
{
|
|
9
|
+
{
|
|
8
10
|
name: true,
|
|
9
11
|
author: {
|
|
10
12
|
id: true,
|
|
@@ -29,13 +31,13 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
|
29
31
|
:id # We always load the primary keys
|
|
30
32
|
]
|
|
31
33
|
end
|
|
32
|
-
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource,selector_fields).selectors
|
|
33
|
-
let(:debug){ false }
|
|
34
|
+
let(:selector_node) { Praxis::Mapper::SelectorGenerator.new.add(ActiveBookResource, selector_fields).selectors }
|
|
35
|
+
let(:debug) { false }
|
|
34
36
|
|
|
35
|
-
subject(:selector) {described_class.new(query: query, selectors: selector_node, debug: debug) }
|
|
37
|
+
subject(:selector) { described_class.new(query: query, selectors: selector_node, debug: debug) }
|
|
36
38
|
context '#generate with a mocked' do
|
|
37
|
-
let(:query) { double(
|
|
38
|
-
it 'calls the select columns for the top level, and includes the right association hashes' do
|
|
39
|
+
let(:query) { double('Query') }
|
|
40
|
+
it 'calls the select columns for the top level, and includes the right association hashes' do
|
|
39
41
|
expect(query).to receive(:select).with(*expected_select_from_to_query).and_return(query)
|
|
40
42
|
expected_includes = {
|
|
41
43
|
author: {
|
|
@@ -49,9 +51,9 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
|
49
51
|
expect(query).to receive(:includes).with(expected_includes).and_return(query)
|
|
50
52
|
expect(subject).to_not receive(:explain_query)
|
|
51
53
|
subject.generate
|
|
52
|
-
end
|
|
54
|
+
end
|
|
53
55
|
context 'when debug is enabled' do
|
|
54
|
-
let(:debug){ true }
|
|
56
|
+
let(:debug) { true }
|
|
55
57
|
it 'calls the explain method' do
|
|
56
58
|
expect(query).to receive(:select).and_return(query)
|
|
57
59
|
expect(query).to receive(:includes).and_return(query)
|
|
@@ -74,7 +76,7 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
|
74
76
|
},
|
|
75
77
|
tags: {}
|
|
76
78
|
}
|
|
77
|
-
#expect(query).to receive(:includes).with(expected_includes).and_return(query)
|
|
79
|
+
# expect(query).to receive(:includes).with(expected_includes).and_return(query)
|
|
78
80
|
expect(subject).to_not receive(:explain_query)
|
|
79
81
|
final_query = subject.generate
|
|
80
82
|
expect(final_query.select_values).to match_array(expected_select_from_to_query)
|
|
@@ -90,7 +92,7 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
|
90
92
|
expect(book1.author.books.size).to eq 1
|
|
91
93
|
expect(book1.author.books.map(&:simple_name)).to eq(['Book1'])
|
|
92
94
|
expect(book1.category.name).to eq 'cat1'
|
|
93
|
-
expect(book1.tags.map(&:name)).to match_array([
|
|
95
|
+
expect(book1.tags.map(&:name)).to match_array(%w[blue red green])
|
|
94
96
|
|
|
95
97
|
expect(book2.author.id).to eq 22
|
|
96
98
|
expect(book2.author.books.size).to eq 1
|
|
@@ -104,7 +106,6 @@ describe Praxis::Extensions::FieldSelection::ActiveRecordQuerySelector do
|
|
|
104
106
|
# Actually make it run all the way...but suppressing the output
|
|
105
107
|
subject.generate
|
|
106
108
|
end
|
|
107
|
-
end
|
|
109
|
+
end
|
|
108
110
|
end
|
|
109
|
-
|
|
110
111
|
end
|