diff --git a/.rubocop.yml b/.rubocop.yml index 41e236ee..a31e42f0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -59,7 +59,7 @@ Style/MethodCalledOnDoEndBlock: Enabled: false Naming/VariableNumber: - Enabled: false + EnforcedColonStyle: normalcase Style/StringLiterals: ConsistentQuotesInMultiline: true diff --git a/lib/graphql_devise.rb b/lib/graphql_devise.rb index 5d789d29..3ec8627e 100644 --- a/lib/graphql_devise.rb +++ b/lib/graphql_devise.rb @@ -2,21 +2,19 @@ require 'graphql' require 'devise_token_auth' +module GraphqlDevise + class Error < StandardError; end + + class InvalidMountOptionsError < GraphqlDevise::Error; end +end + require 'graphql_devise/concerns/controller_methods' require 'graphql_devise/types/authenticatable_type' require 'graphql_devise/types/credential_type' require 'graphql_devise/types/mutation_type' require 'graphql_devise/types/query_type' -require 'graphql_devise/mutations/base' -require 'graphql_devise/mutations/login' -require 'graphql_devise/mutations/logout' -require 'graphql_devise/mutations/resend_confirmation' -require 'graphql_devise/mutations/send_password_reset' -require 'graphql_devise/mutations/sign_up' -require 'graphql_devise/mutations/update_password' -require 'graphql_devise/resolvers/base' -require 'graphql_devise/resolvers/check_password_token' -require 'graphql_devise/resolvers/confirm_account' +require 'graphql_devise/default_operations/mutations' +require 'graphql_devise/default_operations/resolvers' require 'graphql_devise/resolvers/dummy' require 'graphql_devise/engine' @@ -24,11 +22,8 @@ require 'graphql_devise/error_codes' require 'graphql_devise/user_error' require 'graphql_devise/detailed_user_error' -require 'graphql_devise/rails/queries_preparer' -require 'graphql_devise/rails/mutations_preparer' -require 'graphql_devise/rails/operation_checker' -require 'graphql_devise/rails/operation_sanitizer' -module GraphqlDevise - class Error < StandardError; end -end +require 'graphql_devise/mount_method/options_validator' +require 'graphql_devise/mount_method/queries_preparer' +require 'graphql_devise/mount_method/mutations_preparer' +require 'graphql_devise/mount_method/operation_sanitizer' diff --git a/lib/graphql_devise/default_operations/mutations.rb b/lib/graphql_devise/default_operations/mutations.rb new file mode 100644 index 00000000..2b9f76f0 --- /dev/null +++ b/lib/graphql_devise/default_operations/mutations.rb @@ -0,0 +1,20 @@ +require 'graphql_devise/mutations/base' +require 'graphql_devise/mutations/login' +require 'graphql_devise/mutations/logout' +require 'graphql_devise/mutations/resend_confirmation' +require 'graphql_devise/mutations/send_password_reset' +require 'graphql_devise/mutations/sign_up' +require 'graphql_devise/mutations/update_password' + +module GraphqlDevise + module DefaultOperations + MUTATIONS = { + login: GraphqlDevise::Mutations::Login, + logout: GraphqlDevise::Mutations::Logout, + sign_up: GraphqlDevise::Mutations::SignUp, + update_password: GraphqlDevise::Mutations::UpdatePassword, + send_password_reset: GraphqlDevise::Mutations::SendPasswordReset, + resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation + }.freeze + end +end diff --git a/lib/graphql_devise/default_operations/resolvers.rb b/lib/graphql_devise/default_operations/resolvers.rb new file mode 100644 index 00000000..a1be31b9 --- /dev/null +++ b/lib/graphql_devise/default_operations/resolvers.rb @@ -0,0 +1,12 @@ +require 'graphql_devise/resolvers/base' +require 'graphql_devise/resolvers/check_password_token' +require 'graphql_devise/resolvers/confirm_account' + +module GraphqlDevise + module DefaultOperations + QUERIES = { + confirm_account: GraphqlDevise::Resolvers::ConfirmAccount, + check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken + }.freeze + end +end diff --git a/lib/graphql_devise/mount_method/mutations_preparer.rb b/lib/graphql_devise/mount_method/mutations_preparer.rb new file mode 100644 index 00000000..64a467e3 --- /dev/null +++ b/lib/graphql_devise/mount_method/mutations_preparer.rb @@ -0,0 +1,27 @@ +module GraphqlDevise + module MountMethod + class MutationsPreparer + def self.call(resource:, mutations:, authenticatable_type:) + new(resource: resource, mutations: mutations, authenticatable_type: authenticatable_type).call + end + + def initialize(resource:, mutations:, authenticatable_type:) + @mapping_name = resource.underscore.tr('/', '_').to_sym + @mutations = mutations + @authenticatable_type = authenticatable_type + end + + def call + @mutations.each_with_object({}) do |(action, mutation), result| + mapped_action = "#{@mapping_name}_#{action}".to_sym + new_mutation = Class.new(mutation) + new_mutation.graphql_name(mapped_action.to_s.camelize(:upper)) + new_mutation.field(:authenticatable, @authenticatable_type, null: true) + new_mutation.instance_variable_set(:@resource_name, @mapping_name) + + result[mapped_action] = new_mutation + end + end + end + end +end diff --git a/lib/graphql_devise/mount_method/operation_sanitizer.rb b/lib/graphql_devise/mount_method/operation_sanitizer.rb new file mode 100644 index 00000000..50a4578a --- /dev/null +++ b/lib/graphql_devise/mount_method/operation_sanitizer.rb @@ -0,0 +1,35 @@ +module GraphqlDevise + module MountMethod + class OperationSanitizer + def self.call(default:, custom:, only:, skipped:) + new( + default: default, + custom: custom, + only: only, + skipped: skipped + ).call + end + + def initialize(default:, custom:, only:, skipped:) + @default = default + @custom = custom + @only = only + @skipped = skipped + end + + def call + result = @default.merge(@custom.slice(*operations_whitelist)) + result = result.slice(*@only) if @only.present? + result = result.except(*@skipped) if @skipped.present? + + result + end + + private + + def operations_whitelist + @default.keys + end + end + end +end diff --git a/lib/graphql_devise/mount_method/option_validators/provided_operations_validator.rb b/lib/graphql_devise/mount_method/option_validators/provided_operations_validator.rb new file mode 100644 index 00000000..f5ed7a17 --- /dev/null +++ b/lib/graphql_devise/mount_method/option_validators/provided_operations_validator.rb @@ -0,0 +1,44 @@ +require_relative 'supported_operations_validator' + +module GraphqlDevise + module MountMethod + module OptionValidators + class ProvidedOperationsValidator + def initialize(options: {}, supported_operations: {}) + @options = options || {} + @supported_operations = supported_operations + end + + def validate! + skipped = @options.fetch(:skip, []) + only = @options.fetch(:only, []) + operations = @options.fetch(:operations, {}) + supported_keys = @supported_operations.keys + + raise_on_invalid_option_type!(:skip, skipped, Array) + raise_on_invalid_option_type!(:only, only, Array) + raise_on_invalid_option_type!(:operations, operations, Hash) + + custom = operations.keys + + [ + SupportedOperationsValidator.new(provided_operations: skipped, key: :skip, supported_operations: supported_keys), + SupportedOperationsValidator.new(provided_operations: only, key: :only, supported_operations: supported_keys), + SupportedOperationsValidator.new(provided_operations: custom, key: :operations, supported_operations: supported_keys) + ].each(&:validate!) + end + + private + + def raise_on_invalid_option_type!(key, value, expected_class) + unless value.is_a?(expected_class) + raise( + GraphqlDevise::InvalidMountOptionsError, + "#{key} option contains value of invalid value. Value must be #{expected_class.name}." + ) + end + end + end + end + end +end diff --git a/lib/graphql_devise/mount_method/option_validators/skip_only_validator.rb b/lib/graphql_devise/mount_method/option_validators/skip_only_validator.rb new file mode 100644 index 00000000..d14ea306 --- /dev/null +++ b/lib/graphql_devise/mount_method/option_validators/skip_only_validator.rb @@ -0,0 +1,20 @@ +module GraphqlDevise + module MountMethod + module OptionValidators + class SkipOnlyValidator + def initialize(options: {}) + @options = options || {} + end + + def validate! + if [@options[:skip], @options[:only]].all?(&:present?) + raise( + GraphqlDevise::InvalidMountOptionsError, + "Can't specify both `skip` and `only` options when mounting the route." + ) + end + end + end + end + end +end diff --git a/lib/graphql_devise/mount_method/option_validators/supported_operations_validator.rb b/lib/graphql_devise/mount_method/option_validators/supported_operations_validator.rb new file mode 100644 index 00000000..6c54d748 --- /dev/null +++ b/lib/graphql_devise/mount_method/option_validators/supported_operations_validator.rb @@ -0,0 +1,24 @@ +module GraphqlDevise + module MountMethod + module OptionValidators + class SupportedOperationsValidator + def initialize(provided_operations: [], supported_operations: [], key:) + @provided_operations = provided_operations + @supported_operations = supported_operations + @key = key + end + + def validate! + unsupported_operations = @provided_operations - @supported_operations + + if unsupported_operations.present? + raise( + GraphqlDevise::InvalidMountOptionsError, + "#{@key} option contains unsupported operations: \"#{unsupported_operations.join(', ')}\". Check for typos." + ) + end + end + end + end + end +end diff --git a/lib/graphql_devise/mount_method/options_validator.rb b/lib/graphql_devise/mount_method/options_validator.rb new file mode 100644 index 00000000..2c0f295d --- /dev/null +++ b/lib/graphql_devise/mount_method/options_validator.rb @@ -0,0 +1,16 @@ +require_relative 'option_validators/skip_only_validator' +require_relative 'option_validators/provided_operations_validator' + +module GraphqlDevise + module MountMethod + class OptionsValidator + def initialize(validators = []) + @validators = validators + end + + def validate! + @validators.each(&:validate!) + end + end + end +end diff --git a/lib/graphql_devise/mount_method/queries_preparer.rb b/lib/graphql_devise/mount_method/queries_preparer.rb new file mode 100644 index 00000000..f6614774 --- /dev/null +++ b/lib/graphql_devise/mount_method/queries_preparer.rb @@ -0,0 +1,27 @@ +module GraphqlDevise + module MountMethod + class QueriesPreparer + def self.call(resource:, queries:, authenticatable_type:) + new(resource: resource, queries: queries, authenticatable_type: authenticatable_type).call + end + + def initialize(resource:, queries:, authenticatable_type:) + @mapping_name = resource.underscore.tr('/', '_').to_sym + @queries = queries + @authenticatable_type = authenticatable_type + end + + def call + @queries.each_with_object({}) do |(action, query), result| + mapped_action = "#{@mapping_name}_#{action}".to_sym + new_query = Class.new(query) + new_query.graphql_name(mapped_action.to_s.camelize(:upper)) + new_query.type(@authenticatable_type, null: true) + new_query.instance_variable_set(:@resource_name, @mapping_name) + + result[mapped_action] = new_query + end + end + end + end +end diff --git a/lib/graphql_devise/rails/mutations_preparer.rb b/lib/graphql_devise/rails/mutations_preparer.rb deleted file mode 100644 index d5fe0cbf..00000000 --- a/lib/graphql_devise/rails/mutations_preparer.rb +++ /dev/null @@ -1,34 +0,0 @@ -module GraphqlDevise - class MutationsPreparer - DEFAULT_MUTATIONS = { - login: GraphqlDevise::Mutations::Login, - logout: GraphqlDevise::Mutations::Logout, - sign_up: GraphqlDevise::Mutations::SignUp, - update_password: GraphqlDevise::Mutations::UpdatePassword, - send_password_reset: GraphqlDevise::Mutations::SendPasswordReset, - resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation - }.freeze - - def self.call(resource:, mutations:, authenticatable_type:) - new(resource: resource, mutations: mutations, authenticatable_type: authenticatable_type).call - end - - def initialize(resource:, mutations:, authenticatable_type:) - @mapping_name = resource.underscore.tr('/', '_').to_sym - @mutations = mutations - @authenticatable_type = authenticatable_type - end - - def call - @mutations.each_with_object({}) do |(action, mutation), result| - mapped_action = "#{@mapping_name}_#{action}".to_sym - new_mutation = Class.new(mutation) - new_mutation.graphql_name(mapped_action.to_s.camelize(:upper)) - new_mutation.field(:authenticatable, @authenticatable_type, null: true) - new_mutation.instance_variable_set(:@resource_name, @mapping_name) - - result[mapped_action] = new_mutation - end - end - end -end diff --git a/lib/graphql_devise/rails/operation_checker.rb b/lib/graphql_devise/rails/operation_checker.rb deleted file mode 100644 index e22872a1..00000000 --- a/lib/graphql_devise/rails/operation_checker.rb +++ /dev/null @@ -1,59 +0,0 @@ -module GraphqlDevise - class OperationChecker - def self.call(mutations:, queries:, custom:, only:, skipped:) - new( - mutations: mutations, - queries: queries, - custom: custom, - only: only, - skipped: skipped - ).call - end - - def initialize(mutations:, queries:, custom:, only:, skipped:) - @mutations = mutations - @queries = queries - @custom = custom - @only = only - @skipped = skipped - end - - def call - supported_operations = @mutations.keys + @queries.keys - - if [@skipped, @only].all?(&:any?) - raise( - GraphqlDevise::Error, - "Can't specify both `skip` and `only` options when mounting the route." - ) - end - - @custom.keys.each do |custom_op| - next if supported_operations.include?(custom_op) - - raise( - GraphqlDevise::Error, - "Custom operation \"#{custom_op}\" is not supported. Check for typos." - ) - end - - @skipped.each do |skipped_op| - next if supported_operations.include?(skipped_op) - - raise( - GraphqlDevise::Error, - "Trying to skip unsupported operation \"#{skipped_op}\". Check for typos." - ) - end - - @only.each do |only_op| - next if supported_operations.include?(only_op) - - raise( - GraphqlDevise::Error, - "The \"only\" operation \"#{only_op}\" is not supported. Check for typos." - ) - end - end - end -end diff --git a/lib/graphql_devise/rails/operation_sanitizer.rb b/lib/graphql_devise/rails/operation_sanitizer.rb deleted file mode 100644 index 1fb8922e..00000000 --- a/lib/graphql_devise/rails/operation_sanitizer.rb +++ /dev/null @@ -1,33 +0,0 @@ -module GraphqlDevise - class OperationSanitizer - def self.call(default:, custom:, only:, skipped:) - new( - default: default, - custom: custom, - only: only, - skipped: skipped - ).call - end - - def initialize(default:, custom:, only:, skipped:) - @default = default - @custom = custom - @only = only - @skipped = skipped - end - - def call - result = @default.merge(@custom.slice(*operations_whitelist)) - result = result.slice(*@only) if @only.present? - result = result.except(*@skipped) if @skipped.present? - - result - end - - private - - def operations_whitelist - @default.keys - end - end -end diff --git a/lib/graphql_devise/rails/queries_preparer.rb b/lib/graphql_devise/rails/queries_preparer.rb deleted file mode 100644 index f20705ea..00000000 --- a/lib/graphql_devise/rails/queries_preparer.rb +++ /dev/null @@ -1,30 +0,0 @@ -module GraphqlDevise - class QueriesPreparer - DEFAULT_QUERIES = { - confirm_account: GraphqlDevise::Resolvers::ConfirmAccount, - check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken - }.freeze - - def self.call(resource:, queries:, authenticatable_type:) - new(resource: resource, queries: queries, authenticatable_type: authenticatable_type).call - end - - def initialize(resource:, queries:, authenticatable_type:) - @mapping_name = resource.underscore.tr('/', '_').to_sym - @queries = queries - @authenticatable_type = authenticatable_type - end - - def call - @queries.each_with_object({}) do |(action, query), result| - mapped_action = "#{@mapping_name}_#{action}".to_sym - new_query = Class.new(query) - new_query.graphql_name(mapped_action.to_s.camelize(:upper)) - new_query.type(@authenticatable_type, null: true) - new_query.instance_variable_set(:@resource_name, @mapping_name) - - result[mapped_action] = new_query - end - end - end -end diff --git a/lib/graphql_devise/rails/routes.rb b/lib/graphql_devise/rails/routes.rb index 01b30286..1536f511 100644 --- a/lib/graphql_devise/rails/routes.rb +++ b/lib/graphql_devise/rails/routes.rb @@ -10,29 +10,35 @@ class Mapper :invitations ].freeze - def mount_graphql_devise_for(resource, opts = {}) - custom_operations = opts.fetch(:operations, {}) - skipped_operations = opts.fetch(:skip, []) - only_operations = opts.fetch(:only, []) - additional_mutations = opts.fetch(:additional_mutations, {}) - additional_queries = opts.fetch(:additional_queries, {}) - path = opts.fetch(:at, '/graphql_auth') + def mount_graphql_devise_for(resource, options = {}) + default_operations = GraphqlDevise::DefaultOperations::MUTATIONS.merge(GraphqlDevise::DefaultOperations::QUERIES) + + GraphqlDevise::MountMethod::OptionsValidator.new( + [ + GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: options), + GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator.new( + options: options, supported_operations: default_operations + ) + ] + ).validate! + + custom_operations = options.fetch(:operations, {}) + skipped_operations = options.fetch(:skip, []) + only_operations = options.fetch(:only, []) + additional_mutations = options.fetch(:additional_mutations, {}) + additional_queries = options.fetch(:additional_queries, {}) + path = options.fetch(:at, '/graphql_auth') mapping_name = resource.underscore.tr('/', '_').to_sym - authenticatable_type = opts[:authenticatable_type].presence || - "Types::#{resource}Type".safe_constantize || - GraphqlDevise::Types::AuthenticatableType - param_operations = { + authenticatable_type = options[:authenticatable_type].presence || + "Types::#{resource}Type".safe_constantize || + GraphqlDevise::Types::AuthenticatableType + + param_operations = { custom: custom_operations, only: only_operations, skipped: skipped_operations } - GraphqlDevise::OperationChecker.call( - mutations: GraphqlDevise::MutationsPreparer::DEFAULT_MUTATIONS, - queries: GraphqlDevise::QueriesPreparer::DEFAULT_QUERIES, - **param_operations - ) - devise_for( resource.pluralize.underscore.tr('/', '_').to_sym, module: :devise, @@ -40,18 +46,18 @@ def mount_graphql_devise_for(resource, opts = {}) skip: DEVISE_OPERATIONS ) - prepared_mutations = GraphqlDevise::MutationsPreparer.call( + prepared_mutations = GraphqlDevise::MountMethod::MutationsPreparer.call( resource: resource, - mutations: GraphqlDevise::OperationSanitizer.call( - default: GraphqlDevise::MutationsPreparer::DEFAULT_MUTATIONS, **param_operations + mutations: GraphqlDevise::MountMethod::OperationSanitizer.call( + default: GraphqlDevise::DefaultOperations::MUTATIONS, **param_operations ), authenticatable_type: authenticatable_type ) - prepared_queries = GraphqlDevise::QueriesPreparer.call( + prepared_queries = GraphqlDevise::MountMethod::QueriesPreparer.call( resource: resource, - queries: GraphqlDevise::OperationSanitizer.call( - default: GraphqlDevise::QueriesPreparer::DEFAULT_QUERIES, **param_operations + queries: GraphqlDevise::MountMethod::OperationSanitizer.call( + default: GraphqlDevise::DefaultOperations::QUERIES, **param_operations ), authenticatable_type: authenticatable_type ) @@ -61,7 +67,7 @@ def mount_graphql_devise_for(resource, opts = {}) end if (prepared_mutations.present? || additional_mutations.present?) && - (Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?) + (Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?) GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType) end diff --git a/spec/operation_checker_spec.rb b/spec/operation_checker_spec.rb deleted file mode 100644 index b885e441..00000000 --- a/spec/operation_checker_spec.rb +++ /dev/null @@ -1,98 +0,0 @@ -require 'rails_helper' - -RSpec.describe GraphqlDevise::OperationChecker do - describe '.call' do - subject(:result) do - -> { - described_class.call( - mutations: mutations, - queries: queries, - custom: custom, - only: only, - skipped: skipped - ) - } - end - - let(:mutations) { { mutation1: Class.new, mutation2: Class.new } } - let(:queries) { { query1: Class.new, query2: Class.new } } - let(:custom) { {} } - let(:only) { [] } - let(:skipped) { [] } - - context 'when there are not custom, only nor skipped operations' do - it { is_expected.not_to raise_error } - end - - context 'when custom mutations and queries passed are among the defaults' do - let(:custom) { { mutation1: Class.new, query2: Class.new } } - - it { is_expected.not_to raise_error } - end - - context 'when only operations are among the defaults' do - let(:only) { [:mutation1, :query2] } - - it { is_expected.not_to raise_error } - end - - context 'when skipped operations are among the defaults' do - let(:skipped) { [:mutation2, :query1] } - - it { is_expected.not_to raise_error } - end - - context 'when only and skipped operations are both defined' do - let(:only) { [:mutation1, :query2] } - let(:skipped) { [:mutation2, :query1] } - - it { - is_expected.to( - raise_error( - GraphqlDevise::Error, - "Can't specify both `skip` and `only` options when mounting the route." - ) - ) - } - end - - context 'when custom operations include a not supported item' do - let(:custom) { { mutation3: Class.new, query2: Class.new } } - - it { - is_expected.to( - raise_error( - GraphqlDevise::Error, - 'Custom operation "mutation3" is not supported. Check for typos.' - ) - ) - } - end - - context 'when skipped operations include a not supported item' do - let(:skipped) { [:mutation3, :query2] } - - it { - is_expected.to( - raise_error( - GraphqlDevise::Error, - 'Trying to skip unsupported operation "mutation3". Check for typos.' - ) - ) - } - end - - context 'when only operations include a not supported item' do - let(:only) { [:mutation3, :query2] } - - it { - is_expected.to( - raise_error( - GraphqlDevise::Error, - 'The "only" operation "mutation3" is not supported. Check for typos.' - ) - ) - } - end - end -end diff --git a/spec/mutations_preparer_spec.rb b/spec/services/mount_method/mutations_preparer_spec.rb similarity index 94% rename from spec/mutations_preparer_spec.rb rename to spec/services/mount_method/mutations_preparer_spec.rb index 34d24f12..a2c9e3e7 100644 --- a/spec/mutations_preparer_spec.rb +++ b/spec/services/mount_method/mutations_preparer_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe GraphqlDevise::MutationsPreparer do +RSpec.describe GraphqlDevise::MountMethod::MutationsPreparer do describe '.call' do subject do described_class.call( diff --git a/spec/operation_sanitizer_spec.rb b/spec/services/mount_method/operation_sanitizer_spec.rb similarity index 94% rename from spec/operation_sanitizer_spec.rb rename to spec/services/mount_method/operation_sanitizer_spec.rb index 0ecfd99e..a3a20bc8 100644 --- a/spec/operation_sanitizer_spec.rb +++ b/spec/services/mount_method/operation_sanitizer_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe GraphqlDevise::OperationSanitizer do +RSpec.describe GraphqlDevise::MountMethod::OperationSanitizer do describe '.call' do subject { described_class.call(default: default, custom: custom, only: only, skipped: skipped) } diff --git a/spec/services/mount_method/option_validators/provided_operations_validator_spec.rb b/spec/services/mount_method/option_validators/provided_operations_validator_spec.rb new file mode 100644 index 00000000..35de00d0 --- /dev/null +++ b/spec/services/mount_method/option_validators/provided_operations_validator_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' + +RSpec.describe GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator do + describe '#validate!' do + subject { -> { described_class.new(options: provided_operations, supported_operations: supported_operations).validate! } } + + let(:supported_operations) { { operation1: 'irrelevant', operation2: 'irrelevant', operation3: 'irrelevant' } } + + context 'when skip option is provided' do + let(:provided_operations) { { skip: skipped } } + + context 'when all skipped are supported' do + let(:skipped) { [:operation2, :operation3] } + + it { is_expected.not_to raise_error } + end + + context 'when skipped contains unsupported operations' do + let(:skipped) { [:operation2, :operation3, :invalid] } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'skip option contains unsupported operations: "invalid". Check for typos.') } + end + + context 'when provided skip value is not an array' do + let(:skipped) { 'Invalid value' } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'skip option contains value of invalid value. Value must be Array.') } + end + end + + context 'when only option is provided' do + let(:provided_operations) { { only: only } } + + context 'when all only are supported' do + let(:only) { [:operation2, :operation3] } + + it { is_expected.not_to raise_error } + end + + context 'when only contains unsupported operations' do + let(:only) { [:operation2, :operation3, :invalid] } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains unsupported operations: "invalid". Check for typos.') } + end + + context 'when provided only value is not an array' do + let(:only) { 'Invalid value' } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains value of invalid value. Value must be Array.') } + end + end + + context 'when operations option is provided' do + let(:provided_operations) { { operations: operations } } + + context 'when all operations are supported' do + let(:operations) { { operation2: 'irrelevant', operation3: 'irrelevant' } } + + it { is_expected.not_to raise_error } + end + + context 'when operations contains unsupported operations' do + let(:operations) { { operation2: 'irrelevant', operation3: 'irrelevant', invalid: 'invalid' } } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'operations option contains unsupported operations: "invalid". Check for typos.') } + end + + context 'when provided operations value is not a hash' do + let(:operations) { [:one, :two, :three] } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'operations option contains value of invalid value. Value must be Hash.') } + end + end + end +end diff --git a/spec/services/mount_method/option_validators/skip_only_validator_spec.rb b/spec/services/mount_method/option_validators/skip_only_validator_spec.rb new file mode 100644 index 00000000..7d03f124 --- /dev/null +++ b/spec/services/mount_method/option_validators/skip_only_validator_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +RSpec.describe GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator do + describe '#validate!' do + subject { -> { described_class.new(options: options).validate! } } + + context 'when only `only` key is set' do + let(:options) { { only: 'Irrelevant value' } } + + it { is_expected.not_to raise_error } + end + + context 'when only `skip` key is set' do + let(:options) { { skip: 'Irrelevant value' } } + + it { is_expected.not_to raise_error } + end + + context 'when `skip` and `only` keys are set' do + let(:options) { { only: 'Irrelevant value', skip: 'irrelevant for specs' } } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, "Can't specify both `skip` and `only` options when mounting the route.") } + end + + context 'when neither `skip` nor `only are set`' do + let(:options) { { irrelevant_option: 'Irrelevant value', another_option: 'irrelevant for specs' } } + + it { is_expected.not_to raise_error } + end + end +end diff --git a/spec/services/mount_method/option_validators/supported_operations_validator_spec.rb b/spec/services/mount_method/option_validators/supported_operations_validator_spec.rb new file mode 100644 index 00000000..a100af40 --- /dev/null +++ b/spec/services/mount_method/option_validators/supported_operations_validator_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +RSpec.describe GraphqlDevise::MountMethod::OptionValidators::SupportedOperationsValidator do + describe '#validate!' do + subject { -> { described_class.new(provided_operations: provided_operations, supported_operations: supported_operations, key: key).validate! } } + + let(:supported_operations) { [:operation1, :operation2, :operation3] } + let(:key) { :only } + + context 'when custom operations are all supported' do + let(:provided_operations) { [:operation2, :operation3] } + + it { is_expected.not_to raise_error } + end + + context 'when no operations are provided' do + let(:provided_operations) { [] } + + it { is_expected.not_to raise_error } + end + + context 'when default_operations are empty' do + let(:supported_operations) { [] } + let(:provided_operations) { [:invalid] } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains unsupported operations: "invalid". Check for typos.') } + end + + context 'when not all custom operations are supported' do + let(:provided_operations) { [:operation2, :operation3, :unsupported] } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains unsupported operations: "unsupported". Check for typos.') } + end + end +end diff --git a/spec/services/mount_method/options_validator_spec.rb b/spec/services/mount_method/options_validator_spec.rb new file mode 100644 index 00000000..82dc3b4e --- /dev/null +++ b/spec/services/mount_method/options_validator_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +RSpec.describe GraphqlDevise::MountMethod::OptionsValidator do + describe '#validate!' do + subject { -> { described_class.new([validator1, validator2]).validate! } } + + let(:validator1) { double(:validator1, 'validate!': nil) } + let(:validator2) { double(:validator2, 'validate!': nil) } + + context 'when first validator fails' do + before { allow(validator1).to receive(:validate!).and_raise(GraphqlDevise::InvalidMountOptionsError, 'validator1 error') } + + context 'when second validator fails' do + before { allow(validator2).to receive(:validate!).and_raise(GraphqlDevise::InvalidMountOptionsError, 'validator2 error') } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'validator1 error') } + end + + context 'when second validator does not fail' do + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'validator1 error') } + end + end + + context 'when first validator does not fail' do + context 'when second validator fails' do + before { allow(validator2).to receive(:validate!).and_raise(GraphqlDevise::InvalidMountOptionsError, 'validator2 error') } + + it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'validator2 error') } + end + + context 'when second validator does not fail' do + it { is_expected.not_to raise_error } + end + end + end +end diff --git a/spec/queries_preparer_spec.rb b/spec/services/mount_method/queries_preparer_spec.rb similarity index 94% rename from spec/queries_preparer_spec.rb rename to spec/services/mount_method/queries_preparer_spec.rb index 740032f1..b89e2030 100644 --- a/spec/queries_preparer_spec.rb +++ b/spec/services/mount_method/queries_preparer_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe GraphqlDevise::QueriesPreparer do +RSpec.describe GraphqlDevise::MountMethod::QueriesPreparer do describe '.call' do subject do described_class.call(