From 15b9a3bacc4a1c959ea09c82bb84c80ef86047b7 Mon Sep 17 00:00:00 2001 From: Patricio de Villa Date: Wed, 20 Mar 2024 14:29:56 -0600 Subject: [PATCH] add rubocop, github actions --- .github/workflows/test.yml | 25 + .gitignore | 4 +- .rubocop-minitest.yml | 12 + .rubocop.yml | 59 ++ .rubocop_todo.yml | 99 ++++ Gemfile | 20 +- Gemfile.lock | 59 -- README.md | 4 +- Rakefile | 20 +- bin/console | 7 +- body_builder.gemspec | 53 +- lib/body_builder.rb | 87 +-- lib/body_builder/clause.rb | 30 +- lib/body_builder/version.rb | 4 +- test/spec/body_builder_spec.rb | 979 -------------------------------- test/spec/body_builder_test.rb | 988 +++++++++++++++++++++++++++++++++ test/test_helper.rb | 14 +- 17 files changed, 1317 insertions(+), 1147 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 .rubocop-minitest.yml create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml delete mode 100644 Gemfile.lock delete mode 100644 test/spec/body_builder_spec.rb create mode 100644 test/spec/body_builder_test.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b90d526 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test + +on: [pull_request] + +# permissions: +# contents: read + +jobs: + lint: + runs-on: ubuntu-latest + # env: + # BUNDLE_ONLY: rubocop + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby 3.2.2 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2.2 + bundler-cache: true + + - name: Run Tests + run: bundle exec rake + # run: bundle exec rubocop --parallel \ No newline at end of file diff --git a/.gitignore b/.gitignore index abd3323..1f8fbb9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,4 @@ /pkg/ /spec/reports/ /tmp/ - -# Ignore Byebug command history file. -.byebug_history \ No newline at end of file +Gemfile.lock \ No newline at end of file diff --git a/.rubocop-minitest.yml b/.rubocop-minitest.yml new file mode 100644 index 0000000..aea76a5 --- /dev/null +++ b/.rubocop-minitest.yml @@ -0,0 +1,12 @@ +########################################################### +#################### Rubocop Minitest ##################### +########################################################### + +Minitest/AssertTruthy: + Enabled: false + +Minitest/RefuteFalse: + Enabled: false + +Minitest/MultipleAssertions: + Max: 4 \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..1b5e0a0 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,59 @@ +# see example at https://gist.github.com/jhass/a5ae80d87f18e53e7b56 + +# <% unless ENV['BYPASS_RUBOCOP_TODO'] %> +# inherit_from: +# <% else %> +# inherit_from: +# - '.rubocop-todo.yml' +# <% end %> + +inherit_from: + - .rubocop_todo.yml + - .rubocop-minitest.yml + +require: + - rubocop-minitest + - rubocop-rake + - rubocop-performance + +AllCops: + NewCops: enable + # TargetRubyVersion: 2.7.8 + # TargetRailsVersion: 6.1.4 + # Exclude: + # - 'Gemfile.lock' + +Naming/VariableNumber: + Enabled: false + +Layout/SpaceInsideHashLiteralBraces: + Enabled: false + +Layout/EmptyLinesAroundModuleBody: + EnforcedStyle: empty_lines_special + Enabled: false + +Layout/TrailingEmptyLines: + Enabled: false + EnforcedStyle: final_blank_line + +Layout/EmptyLinesAroundClassBody: + Enabled: false + +Style/RaiseArgs: + EnforcedStyle: compact + +Layout/CaseIndentation: + EnforcedStyle: end + +Layout/MultilineMethodCallIndentation: + EnforcedStyle: indented + +Layout/IndentationWidth: + Enabled: false + +Layout/EndAlignment: + Enabled: false + +Layout/ElseAlignment: + Enabled: false \ No newline at end of file diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..ceff0c7 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,99 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2024-03-20 20:20:07 UTC using RuboCop version 1.62.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# Configuration parameters: Severity, Include. +# Include: **/*.gemspec +Gemspec/RequiredRubyVersion: + Exclude: + - 'body_builder.gemspec' + +# Offense count: 5 +Lint/ShadowingOuterLocalVariable: + Exclude: + - 'test/spec/body_builder_test.rb' + +# Offense count: 3 +# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 118 + +# Offense count: 2 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +# AllowedMethods: refine +Metrics/BlockLength: + Max: 55 + +# Offense count: 2 +# Configuration parameters: CountBlocks. +Metrics/BlockNesting: + Max: 4 + +# Offense count: 2 +# Configuration parameters: CountComments, CountAsOne. +Metrics/ClassLength: + Max: 852 + +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/CyclomaticComplexity: + Max: 49 + +# Offense count: 20 +# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. +Metrics/MethodLength: + Exclude: + - 'test/spec/super_hash/utils_spec.rb' + - 'lib/body_builder.rb' + - 'lib/body_builder/clause.rb' + - 'test/spec/body_builder_test.rb' + +# Offense count: 3 +# Configuration parameters: CountKeywordArgs. +Metrics/ParameterLists: + Max: 6 + MaxOptionalParameters: 4 + +# Offense count: 2 +# Configuration parameters: AllowedMethods, AllowedPatterns. +Metrics/PerceivedComplexity: + Max: 56 + +# Offense count: 1 +# Configuration parameters: Max. +Minitest/MultipleAssertions: + Exclude: + - 'test/spec/body_builder_test.rb' + +# Offense count: 4 +Naming/AccessorMethodName: + Exclude: + - 'lib/body_builder.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# AllowedMethods: lambda, proc, it +Style/BlockDelimiters: + Exclude: + - 'test/spec/body_builder_test.rb' + +# Offense count: 1 +# This cop supports unsafe autocorrection (--autocorrect-all). +# Configuration parameters: InverseMethods, InverseBlocks. +Style/InverseMethods: + Exclude: + - 'lib/body_builder.rb' + +# Offense count: 10 +Style/MultilineBlockChain: + Exclude: + - 'test/spec/body_builder_test.rb' diff --git a/Gemfile b/Gemfile index 7d990ed..b52077b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,8 +1,22 @@ -source "https://rubygems.org" +# frozen_string_literal: true -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +source 'https://rubygems.org' + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } # gem 'super_hash', git: 'https://github.com/prysmex/super_hash.git' # Specify your gem's dependencies in json_schema_form.gemspec -gemspec \ No newline at end of file +gemspec + +# gem 'bundler', '2.4.22' +gem 'debug', '>= 1.0.0' +gem 'minitest', '~> 5.14' +gem 'minitest-reporters', '~> 1.6' +gem 'rake', '~> 13.1' + +# rubocop +gem 'rubocop', '~> 1.62' +gem 'rubocop-minitest', '~> 0.35' +gem 'rubocop-performance', '~> 1.20' +gem 'rubocop-rake', '~> 0.6' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 5847690..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,59 +0,0 @@ -PATH - remote: . - specs: - body_builder (0.1.7) - activesupport (~> 6) - -GEM - remote: https://rubygems.org/ - specs: - activesupport (6.1.7.3) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 1.6, < 2) - minitest (>= 5.1) - tzinfo (~> 2.0) - zeitwerk (~> 2.3) - ansi (1.5.0) - builder (3.2.4) - concurrent-ruby (1.2.2) - debug (1.8.0) - irb (>= 1.5.0) - reline (>= 0.3.1) - i18n (1.14.1) - concurrent-ruby (~> 1.0) - io-console (0.6.0) - irb (1.9.1) - rdoc - reline (>= 0.3.8) - minitest (5.14.4) - minitest-reporters (1.6.1) - ansi - builder - minitest (>= 5.0) - ruby-progressbar - psych (5.1.1.1) - stringio - rake (13.1.0) - rdoc (6.6.0) - psych (>= 4.0.0) - reline (0.4.1) - io-console (~> 0.5) - ruby-progressbar (1.13.0) - stringio (3.1.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - zeitwerk (2.6.8) - -PLATFORMS - ruby - -DEPENDENCIES - body_builder! - bundler (= 2.4.22) - debug (>= 1.0.0) - minitest (~> 5.14) - minitest-reporters (~> 1.6) - rake (~> 13.1) - -BUNDLED WITH - 2.4.22 diff --git a/README.md b/README.md index cc00e26..8ee984b 100644 --- a/README.md +++ b/README.md @@ -178,8 +178,8 @@ BodyBuilder::Builder.new ### Other methods -- `has_queries?` -- `has_filters?` +- `queries?` +- `filters?` ### Reset diff --git a/Rakefile b/Rakefile index 353b967..ef67811 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,18 @@ -require "bundler/gem_tasks" -require "rake/testtask" +# frozen_string_literal: true -Rake::TestTask.new do |t| - t.libs << "test" - t.test_files = FileList["test/**/*_spec.rb"] +require 'bundler/gem_tasks' +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| t.verbose = true + t.warning = true + t.libs << 'test' + t.libs << 'lib' + t.test_files = FileList['test/**/*_test.rb'] end -task :default => :test +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +task default: %i[test rubocop] diff --git a/bin/console b/bin/console index b73ae5d..ca78be7 100755 --- a/bin/console +++ b/bin/console @@ -1,7 +1,8 @@ #!/usr/bin/env ruby +# frozen_string_literal: true -require "bundler/setup" -require "body_builder" +require 'bundler/setup' +require 'body_builder' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. @@ -10,5 +11,5 @@ require "body_builder" # require "pry" # Pry.start -require "irb" +require 'irb' IRB.start(__FILE__) diff --git a/body_builder.gemspec b/body_builder.gemspec index bb24f70..c849c38 100644 --- a/body_builder.gemspec +++ b/body_builder.gemspec @@ -1,44 +1,35 @@ +# frozen_string_literal: true -lib = File.expand_path("../lib", __FILE__) -$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "body_builder/version" +require_relative 'lib/body_builder/version' Gem::Specification.new do |spec| - spec.name = "body_builder" + spec.name = 'body_builder' spec.version = BodyBuilder::VERSION - spec.authors = ["Pato"] - spec.email = ["pato_devilla@hotmail.com"] + spec.authors = ['Pato'] + spec.email = ['pato_devilla@hotmail.com'] - spec.summary = %q{Simple Elasticsearch query builder} - spec.description = %q{Simple Elasticsearch query builder} + spec.summary = 'Simple Elasticsearch query builder' + spec.description = 'Simple Elasticsearch query builder' # spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.license = 'MIT' + spec.required_ruby_version = '>= 3.1.0' - # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' - # to allow pushing to a single host or delete this section to allow pushing to any host. - # if spec.respond_to?(:metadata) - # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - - # spec.metadata["homepage_uri"] = spec.homepage - # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - # else - # raise "RubyGems 2.0 or newer is required to protect against " \ - # "public gem pushes." - # end + # spec.metadata["homepage_uri"] = spec.homepage + # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." + # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata['rubygems_mfa_required'] = 'true' # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject do |f| + (File.expand_path(f) == __FILE__) || + f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile]) + end end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] + spec.bindir = 'exe' + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ['lib'] - spec.add_development_dependency "bundler", "2.4.22" - spec.add_development_dependency "rake", "~> 13.1" - spec.add_development_dependency "debug", ">= 1.0.0" - spec.add_development_dependency "minitest", "~> 5.14" - spec.add_development_dependency "minitest-reporters", "~> 1.6" - spec.add_dependency "activesupport", '~> 6' + spec.add_dependency 'activesupport', '~> 6' end diff --git a/lib/body_builder.rb b/lib/body_builder.rb index c2c87b5..0356aec 100644 --- a/lib/body_builder.rb +++ b/lib/body_builder.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'body_builder/version' require 'body_builder/clause' require 'active_support/core_ext/hash/deep_merge' @@ -12,7 +14,7 @@ module BodyBuilder # For examples, see the specs # class Builder - + attr_reader :filters, :queries, :raw_options, :sort_fields, :parent attr_accessor :base_query, :size, :from, :query_minimum_should_match, :filter_minimum_should_match @@ -25,44 +27,44 @@ def initialize(base_query: {}, parent: nil) @parent = parent reset! end - + # Adds a *and* *filter* clause. For examples, see the specs # # @return [Builder] self def filter(*args, &block) _add_clause(true, :and, *args, &block) end - alias_method :and_filter, :filter - + alias and_filter filter + # Adds a *or* *filter* clause. For examples, see the specs # # @return [Builder] self def or_filter(*args, &block) _add_clause(true, :or, *args, &block) end - + # Adds a *not* *filter* clause. For examples, see the specs # # @return [Builder] self def not_filter(*args, &block) _add_clause(true, :not, *args, &block) end - + # Adds a *and* *query* clause. For examples, see the specs # # @return [Builder] self def query(*args, &block) _add_clause(false, :and, *args, &block) end - alias_method :and_query, :query - + alias and_query query + # Adds a *or* *query* clause. For examples, see the specs # # @return [Builder] self def or_query(*args, &block) _add_clause(false, :or, *args, &block) end - + # Adds a *not* *query* clause. For examples, see the specs # # @return [Builder] self @@ -86,14 +88,16 @@ def raw_option(key, value) # @param [String] direction ('asc' or 'desc') # @return [Builder] self def sort_field(field, direction = 'asc') - raise ArgumentError.new("direction must be 'asc' or 'desc', got '#{direction}'") unless ['desc', 'asc'].include?(direction.to_s.downcase) + unless %w[desc asc].include?(direction.to_s.downcase) + raise ArgumentError.new("direction must be 'asc' or 'desc', got '#{direction}'") + end field = field.to_sym - sort = sort_fields.find{|obj| obj.key?(field) } + sort = sort_fields.find { |obj| obj.key?(field) } if sort sort[field] = direction else - @sort_fields << {"#{field}".to_sym => direction} + @sort_fields << {"#{field}": direction} end self end @@ -139,64 +143,63 @@ def set_filter_minimum_should_match(value) # # @param [Symbol] key (:and, :or, :not) # @return [Boolean] true if any filter clause is present - def has_filters?(key=nil) - @filters.any? do |k,v| + def filters?(key = nil) + @filters.any? do |k, v| next if key && k != key + !v.empty? end end - + # Checks if builder has any *query* clauses. If a key # is passed as an argument, it ignores all other keys. # # @param [Symbol] key (:and, :or, :not) # @return [Boolean] true if any query clause is present - def has_queries?(key=nil) - @queries.any? do |k,v| + def queries?(key = nil) + @queries.any? do |k, v| next if key && k != key + !v.empty? end end - + # Builds the elasticsearch query # # @return [Hash] built elasticsearch query def build - # TODO should this validation be recursive and optional? - if parent&.is_filter && has_queries? - raise StandardError.new('cannot query when parent is filter') - end + # TODO: should this validation be recursive and optional? + raise StandardError.new('cannot query when parent is filter') if parent&.is_filter && queries? - query = Marshal.load(Marshal.dump(self.base_query)) #dup - query.deep_transform_keys!{|k| k.to_sym} + query = Marshal.load(Marshal.dump(base_query)) # dup + query.deep_transform_keys!(&:to_sym) base_query_is_bool = query[:query]&.key?(:bool) if query.key?(:query) && !base_query_is_bool raise StandardError.new('cannot build query when base query root is not bool clause') end - is_simple_filter = ( + is_simple_filter = only_one_and_clause?(:filters) || ( filters[:or].empty? && - !self.has_queries? && - !filters.any?{|_key, clauses| clauses.any?{|c| c.block }} + !queries? && + !filters.any? { |_key, clauses| clauses.any?(&:block) } ) - ) # Process queries and filters - if !parent && !base_query_is_bool && !self.has_filters? && only_one_and_clause?(:queries) - query[:query] = self.queries[:and].first.build + if !parent && !base_query_is_bool && !filters? && only_one_and_clause?(:queries) + query[:query] = queries[:and].first.build else {filters: @filters, queries: @queries}.each do |type, object| current_is_simple_filter = is_simple_filter && type == :filters # build bool clause object.each do |key, clauses| next if clauses.empty? - + built_clauses = clauses.map(&:build) built_clauses = built_clauses.first if clauses.size == 1 # && !base_query_is_bool - + scope = if parent&.is_filter # query query[:query] ||= {} @@ -250,14 +253,12 @@ def build # add minimum_should_match for query or filter if more than 1 clause if mapped_key == :should && scope[:should].is_a?(Array) if type == :queries - scope[:minimum_should_match] = self.query_minimum_should_match unless self.query_minimum_should_match.nil? + scope[:minimum_should_match] = query_minimum_should_match unless query_minimum_should_match.nil? else - scope[:minimum_should_match] = self.filter_minimum_should_match unless self.filter_minimum_should_match.nil? + scope[:minimum_should_match] = filter_minimum_should_match unless filter_minimum_should_match.nil? end end - end - end end @@ -271,7 +272,7 @@ def build query[:sort] = sort_fields unless sort_fields.empty? query[:size] = @size unless @size.nil? query[:from] = @from unless @from.nil? - + query end @@ -283,8 +284,8 @@ def reset! reset_filters! reset_raw_options! reset_sort_fields! - from = nil - size = nil + self.from = nil + self.size = nil end # Empties all *query* caluses from the builder @@ -322,7 +323,7 @@ def reset_raw_options! def reset_sort_fields! @sort_fields = [] end - + private # Adds a clause to the builder @@ -335,8 +336,8 @@ def reset_sort_fields! # @param [Hash] options (optional) # @param &block (optional) # @return [Builder] builder with added clause - def _add_clause(is_filter, key, type, field=nil, value=nil, options={}, &block) - obj = is_filter ? self.filters : self.queries + def _add_clause(is_filter, key, type, field = nil, value = nil, options = {}, &block) + obj = is_filter ? filters : queries obj[key] << Clause.new(type, is_filter, field, value, self, options, &block) self end @@ -346,7 +347,7 @@ def _add_clause(is_filter, key, type, field=nil, value=nil, options={}, &block) # @param [Symbol] type (:filters or :queries) # @return [Boolean] def only_one_and_clause?(type) - object = type == :filters ? self.filters : self.queries + object = type == :filters ? filters : queries object[:and].length == 1 && object[:or].empty? && object[:not].empty? end diff --git a/lib/body_builder/clause.rb b/lib/body_builder/clause.rb index a86cf2d..17a0991 100644 --- a/lib/body_builder/clause.rb +++ b/lib/body_builder/clause.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module BodyBuilder # # This class is used by BodyBuilder::Builder class to create a query. @@ -5,18 +7,20 @@ module BodyBuilder # class Clause + MATCH_KEYS = %i[must must_not filter should].freeze + attr_accessor :type, :is_filter, :field, :value, :options, :block attr_reader :parent - + # Initialization method # - # @param [String] type examples: (terms, match, match_all, etc...) + # @param [String] type examples: (terms, match, match_all, etc...) # @param [Boolean] is_filter # @param [String] field name of the field # @param [String, Integer, Array] value to be used by query o filter # @param [Builder] parent # @param [Hash] options used to support params in clause - def initialize(type, is_filter, field=nil, value=nil, parent=nil, options={}, &block) + def initialize(type, is_filter, field = nil, value = nil, parent = nil, options = {}, &block) @type = type @is_filter = is_filter @field = field @@ -25,13 +29,13 @@ def initialize(type, is_filter, field=nil, value=nil, parent=nil, options={}, &b @options = options @block = block end - + # Builds the elasticsearch query clause # # @return [Hash] elasticsearch query clause def build hash = if !@value.nil? - {"#{@field}".to_sym => @value} + {"#{@field}": @value} elsif @field.is_a? Hash @field elsif @field @@ -41,23 +45,23 @@ def build end hash.merge!(@options || {}) - + if @block.is_a? Proc builder = BodyBuilder::Builder.new(parent: self) child_hash = @block.call(builder).build if type.to_sym != :bool - if child_hash.any?{|k,v| [:must, :must_not, :filter, :should].include?(k.to_sym) } - child_hash = {query: {bool: child_hash}} + child_hash = if child_hash.any? { |k, _v| MATCH_KEYS.include?(k.to_sym) } + {query: {bool: child_hash}} else - child_hash = {query: child_hash} - end + {query: child_hash} + end end child_hash = child_hash[:bool] if child_hash.key?(:bool) hash.merge!(child_hash) end - - return {"#{@type}".to_sym => hash} + + {"#{@type}": hash} end - + end end \ No newline at end of file diff --git a/lib/body_builder/version.rb b/lib/body_builder/version.rb index 06c0c9c..4fb8213 100644 --- a/lib/body_builder/version.rb +++ b/lib/body_builder/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module BodyBuilder - VERSION = "0.2.0" + VERSION = '0.2.1' end diff --git a/test/spec/body_builder_spec.rb b/test/spec/body_builder_spec.rb deleted file mode 100644 index 0c0f6fc..0000000 --- a/test/spec/body_builder_spec.rb +++ /dev/null @@ -1,979 +0,0 @@ -require 'test_helper' -require 'json' -require 'debug' - -module BuilderTestMethods - def setup - @builder = BodyBuilder::Builder.new - end -end - -class HelperTest < Minitest::Test - - include BuilderTestMethods - - def compare_jsons(a, b) - assert_equal a, b - end - - def test_respond_to_methods - #query - assert_respond_to @builder, :query - assert_respond_to @builder, :and_query - assert_respond_to @builder, :or_query - assert_respond_to @builder, :not_query - - #filter - assert_respond_to @builder, :filter - assert_respond_to @builder, :and_filter - assert_respond_to @builder, :or_filter - assert_respond_to @builder, :not_filter - - #other - assert_respond_to @builder, :base_query - assert_respond_to @builder, :raw_option - assert_respond_to @builder, :sort_field - - assert_respond_to @builder, :has_queries? - assert_respond_to @builder, :has_filters? - - assert_respond_to @builder, :reset! - assert_respond_to @builder, :reset_queries! - assert_respond_to @builder, :reset_filters! - assert_respond_to @builder, :reset_raw_options! - assert_respond_to @builder, :reset_sort_fields! - end - - def test_size - a = @builder - .set_size(5) - .build - b = { "size": 5 } - compare_jsons(a, b) - end - - def test_from - a = @builder - .set_from(50) - .build - b = { "from": 50 } - compare_jsons(a, b) - end - - def test_raw_option - a = @builder - .raw_option(:size, 5) - .build - b = { "size": 5 } - compare_jsons(a, b) - end - - def test_sort_field - a = @builder - .sort_field(:id, 'desc') - .build - b = { "sort": [{id: 'desc'}] } - compare_jsons(a, b) - end - - def test_override_sort_field - a = @builder - .sort_field(:id, 'desc') - .sort_field(:id, 'asc') - .build - b = { "sort": [{id: 'asc'}] } - compare_jsons(a, b) - end - - def test_has_queries? - assert_equal false, @builder.has_queries? - @builder.filter('match_all') - assert_equal false, @builder.has_queries? - @builder.query('match_all') - assert_equal true, @builder.has_queries? - end - - def test_has_filters? - assert_equal false, @builder.has_filters? - @builder.query('match_all') - assert_equal false, @builder.has_filters? - @builder.filter('match_all') - assert_equal true, @builder.has_filters? - end - - def test_reset_queries! - @builder.query('match_all') - @builder.reset_queries! - assert_equal false, @builder.has_queries? - end - - def test_reset_filters! - @builder.filter('match_all') - @builder.reset_filters! - assert_equal false, @builder.has_filters? - end - - def test_reset_raw_options! - @builder.raw_option('size', 10) - assert_equal 1, @builder.raw_options.size - @builder.reset_raw_options! - assert_empty @builder.raw_options - end - - def reset_sort_fields! - @builder.sort_field('id', 'asc') - assert_equal 1, @builder.sort_fields.size - @builder.reset_sort_fields! - assert_empty @builder.sort_fields - end - - # def test_reset! - # end - - ###### - #ARGS# - ###### - - def test_single_query_1_arg - a = @builder - .query('match_all') - .build - b = { "query": { "match_all": {} } } - compare_jsons(a, b) - end - - def test_single_query_2_args - a = @builder - .query('exists', 'user') - .build - b = { "query": { "exists": { "field": "user" } } } - compare_jsons(a, b) - end - - def test_single_query_3_args - a = @builder - .query('range', 'date', {gt: 'now-1d'}) - .build() - b = { "query": { "range": { "date": { "gt": "now-1d" } } } } - compare_jsons(a, b) - end - - def test_single_query_4_args - a = @builder - .query('geo_distance', 'point', {lat: 40, lon: 20}, {distance: '12km'}) - .build() - b = { - "query": { - "geo_distance": { "point": { "lat": 40, "lon": 20 }, "distance": "12km" } - } - } - compare_jsons(a, b) - end - - ######### - #FILTERS# - ######### - - # SINGLES - - def test_single_filter_boolean - a = @builder - .filter('term', 'active', false) - .build - b = { "query": { "bool": { "filter": { "term": { "active": false } } } } } - compare_jsons(a, b) - end - - def test_single_filter - a = @builder - .filter('terms', 'tags', ['Emerging']) - .build - b = { "query": { "bool": { "filter": { "terms": { "tags": [ "Emerging" ] } } } } } - compare_jsons(a, b) - end - - def test_single_and_filter - a = @builder - .and_filter('terms', 'tags', ['Emerging']) - .build - b = { "query": { "bool": { "filter": { "terms": { "tags": [ "Emerging" ] } } } } } - compare_jsons(a, b) - end - - def test_single_or_filter - a = @builder - .or_filter('terms', 'tags', ['Emerging']) - .build - b = { - "query": { - "bool": { - "filter": { "bool": { "should": { "terms": { "tags": ["Emerging"] } } } } - } - } - } - compare_jsons(a, b) - end - - def test_single_not_filter - a = @builder - .not_filter('terms', 'tags', ['Emerging']) - .build - b = { "query": { "bool": { "must_not": { "terms": { "tags": [ "Emerging" ] } } } } } - compare_jsons(a, b) - end - - # COMBINED - - def test_all_filters - a = @builder - .filter('terms', 'tags', 'filter') - .or_filter('terms', 'tags', 'or_filter') - .not_filter('terms', 'tags', 'not_filter') - .build - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": { "terms": { "tags": "filter" } }, - "should": { "terms": { "tags": "or_filter" } }, - "must_not": { "terms": { "tags": "not_filter" } } - } - } - } - } - } - compare_jsons(a, b) - end - - def test_repeated_filters - a = @builder - .filter('terms', 'tags', 'filter') - .filter('terms', 'tags', 'filter') - .or_filter('terms', 'tags', 'or_filter') - .or_filter('terms', 'tags', 'or_filter') - .not_filter('terms', 'tags', 'not_filter') - .not_filter('terms', 'tags', 'not_filter') - .build - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": [ - { "terms": { "tags": "filter" } }, - { "terms": { "tags": "filter" } } - ], - "should": [ - { "terms": { "tags": "or_filter" } }, - { "terms": { "tags": "or_filter" } } - ], - "must_not": [ - { "terms": { "tags": "not_filter" } }, - { "terms": { "tags": "not_filter" } } - ] - } - } - } - } - } - compare_jsons(a, b) - end - - # Block - - def test_all_filter_blocks - a = @builder - .filter('bool') do |f| - f.filter("match", "message", "filter") - end - .not_filter('bool') do |f| - f.filter("match", "message", "not_filter") - end - .or_filter('bool') do |f| - f.filter("match", "message", "or_filter") - end - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": { - "bool": { "filter": { "match": { "message": "filter" } } } - }, - "should": { - "bool": { "filter": { "match": { "message": "or_filter" } } } - }, - "must_not": { - "bool": { "filter": { "match": { "message": "not_filter" } } } - } - } - } - } - } - } - - compare_jsons(a, b) - end - - def test_all_filter_blocks_double - a = @builder - .filter('bool') do |f| - f.filter("match", "message", "filter") - f.filter("match", "message", "filter") - end - .not_filter('bool') do |f| - f.filter("match", "message", "not_filter") - f.filter("match", "message", "not_filter") - end - .or_filter('bool') do |f| - f.filter("match", "message", "or_filter") - f.filter("match", "message", "or_filter") - end - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": { - "bool": { "filter": [{ "match": { "message": "filter" } }, { "match": { "message": "filter" } }] } - }, - "should": { - "bool": { "filter": [{ "match": { "message": "or_filter" } }, { "match": { "message": "or_filter" } }] } - }, - "must_not": { - "bool": { "filter": [{ "match": { "message": "not_filter" } }, { "match": { "message": "not_filter" } }] } - } - } - } - } - } - } - - compare_jsons(a, b) - end - - def test_all_filter_blocks_nested - a = @builder - .filter('bool') do |f| - f.filter("match", "message", "filter") - f.not_filter('bool') do |f| - f.filter("match", "message", "not_filter") - f.or_filter('bool') do |f| - f.filter("match", "message", "or_filter") - end - end - end - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": { "match": { "message": "filter" } }, - "must_not": { - "bool": { - "must": { "match": { "message": "not_filter" } }, - "should": { - "bool": { "filter": { "match": { "message": "or_filter" } } } - } - } - } - } - } - } - } - } - - compare_jsons(a, b) - end - - ####### - #QUERY# - ####### - - # SINGLES - - def test_single_query - a = @builder - .query('match', 'tags', 'Emerging') - .build - b = { "query": { "match": { "tags": "Emerging" } } } - compare_jsons(a, b) - end - - def test_single_and_query - a = @builder - .query('match', 'tags', 'Emerging') - .build - b = { "query": { "match": { "tags": "Emerging" } } } - compare_jsons(a, b) - end - - def test_single_or_query - a = @builder - .or_query('terms', 'tags', ['Emerging']) - .build() - b = { "query": { "bool": { "should": { "terms": { "tags": [ "Emerging" ] } } } } } - compare_jsons(a, b) - end - - def test_single_not_query - a = @builder - .not_query('terms', 'tags', ['Emerging']) - .build() - b = { "query": { "bool": { "must_not": { "terms": { "tags": [ "Emerging" ] } } } } } - compare_jsons(a, b) - end - - # COMBINED - - def test_all_querys - a = @builder - .query('terms', 'tags', 'query') - .or_query('terms', 'tags', 'or_query') - .not_query('terms', 'tags', 'not_query') - .build - b = { - "query": { - "bool": { - "must": { "terms": { "tags": "query" } }, - "should": { "terms": { "tags": "or_query" } }, - "must_not": { "terms": { "tags": "not_query" } } - } - } - } - compare_jsons(a, b) - end - - def test_repeated_queries - a = @builder - .query('terms', 'tags', 'query') - .query('terms', 'tags', 'query') - .or_query('terms', 'tags', 'or_query') - .or_query('terms', 'tags', 'or_query') - .not_query('terms', 'tags', 'not_query') - .not_query('terms', 'tags', 'not_query') - .build - b = { - "query": { - "bool": { - "must": [ - { "terms": { "tags": "query" } }, - { "terms": { "tags": "query" } } - ], - "should": [ - { "terms": { "tags": "or_query" } }, - { "terms": { "tags": "or_query" } } - ], - "must_not": [ - { "terms": { "tags": "not_query" } }, - { "terms": { "tags": "not_query" } } - ] - } - } - } - compare_jsons(a, b) - end - - ####### - #BLOCK# - ####### - - def test_all_query_blocks - a = @builder - .query('bool') do |f| - f.query("match", "message", "query") - end - .not_query('bool') do |f| - f.query("match", "message", "not_query") - end - .or_query('bool') do |f| - f.query("match", "message", "or_query") - end - .build() - b = { - "query": { - "bool": { - "must": { "bool": { "must": { "match": { "message": "query" } } } }, - "should": { "bool": { "must": { "match": { "message": "or_query" } } } }, - "must_not": { - "bool": { "must": { "match": { "message": "not_query" } } } - } - } - } - } - compare_jsons(a, b) - end - - def test_all_query_blocks_double - a = @builder - .query('bool') do |f| - f.query("match", "message", "query") - f.query("match", "message", "query") - end - .not_query('bool') do |f| - f.query("match", "message", "not_query") - f.query("match", "message", "not_query") - end - .or_query('bool') do |f| - f.query("match", "message", "or_query") - f.query("match", "message", "or_query") - end - .build() - b = { - "query": { - "bool": { - "must": { - "bool": { - "must": [ - { "match": { "message": "query" } }, - { "match": { "message": "query" } } - ] - } - }, - "should": { - "bool": { - "must": [ - { "match": { "message": "or_query" } }, - { "match": { "message": "or_query" } } - ] - } - }, - "must_not": { - "bool": { - "must": [ - { "match": { "message": "not_query" } }, - { "match": { "message": "not_query" } } - ] - } - } - } - } - } - - compare_jsons(a, b) - end - - def test_all_query_blocks_nested - a = @builder - .query('bool') do |f| - f.query("match", "message", "query") - f.not_query('bool') do |f| - f.query("match", "message", "not_query") - f.or_query('bool') do |f| - f.query("match", "message", "or_query") - end - end - end - .build() - b = { - "query": { - "bool": { - "must": { "match": { "message": "query" } }, - "must_not": { - "bool": { - "must": { "match": { "message": "not_query" } }, - "should": { - "bool": { "must": { "match": { "message": "or_query" } } } - } - } - } - } - } - } - - - compare_jsons(a, b) - end - - def test_query_block_1 - a = @builder - .query('bool') do |f| - f.query('term', 'field1', 1) - f.query('term', 'field2', 2) - f.or_query('term', 'field3', 3) - end - .query('bool') do |f| - f.query('term', 'field4', 10) - f.query('term', 'field5', 20) - f.or_query('term', 'field6', 30) - end - .build - - b = { - "query": { - "bool": { - "must": [ - { - "bool": { - "must": [{ "term": { "field1": 1 } }, { "term": { "field2": 2 } }], - "should": { "term": { "field3": 3 } } - } - }, - { - "bool": { - "must": [ - { "term": { "field4": 10 } }, - { "term": { "field5": 20 } } - ], - "should": { "term": { "field6": 30 } } - } - } - ] - } - } - } - compare_jsons(a,b) - end - - ################# - #WITH BASE QUERY# - ################# - - def test_base_query - @builder.base_query = { - query: { - bool: { - } - } - } - a = @builder - .query('match_all') - .build() - b = { "query": { "bool": { "must": { "match_all": {} } } } } - compare_jsons(a, b) - end - - def test_base_query_2 - @builder.base_query = { - query: { - bool: { - } - } - } - a = @builder - .query('bool') do |b| - b.or_query('multi_match', { 'query': 'test', 'fuzziness': 'AUTO' }) - b.or_query('constant_score', { 'filter': { 'terms': { 'id': ['1'] } }, 'boost': 100 }) - end - .build() - b = { - "query": { - "bool": { - "must": { - "bool": { - "should": [ - { "multi_match": { "query": "test", "fuzziness": "AUTO" } }, - { - "constant_score": { - "filter": { "terms": { "id": ["1"] } }, - "boost": 100 - } - } - ] - } - } - } - } - } - compare_jsons(a, b) - end - - ##################### - #COMBINED QUERY/FILTER # - ##################### - - def test_multi_0 - a = @builder - .query('match_all') - .filter('term', 'user', 'kimchy') - .build() - b = { - "query": { - "bool": { - "filter": { "term": { "user": "kimchy" } }, - "must": { "match_all": {} } - } - } - } - compare_jsons(a, b) - end - - def test_multi_1 - a = @builder - .filter('term', 'filter') - .or_filter('term', 'or_filter') - .not_filter('term', 'not_filter') - .query('term', 'query') - .or_query('term', 'or_query') - .not_query('term', 'not_query') - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": { "term": { "field": "filter" } }, - "should": { "term": { "field": "or_filter" } }, - "must_not": { "term": { "field": "not_filter" } } - } - }, - "must": { "term": { "field": "query" } }, - "should": { "term": { "field": "or_query" } }, - "must_not": { "term": { "field": "not_query" } } - } - } - } - compare_jsons(a, b) - end - - def test_multi_2 - a = @builder - .filter('term', 'filter') - .or_filter('term', 'or_filter') - .not_filter('term', 'not_filter') - .query('term', 'query') - .or_query('term', 'or_query') - .not_query('term', 'not_query') - .filter('term', 'filter_2') - .or_filter('term', 'or_filter_2') - .not_filter('term', 'not_filter_2') - .query('term', 'query_2') - .or_query('term', 'or_query_2') - .not_query('term', 'not_query_2') - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "must": [ - { "term": { "field": "filter" } }, - { "term": { "field": "filter_2" } } - ], - "should": [ - { "term": { "field": "or_filter" } }, - { "term": { "field": "or_filter_2" } } - ], - "must_not": [ - { "term": { "field": "not_filter" } }, - { "term": { "field": "not_filter_2" } } - ] - } - }, - "must": [ - { "term": { "field": "query" } }, - { "term": { "field": "query_2" } } - ], - "should": [ - { "term": { "field": "or_query" } }, - { "term": { "field": "or_query_2" } } - ], - "must_not": [ - { "term": { "field": "not_query" } }, - { "term": { "field": "not_query_2" } } - ] - } - } - } - compare_jsons(a, b) - end - - def test_multi_3 - a = @builder.query('nested', 'path', 'obj1', {score_mode: 'avg'}) do |q| - q.query('match', 'obj1.name', 'blue') - q.query('range', 'obj1.count', {gt: 5}) - end.build() - b = { - "query": { - "nested": { - "path": "obj1", - "score_mode": "avg", - "query": { - "bool": { - "must": [ - { "match": { "obj1.name": "blue" } }, - { "range": { "obj1.count": { "gt": 5 } } } - ] - } - } - } - } - } - compare_jsons(a, b) - end - - # def test_multi_4 - # a = @builder.query('a_key') do |q| - # q.query('nice', 'wow') - # end.build() - # b = { "query": { "a_key": { "query": { "nice": { "field": "wow" } } } } } - # compare_jsons(a, b) - # end - - def test_multi_5 - a = @builder - .or_filter('bool') do |f| - f.filter('terms', 'tags', ['Popular']) - f.filter('terms', 'brands', ['A', 'B']) - f.or_filter('bool') do |f| - f.filter('terms', 'tags', ['Emerging']) - f.filter('terms', 'brands', ['C']) - end - end - .or_filter('bool') do |f| - f.filter('terms', 'tags', ['Rumor']) - f.filter('terms', 'companies', ['A', 'C', 'D']) - end - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "should": [ - { - "bool": { - "must": [ - { "terms": { "tags": ["Popular"] } }, - { "terms": { "brands": ["A", "B"] } } - ], - "should": { - "bool": { - "filter": [ - { "terms": { "tags": ["Emerging"] } }, - { "terms": { "brands": ["C"] } } - ] - } - } - } - }, - { - "bool": { - "filter": [ - { "terms": { "tags": ["Rumor"] } }, - { "terms": { "companies": ["A", "C", "D"] } } - ] - } - } - ] - } - } - } - } - } - compare_jsons(a, b) - end - - def test_multi_6 - assert_raises(StandardError) { - a = @builder.filter('bool') do |f| - f.query('match', 'message', 'this is a test') - end.build() - } - end - - def test_multi_8 - a = @builder - .query('bool') do |q| - q.filter('term', 'message', 'asdf') - end - .build - b = { "query": { "bool": { "filter": { "term": { "message": "asdf" } } } } } - compare_jsons(a, b) - end - - def test_multi_9 - a = @builder - .query('bool') do |q| - q.or_filter('term', 'message', 'asdf') - end - .build - b = { - "query": { - "bool": { - "filter": { "bool": { "should": { "term": { "message": "asdf" } } } } - } - } - } - - compare_jsons(a, b) - end - - # minimumShouldMatch - - def test_mimimum_should_match_1 - a = @builder - .or_filter('term', 'state', 'one') - .or_filter('term', 'state', 'two') - .or_query('match', 'message', 'nice') - .or_query('match', 'message', 'something') - .set_query_minimum_should_match(2) - .set_filter_minimum_should_match(3) - .build() - b = { - "query": { - "bool": { - "filter": { - "bool": { - "should": [ - { "term": { "state": "one" } }, - { "term": { "state": "two" } } - ], - "minimum_should_match": 3 - } - }, - "should": [ - { "match": { "message": "nice" } }, - { "match": { "message": "something" } } - ], - "minimum_should_match": 2 - } - } - } - - compare_jsons(a, b) - end - - def test_mimimum_should_match_2 - a = @builder - .or_query('match', 'message', 'nice') - .set_query_minimum_should_match(2) - .build() - b = { "query": { "bool": { "should": { "match": { "message": "nice" } } } } } - compare_jsons(a, b) - end - - def test_mimimum_should_match_3 - a = @builder - .or_query('match', 'message', 'nice') - .set_query_minimum_should_match(1) - .build() - b = { "query": { "bool": { "should": { "match": { "message": "nice" } } } } } - compare_jsons(a, b) - end - - def test_mimimum_should_match_4 - a = @builder.query('bool') do |b| - b.or_query('match', 'message', 'test') - b.or_query('match', 'message', 'something else') - b.set_query_minimum_should_match(1) - end.build - b = { - "query": { - "bool": { - "should": [ - { "match": { "message": "test" } }, - { "match": { "message": "something else" } } - ], - "minimum_should_match": 1 - } - } - } - compare_jsons(a, b) - end - -end \ No newline at end of file diff --git a/test/spec/body_builder_test.rb b/test/spec/body_builder_test.rb new file mode 100644 index 0000000..e8c3d45 --- /dev/null +++ b/test/spec/body_builder_test.rb @@ -0,0 +1,988 @@ +# frozen_string_literal: true + +require 'test_helper' + +module BuilderTestMethods + def setup + @builder = BodyBuilder::Builder.new + end +end + +class HelperTest < Minitest::Test + + include BuilderTestMethods + + def compare_jsons(value, expected) + assert_equal value, expected + end + + def test_respond_to_methods + # query + assert_respond_to @builder, :query + assert_respond_to @builder, :and_query + assert_respond_to @builder, :or_query + assert_respond_to @builder, :not_query + + # filter + assert_respond_to @builder, :filter + assert_respond_to @builder, :and_filter + assert_respond_to @builder, :or_filter + assert_respond_to @builder, :not_filter + + # other + assert_respond_to @builder, :base_query + assert_respond_to @builder, :raw_option + assert_respond_to @builder, :sort_field + + assert_respond_to @builder, :queries? + assert_respond_to @builder, :filters? + + assert_respond_to @builder, :reset! + assert_respond_to @builder, :reset_queries! + assert_respond_to @builder, :reset_filters! + assert_respond_to @builder, :reset_raw_options! + assert_respond_to @builder, :reset_sort_fields! + end + + def test_size + a = @builder + .set_size(5) + .build + b = { size: 5 } + compare_jsons(a, b) + end + + def test_from + a = @builder + .set_from(50) + .build + b = { from: 50 } + compare_jsons(a, b) + end + + def test_raw_option + a = @builder + .raw_option(:size, 5) + .build + b = { size: 5 } + compare_jsons(a, b) + end + + def test_sort_field + a = @builder + .sort_field(:id, 'desc') + .build + b = { sort: [{id: 'desc'}] } + compare_jsons(a, b) + end + + def test_override_sort_field + a = @builder + .sort_field(:id, 'desc') + .sort_field(:id, 'asc') + .build + b = { sort: [{id: 'asc'}] } + compare_jsons(a, b) + end + + def test_queries? + assert_equal false, @builder.queries? + @builder.filter('match_all') + + assert_equal false, @builder.queries? + @builder.query('match_all') + + assert_equal true, @builder.queries? + end + + def test_filters? + assert_equal false, @builder.filters? + @builder.query('match_all') + + assert_equal false, @builder.filters? + @builder.filter('match_all') + + assert_equal true, @builder.filters? + end + + def test_reset_queries! + @builder.query('match_all') + @builder.reset_queries! + + assert_equal false, @builder.queries? + end + + def test_reset_filters! + @builder.filter('match_all') + @builder.reset_filters! + + assert_equal false, @builder.filters? + end + + def test_reset_raw_options! + @builder.raw_option('size', 10) + + assert_equal 1, @builder.raw_options.size + @builder.reset_raw_options! + + assert_empty @builder.raw_options + end + + def test_reset_sort_fields! + @builder.sort_field('id', 'asc') + + assert_equal 1, @builder.sort_fields.size + @builder.reset_sort_fields! + + assert_empty @builder.sort_fields + end + + # def test_reset! + # end + + ###### + # ARGS# + ###### + + def test_single_query_1_arg + a = @builder + .query('match_all') + .build + b = { query: { match_all: {} } } + compare_jsons(a, b) + end + + def test_single_query_2_args + a = @builder + .query('exists', 'user') + .build + b = { query: { exists: { field: 'user' } } } + compare_jsons(a, b) + end + + def test_single_query_3_args + a = @builder + .query('range', 'date', {gt: 'now-1d'}) + .build + b = { query: { range: { date: { gt: 'now-1d' } } } } + compare_jsons(a, b) + end + + def test_single_query_4_args + a = @builder + .query('geo_distance', 'point', {lat: 40, lon: 20}, {distance: '12km'}) + .build + b = { + query: { + geo_distance: { point: { lat: 40, lon: 20 }, distance: '12km' } + } + } + compare_jsons(a, b) + end + + ######### + # FILTERS# + ######### + + # SINGLES + + def test_single_filter_boolean + a = @builder + .filter('term', 'active', false) + .build + b = { query: { bool: { filter: { term: { active: false } } } } } + compare_jsons(a, b) + end + + def test_single_filter + a = @builder + .filter('terms', 'tags', ['Emerging']) + .build + b = { query: { bool: { filter: { terms: { tags: ['Emerging'] } } } } } + compare_jsons(a, b) + end + + def test_single_and_filter + a = @builder + .and_filter('terms', 'tags', ['Emerging']) + .build + b = { query: { bool: { filter: { terms: { tags: ['Emerging'] } } } } } + compare_jsons(a, b) + end + + def test_single_or_filter + a = @builder + .or_filter('terms', 'tags', ['Emerging']) + .build + b = { + query: { + bool: { + filter: { bool: { should: { terms: { tags: ['Emerging'] } } } } + } + } + } + compare_jsons(a, b) + end + + def test_single_not_filter + a = @builder + .not_filter('terms', 'tags', ['Emerging']) + .build + b = { query: { bool: { must_not: { terms: { tags: ['Emerging'] } } } } } + compare_jsons(a, b) + end + + # COMBINED + + def test_all_filters + a = @builder + .filter('terms', 'tags', 'filter') + .or_filter('terms', 'tags', 'or_filter') + .not_filter('terms', 'tags', 'not_filter') + .build + b = { + query: { + bool: { + filter: { + bool: { + must: { terms: { tags: 'filter' } }, + should: { terms: { tags: 'or_filter' } }, + must_not: { terms: { tags: 'not_filter' } } + } + } + } + } + } + compare_jsons(a, b) + end + + def test_repeated_filters + a = @builder + .filter('terms', 'tags', 'filter') + .filter('terms', 'tags', 'filter') + .or_filter('terms', 'tags', 'or_filter') + .or_filter('terms', 'tags', 'or_filter') + .not_filter('terms', 'tags', 'not_filter') + .not_filter('terms', 'tags', 'not_filter') + .build + b = { + query: { + bool: { + filter: { + bool: { + must: [ + { terms: { tags: 'filter' } }, + { terms: { tags: 'filter' } } + ], + should: [ + { terms: { tags: 'or_filter' } }, + { terms: { tags: 'or_filter' } } + ], + must_not: [ + { terms: { tags: 'not_filter' } }, + { terms: { tags: 'not_filter' } } + ] + } + } + } + } + } + compare_jsons(a, b) + end + + # Block + + def test_all_filter_blocks + a = @builder + .filter('bool') do |f| + f.filter('match', 'message', 'filter') + end + .not_filter('bool') do |f| + f.filter('match', 'message', 'not_filter') + end + .or_filter('bool') do |f| + f.filter('match', 'message', 'or_filter') + end + .build + b = { + query: { + bool: { + filter: { + bool: { + must: { + bool: { filter: { match: { message: 'filter' } } } + }, + should: { + bool: { filter: { match: { message: 'or_filter' } } } + }, + must_not: { + bool: { filter: { match: { message: 'not_filter' } } } + } + } + } + } + } + } + + compare_jsons(a, b) + end + + def test_all_filter_blocks_double + a = @builder + .filter('bool') do |f| + f.filter('match', 'message', 'filter') + f.filter('match', 'message', 'filter') + end + .not_filter('bool') do |f| + f.filter('match', 'message', 'not_filter') + f.filter('match', 'message', 'not_filter') + end + .or_filter('bool') do |f| + f.filter('match', 'message', 'or_filter') + f.filter('match', 'message', 'or_filter') + end + .build + b = { + query: { + bool: { + filter: { + bool: { + must: { + bool: { filter: [{ match: { message: 'filter' } }, { match: { message: 'filter' } }] } + }, + should: { + bool: { filter: [{ match: { message: 'or_filter' } }, { match: { message: 'or_filter' } }] } + }, + must_not: { + bool: { filter: [{ match: { message: 'not_filter' } }, { match: { message: 'not_filter' } }] } + } + } + } + } + } + } + + compare_jsons(a, b) + end + + def test_all_filter_blocks_nested + a = @builder + .filter('bool') do |f| + f.filter('match', 'message', 'filter') + f.not_filter('bool') do |f| + f.filter('match', 'message', 'not_filter') + f.or_filter('bool') do |f| + f.filter('match', 'message', 'or_filter') + end + end + end + .build + b = { + query: { + bool: { + filter: { + bool: { + must: { match: { message: 'filter' } }, + must_not: { + bool: { + must: { match: { message: 'not_filter' } }, + should: { + bool: { filter: { match: { message: 'or_filter' } } } + } + } + } + } + } + } + } + } + + compare_jsons(a, b) + end + + ####### + # QUERY# + ####### + + # SINGLES + + def test_single_query + a = @builder + .query('match', 'tags', 'Emerging') + .build + b = { query: { match: { tags: 'Emerging' } } } + compare_jsons(a, b) + end + + def test_single_and_query + a = @builder + .query('match', 'tags', 'Emerging') + .build + b = { query: { match: { tags: 'Emerging' } } } + compare_jsons(a, b) + end + + def test_single_or_query + a = @builder + .or_query('terms', 'tags', ['Emerging']) + .build + b = { query: { bool: { should: { terms: { tags: ['Emerging'] } } } } } + compare_jsons(a, b) + end + + def test_single_not_query + a = @builder + .not_query('terms', 'tags', ['Emerging']) + .build + b = { query: { bool: { must_not: { terms: { tags: ['Emerging'] } } } } } + compare_jsons(a, b) + end + + # COMBINED + + def test_all_querys + a = @builder + .query('terms', 'tags', 'query') + .or_query('terms', 'tags', 'or_query') + .not_query('terms', 'tags', 'not_query') + .build + b = { + query: { + bool: { + must: { terms: { tags: 'query' } }, + should: { terms: { tags: 'or_query' } }, + must_not: { terms: { tags: 'not_query' } } + } + } + } + compare_jsons(a, b) + end + + def test_repeated_queries + a = @builder + .query('terms', 'tags', 'query') + .query('terms', 'tags', 'query') + .or_query('terms', 'tags', 'or_query') + .or_query('terms', 'tags', 'or_query') + .not_query('terms', 'tags', 'not_query') + .not_query('terms', 'tags', 'not_query') + .build + b = { + query: { + bool: { + must: [ + { terms: { tags: 'query' } }, + { terms: { tags: 'query' } } + ], + should: [ + { terms: { tags: 'or_query' } }, + { terms: { tags: 'or_query' } } + ], + must_not: [ + { terms: { tags: 'not_query' } }, + { terms: { tags: 'not_query' } } + ] + } + } + } + compare_jsons(a, b) + end + + ####### + # BLOCK# + ####### + + def test_all_query_blocks + a = @builder + .query('bool') do |f| + f.query('match', 'message', 'query') + end + .not_query('bool') do |f| + f.query('match', 'message', 'not_query') + end + .or_query('bool') do |f| + f.query('match', 'message', 'or_query') + end + .build + b = { + query: { + bool: { + must: { bool: { must: { match: { message: 'query' } } } }, + should: { bool: { must: { match: { message: 'or_query' } } } }, + must_not: { + bool: { must: { match: { message: 'not_query' } } } + } + } + } + } + compare_jsons(a, b) + end + + def test_all_query_blocks_double + a = @builder + .query('bool') do |f| + f.query('match', 'message', 'query') + f.query('match', 'message', 'query') + end + .not_query('bool') do |f| + f.query('match', 'message', 'not_query') + f.query('match', 'message', 'not_query') + end + .or_query('bool') do |f| + f.query('match', 'message', 'or_query') + f.query('match', 'message', 'or_query') + end + .build + b = { + query: { + bool: { + must: { + bool: { + must: [ + { match: { message: 'query' } }, + { match: { message: 'query' } } + ] + } + }, + should: { + bool: { + must: [ + { match: { message: 'or_query' } }, + { match: { message: 'or_query' } } + ] + } + }, + must_not: { + bool: { + must: [ + { match: { message: 'not_query' } }, + { match: { message: 'not_query' } } + ] + } + } + } + } + } + + compare_jsons(a, b) + end + + def test_all_query_blocks_nested + a = @builder + .query('bool') do |f| + f.query('match', 'message', 'query') + f.not_query('bool') do |f| + f.query('match', 'message', 'not_query') + f.or_query('bool') do |f| + f.query('match', 'message', 'or_query') + end + end + end + .build + b = { + query: { + bool: { + must: { match: { message: 'query' } }, + must_not: { + bool: { + must: { match: { message: 'not_query' } }, + should: { + bool: { must: { match: { message: 'or_query' } } } + } + } + } + } + } + } + + compare_jsons(a, b) + end + + def test_query_block_1 + a = @builder + .query('bool') do |f| + f.query('term', 'field1', 1) + f.query('term', 'field2', 2) + f.or_query('term', 'field3', 3) + end + .query('bool') do |f| + f.query('term', 'field4', 10) + f.query('term', 'field5', 20) + f.or_query('term', 'field6', 30) + end + .build + + b = { + query: { + bool: { + must: [ + { + bool: { + must: [{ term: { field1: 1 } }, { term: { field2: 2 } }], + should: { term: { field3: 3 } } + } + }, + { + bool: { + must: [ + { term: { field4: 10 } }, + { term: { field5: 20 } } + ], + should: { term: { field6: 30 } } + } + } + ] + } + } + } + compare_jsons(a, b) + end + + ################# + # WITH BASE QUERY# + ################# + + def test_base_query + @builder.base_query = { + query: { + bool: { + } + } + } + a = @builder + .query('match_all') + .build + b = { query: { bool: { must: { match_all: {} } } } } + compare_jsons(a, b) + end + + def test_base_query_2 + @builder.base_query = { + query: { + bool: { + } + } + } + a = @builder + .query('bool') do |b| + b.or_query('multi_match', { query: 'test', fuzziness: 'AUTO' }) + b.or_query('constant_score', { filter: { terms: { id: ['1'] } }, boost: 100 }) + end + .build + b = { + query: { + bool: { + must: { + bool: { + should: [ + { multi_match: { query: 'test', fuzziness: 'AUTO' } }, + { + constant_score: { + filter: { terms: { id: ['1'] } }, + boost: 100 + } + } + ] + } + } + } + } + } + compare_jsons(a, b) + end + + ##################### + # COMBINED QUERY/FILTER # + ##################### + + def test_multi_0 + a = @builder + .query('match_all') + .filter('term', 'user', 'kimchy') + .build + b = { + query: { + bool: { + filter: { term: { user: 'kimchy' } }, + must: { match_all: {} } + } + } + } + compare_jsons(a, b) + end + + def test_multi_1 + a = @builder + .filter('term', 'filter') + .or_filter('term', 'or_filter') + .not_filter('term', 'not_filter') + .query('term', 'query') + .or_query('term', 'or_query') + .not_query('term', 'not_query') + .build + b = { + query: { + bool: { + filter: { + bool: { + must: { term: { field: 'filter' } }, + should: { term: { field: 'or_filter' } }, + must_not: { term: { field: 'not_filter' } } + } + }, + must: { term: { field: 'query' } }, + should: { term: { field: 'or_query' } }, + must_not: { term: { field: 'not_query' } } + } + } + } + compare_jsons(a, b) + end + + def test_multi_2 + a = @builder + .filter('term', 'filter') + .or_filter('term', 'or_filter') + .not_filter('term', 'not_filter') + .query('term', 'query') + .or_query('term', 'or_query') + .not_query('term', 'not_query') + .filter('term', 'filter_2') + .or_filter('term', 'or_filter_2') + .not_filter('term', 'not_filter_2') + .query('term', 'query_2') + .or_query('term', 'or_query_2') + .not_query('term', 'not_query_2') + .build + b = { + query: { + bool: { + filter: { + bool: { + must: [ + { term: { field: 'filter' } }, + { term: { field: 'filter_2' } } + ], + should: [ + { term: { field: 'or_filter' } }, + { term: { field: 'or_filter_2' } } + ], + must_not: [ + { term: { field: 'not_filter' } }, + { term: { field: 'not_filter_2' } } + ] + } + }, + must: [ + { term: { field: 'query' } }, + { term: { field: 'query_2' } } + ], + should: [ + { term: { field: 'or_query' } }, + { term: { field: 'or_query_2' } } + ], + must_not: [ + { term: { field: 'not_query' } }, + { term: { field: 'not_query_2' } } + ] + } + } + } + compare_jsons(a, b) + end + + def test_multi_3 + a = @builder.query('nested', 'path', 'obj1', {score_mode: 'avg'}) do |q| + q.query('match', 'obj1.name', 'blue') + q.query('range', 'obj1.count', {gt: 5}) + end.build + b = { + query: { + nested: { + path: 'obj1', + score_mode: 'avg', + query: { + bool: { + must: [ + { match: { 'obj1.name': 'blue' } }, + { range: { 'obj1.count': { gt: 5 } } } + ] + } + } + } + } + } + compare_jsons(a, b) + end + + # def test_multi_4 + # a = @builder.query('a_key') do |q| + # q.query('nice', 'wow') + # end.build() + # b = { "query": { "a_key": { "query": { "nice": { "field": "wow" } } } } } + # compare_jsons(a, b) + # end + + def test_multi_5 + a = @builder + .or_filter('bool') do |f| + f.filter('terms', 'tags', ['Popular']) + f.filter('terms', 'brands', %w[A B]) + f.or_filter('bool') do |f| + f.filter('terms', 'tags', ['Emerging']) + f.filter('terms', 'brands', ['C']) + end + end + .or_filter('bool') do |f| + f.filter('terms', 'tags', ['Rumor']) + f.filter('terms', 'companies', %w[A C D]) + end + .build + b = { + query: { + bool: { + filter: { + bool: { + should: [ + { + bool: { + must: [ + { terms: { tags: ['Popular'] } }, + { terms: { brands: %w[A B] } } + ], + should: { + bool: { + filter: [ + { terms: { tags: ['Emerging'] } }, + { terms: { brands: ['C'] } } + ] + } + } + } + }, + { + bool: { + filter: [ + { terms: { tags: ['Rumor'] } }, + { terms: { companies: %w[A C D] } } + ] + } + } + ] + } + } + } + } + } + compare_jsons(a, b) + end + + def test_multi_6 + assert_raises(StandardError) { + @builder.filter('bool') do |f| + f.query('match', 'message', 'this is a test') + end.build + } + end + + def test_multi_8 + a = @builder + .query('bool') do |q| + q.filter('term', 'message', 'asdf') + end + .build + b = { query: { bool: { filter: { term: { message: 'asdf' } } } } } + compare_jsons(a, b) + end + + def test_multi_9 + a = @builder + .query('bool') do |q| + q.or_filter('term', 'message', 'asdf') + end + .build + b = { + query: { + bool: { + filter: { bool: { should: { term: { message: 'asdf' } } } } + } + } + } + + compare_jsons(a, b) + end + + # minimumShouldMatch + + def test_mimimum_should_match_1 + a = @builder + .or_filter('term', 'state', 'one') + .or_filter('term', 'state', 'two') + .or_query('match', 'message', 'nice') + .or_query('match', 'message', 'something') + .set_query_minimum_should_match(2) + .set_filter_minimum_should_match(3) + .build + b = { + query: { + bool: { + filter: { + bool: { + should: [ + { term: { state: 'one' } }, + { term: { state: 'two' } } + ], + minimum_should_match: 3 + } + }, + should: [ + { match: { message: 'nice' } }, + { match: { message: 'something' } } + ], + minimum_should_match: 2 + } + } + } + + compare_jsons(a, b) + end + + def test_mimimum_should_match_2 + a = @builder + .or_query('match', 'message', 'nice') + .set_query_minimum_should_match(2) + .build + b = { query: { bool: { should: { match: { message: 'nice' } } } } } + compare_jsons(a, b) + end + + def test_mimimum_should_match_3 + a = @builder + .or_query('match', 'message', 'nice') + .set_query_minimum_should_match(1) + .build + b = { query: { bool: { should: { match: { message: 'nice' } } } } } + compare_jsons(a, b) + end + + def test_mimimum_should_match_4 + a = @builder.query('bool') do |b| + b.or_query('match', 'message', 'test') + b.or_query('match', 'message', 'something else') + b.set_query_minimum_should_match(1) + end.build + b = { + query: { + bool: { + should: [ + { match: { message: 'test' } }, + { match: { message: 'something else' } } + ], + minimum_should_match: 1 + } + } + } + compare_jsons(a, b) + end + +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index 15cc712..7adfd07 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,12 @@ -require "minitest/autorun" -require "minitest/spec" -require "minitest/reporters" -require "body_builder" +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path('../lib', __dir__) +require 'body_builder' +require 'debug' +require 'json' + +require 'minitest/autorun' +require 'minitest/spec' +require 'minitest/reporters' Minitest::Reporters.use! \ No newline at end of file