sql_test.rb 9.8 KB
require_relative "test_helper"

class SqlTest < Minitest::Test
  def test_operator
    store_names ["Honey"]
    assert_search "fresh honey", []
    assert_search "fresh honey", ["Honey"], operator: "or"
    assert_search_relation ["Honey"], Product.search("fresh honey").operator(:or)
  end

  def test_operator_scoring
    store_names ["Big Red Circle", "Big Green Circle", "Small Orange Circle"]
    expected = ["Big Red Circle", "Big Green Circle", "Small Orange Circle"]
    assert_order "big red circle", expected, operator: "or"
    assert_search_relation expected, Product.search("big red circle").operator(:or)
  end

  def test_fields_operator
    store [
      {name: "red", color: "red"},
      {name: "blue", color: "blue"},
      {name: "cyan", color: "blue green"},
      {name: "magenta", color: "red blue"},
      {name: "green", color: "green"}
    ]
    expected = ["red", "blue", "cyan", "magenta"]
    assert_search "red blue", expected, operator: "or", fields: ["color"]
    assert_search_relation expected, Product.search("red blue").operator(:or).fields(:color)
  end

  def test_fields
    store [
      {name: "red", color: "light blue"},
      {name: "blue", color: "red fish"}
    ]
    assert_search "blue", ["red"], fields: ["color"]
    assert_search_relation ["red"], Product.search("blue").fields(:color)
    assert_search_relation ["red", "blue"], Product.search("blue").fields(:name).fields(:color)
  end

  def test_non_existent_field
    store_names ["Milk"]
    assert_search "milk", [], fields: ["not_here"]
  end

  def test_fields_both_match
    # have same score due to dismax
    store [
      {name: "Blue A", color: "red"},
      {name: "Blue B", color: "light blue"}
    ]
    assert_first "blue", "Blue B", fields: [:name, :color]
  end

  def test_big_decimal
    store [
      {name: "Product", latitude: 80.0}
    ]
    assert_search "product", ["Product"], where: {latitude: {gt: 79}}
    assert_search_relation ["Product"], Product.search("product").where(latitude: {gt: 79})
  end

  # body_options

  def test_body_options_should_merge_into_body
    query = Product.search("*", body_options: {min_score: 1.0}, execute: false)
    assert_equal 1.0, query.body[:min_score]
  end

  # load

  def test_load_default
    store_names ["Product A"]
    assert_kind_of Product, Product.search("product").first
  end

  def test_load_false
    store_names ["Product A"]
    assert_kind_of Hash, Product.search("product", load: false).first
    assert_kind_of Hash, Product.search("product").load(false).first
  end

  def test_load_false_methods
    store_names ["Product A"]
    assert_equal "Product A", Product.search("product", load: false).first.name
    assert_equal "Product A", Product.search("product").load(false).first.name
  end

  def test_load_false_with_includes
    store_names ["Product A"]
    assert_kind_of Hash, Product.search("product", load: false, includes: [:store]).first
    assert_kind_of Hash, Product.search("product").load(false).includes(:store).first
  end

  def test_load_false_nested_object
    aisle = {"id" => 1, "name" => "Frozen"}
    store [{name: "Product A", aisle: aisle}]
    assert_equal aisle, Product.search("product", load: false).first.aisle.to_hash
    assert_equal aisle, Product.search("product").load(false).first.aisle.to_hash
  end

  # select

  def test_select
    store [{name: "Product A", store_id: 1}]
    result = Product.search("product", load: false, select: [:name, :store_id]).first
    assert_equal %w(id name store_id), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_equal "Product A", result.name
    assert_equal 1, result.store_id
  end

  def test_select_array
    store [{name: "Product A", user_ids: [1, 2]}]
    result = Product.search("product", load: false, select: [:user_ids]).first
    assert_equal [1, 2], result.user_ids
  end

  def test_select_single_field
    store [{name: "Product A", store_id: 1}]
    result = Product.search("product", load: false, select: :name).first
    assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_equal "Product A", result.name
    assert_nil result.store_id
  end

  def test_select_all
    store [{name: "Product A", user_ids: [1, 2]}]
    hit = Product.search("product", select: true).hits.first
    assert_equal hit["_source"]["name"], "Product A"
    assert_equal hit["_source"]["user_ids"], [1, 2]
  end

  def test_select_none
    store [{name: "Product A", user_ids: [1, 2]}]
    hit = Product.search("product", select: []).hits.first
    assert_nil hit["_source"]
    hit = Product.search("product", select: false).hits.first
    assert_nil hit["_source"]
  end

  def test_select_includes
    store [{name: "Product A", user_ids: [1, 2]}]
    result = Product.search("product", load: false, select: {includes: [:name]}).first
    assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_equal "Product A", result.name
    assert_nil result.store_id
  end

  def test_select_excludes
    store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
    result = Product.search("product", load: false, select: {excludes: [:name]}).first
    assert_nil result.name
    assert_equal [1, 2], result.user_ids
    assert_equal 1, result.store_id
  end

  def test_select_include_and_excludes
    # let's take this to the next level
    store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
    result = Product.search("product", load: false, select: {includes: [:store_id], excludes: [:name]}).first
    assert_equal 1, result.store_id
    assert_nil result.name
    assert_nil result.user_ids
  end

  # select relation

  def test_select_relation
    store [{name: "Product A", store_id: 1}]
    result = Product.search("product").load(false).select(:name, :store_id).first
    assert_equal %w(id name store_id), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_equal "Product A", result.name
    assert_equal 1, result.store_id
  end

  def test_select_multiple_relation
    store [{name: "Product A", store_id: 1}]
    # only last select applies - different from Active Record
    # since we have to allow for boolean, array, and hash values
    result = Product.search("product").load(false).select(:name).select(:store_id).first
    assert_equal %w(id store_id), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_nil result.name
    assert_equal 1, result.store_id
  end

  def test_select_array_relation
    store [{name: "Product A", user_ids: [1, 2]}]
    result = Product.search("product").load(false).select(:user_ids).first
    assert_equal [1, 2], result.user_ids
  end

  def test_select_single_field_relation
    store [{name: "Product A", store_id: 1}]
    result = Product.search("product").load(false).select(:name).first
    assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_equal "Product A", result.name
    assert_nil result.store_id
  end

  def test_select_all_relation
    store [{name: "Product A", user_ids: [1, 2]}]
    hit = Product.search("product").select(true).hits.first
    assert_equal hit["_source"]["name"], "Product A"
    assert_equal hit["_source"]["user_ids"], [1, 2]
  end

  def test_select_none_relation
    store [{name: "Product A", user_ids: [1, 2]}]
    hit = Product.search("product").select(false).hits.first
    assert_nil hit["_source"]
  end

  def test_select_includes_relation
    store [{name: "Product A", user_ids: [1, 2]}]
    result = Product.search("product").load(false).select(includes: [:name]).first
    assert_equal %w(id name), result.keys.reject { |k| k.start_with?("_") }.sort
    assert_equal "Product A", result.name
    assert_nil result.store_id
  end

  def test_select_excludes_relation
    store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
    result = Product.search("product").load(false).select(excludes: [:name]).first
    assert_nil result.name
    assert_equal [1, 2], result.user_ids
    assert_equal 1, result.store_id
  end

  def test_select_include_and_excludes_relation
    # let's take this to the next level
    store [{name: "Product A", user_ids: [1, 2], store_id: 1}]
    result = Product.search("product").load(false).select(includes: [:store_id], excludes: [:name]).first
    assert_equal 1, result.store_id
    assert_nil result.name
    assert_nil result.user_ids
  end

  # nested

  def test_nested_search
    store [{name: "Product A", aisle: {"id" => 1, "name" => "Frozen"}}], Speaker
    assert_search "frozen", ["Product A"], {fields: ["aisle.name"]}, Speaker
    assert_equal ["Product A"], Speaker.search("frozen").fields("aisle.name").map(&:name)
  end

  # other tests

  def test_includes
    skip unless defined?(ActiveRecord)

    store_names ["Product A"]
    assert Product.search("product", includes: [:store]).first.association(:store).loaded?
    assert Product.search("product").includes(:store).first.association(:store).loaded?
  end

  def test_model_includes
    skip unless defined?(ActiveRecord)

    store_names ["Product A"]
    store_names ["Store A"], Store

    associations = {Product => [:store], Store => [:products]}

    result = Searchkick.search("*", models: [Product, Store], model_includes: associations)
    assert_equal 2, result.length
    result.group_by(&:class).each_pair do |klass, records|
      assert records.first.association(associations[klass].first).loaded?
    end

    result = Searchkick.search("*").models(Product, Store).model_includes(associations)
    assert_equal 2, result.length
    result.group_by(&:class).each_pair do |klass, records|
      assert records.first.association(associations[klass].first).loaded?
    end
  end

  def test_scope_results
    skip unless defined?(ActiveRecord)

    store_names ["Product A", "Product B"]
    assert_search "product", ["Product A"], scope_results: ->(r) { r.where(name: "Product A") }
    assert_equal ["Product A"], Product.search("product").load(->(r) { r.where(name: "Product A") }).map(&:name)
  end
end