searchkick_test.rb 6.02 KB
require "test_helper"

class TestSearchkick < Minitest::Unit::TestCase

  def setup
    $index = Tire::Index.new("products")
    $index.delete
    index_options = {
      settings: {
        number_of_shards: 1,
        analysis: {
          analyzer: {
            searchkick_keyword: {
              type: "custom",
              tokenizer: "keyword",
              filter: ["lowercase", "snowball"]
            },
            searchkick: {
              type: "custom",
              tokenizer: "standard",
              # synonym should come last, after stemming and shingle
              # shingle must come before snowball
              filter: ["standard", "lowercase", "asciifolding", "stop", "searchkick_shingle", "snowball", "searchkick_synonym"]
            }
          },
          filter: {
            searchkick_shingle: {
              type: "shingle",
              token_separator: ""
            },
            searchkick_synonym: {
              type: "synonym",
              ignore_case: true,
              synonyms: [
                "clorox => bleach",
                "saran wrap => plastic wrap",
                "scallion => green onion",
                "qtip => cotton swab"
              ]
            }
          }
        }
      },
      mappings: {
        document: {
          properties: {
            name: {
              type: "string",
              analyzer: "searchkick"
            },
            conversions: {
              type: "nested",
              properties: {
                query: {
                  type: "string",
                  analyzer: "searchkick_keyword"
                },
                count: {
                  type: "integer"
                }
              }
            }
          }
        }
      }
    }
    $index.create index_options
  end

  # exact

  def test_match
    store_names ["Whole Milk", "Fat Free Milk", "Milk"]
    assert_search "milk", ["Milk", "Whole Milk", "Fat Free Milk"]
  end

  def test_case
    store_names ["Whole Milk", "Fat Free Milk", "Milk"]
    assert_search "MILK", ["Milk", "Whole Milk", "Fat Free Milk"]
  end

  # ascii

  def test_jalapenos
    store_names ["Jalapeño"]
    assert_search "jalapeno", ["Jalapeño"]
  end

  # stemming

  def test_stemming
    store_names ["Whole Milk", "Fat Free Milk", "Milk"]
    assert_search "milks", ["Milk", "Whole Milk", "Fat Free Milk"]
  end

  # fuzzy

  def test_fuzzy
    store_names ["Sliced Beets", "Beef"]
    assert_search "beets", ["Sliced Beets", "Beef"]
  end

  # conversions

  def test_conversions
    store [
      {name: "Tomato Sauce", conversions: [{query: "tomato sauce", count: 100}, {query: "tomato", count: 2}]},
      {name: "Tomato Paste", conversions: []},
      {name: "Tomatoes", conversions: [{query: "tomato", count: 100}, {query: "tomato sauce", count: 2}]}
    ]
    assert_search "tomato sauce", ["Tomato Sauce", "Tomatoes"] #, "Tomato Paste"]
    assert_search "tomato", ["Tomatoes", "Tomato Sauce", "Tomato Paste"]
    assert_search "tomato paste", ["Tomato Paste"] #, "Tomatoes", "Tomato Sauce"]
  end

  def test_conversions_stemmed
    store [
      {name: "Tomato A", conversions: [{query: "tomato", count: 1}, {query: "tomatos", count: 1}, {query: "Tomatoes", count: 3}]},
      {name: "Tomato B", conversions: [{query: "tomato", count: 4}]}
    ]
    assert_search "tomato", ["Tomato A", "Tomato B"]
  end

  # spaces

  def test_spaces_in_field
    store_names ["Red Bull"]
    assert_search "redbull", ["Red Bull"]
  end

  def test_spaces_in_query
    store_names ["Dishwasher Soap"]
    assert_search "dish washer", ["Dishwasher Soap"]
  end

  def test_spaces_three_words
    store_names ["Dish Washer Soap", "Dish Washer"]
    assert_search "dish washer soap", ["Dish Washer Soap"]
  end

  def test_spaces_stemming
    store_names ["Almond Milk"]
    assert_search "almondmilks", ["Almond Milk"]
  end

  # keywords

  def test_keywords
    store_names ["Clorox Bleach", "Kroger Bleach", "Saran Wrap", "Kroger Plastic Wrap"]
    assert_search "clorox", ["Clorox Bleach", "Kroger Bleach"]
    assert_search "saran wrap", ["Saran Wrap", "Kroger Plastic Wrap"]
  end

  def test_keywords_qtips
    store_names ["Q Tips", "Kroger Cotton Swabs"]
    assert_search "q tips", ["Q Tips", "Kroger Cotton Swabs"]
  end

  def test_keywords_exact
    store_names ["Green Onions", "Yellow Onions"]
    assert_search "scallion", ["Green Onions"]
  end

  def test_keywords_stemmed
    store_names ["Green Onions", "Yellow Onions"]
    assert_search "scallions", ["Green Onions"]
  end

  # global boost

  def test_boost
    store [
      {name: "Organic Tomato A", _boost: 10},
      {name: "Tomato B"}
    ]
    assert_search "tomato", ["Organic Tomato A", "Tomato B"]
  end

  def test_boost_zero
    store [
      {name: "Zero Boost", _boost: 0}
    ]
    assert_search "zero", ["Zero Boost"]
  end

  # default to 1
  def test_boost_null
    store [
      {name: "Zero Boost A", _boost: 1.1},
      {name: "Zero Boost B"},
      {name: "Zero Boost C", _boost: 0.9},
    ]
    assert_search "zero", ["Zero Boost A", "Zero Boost B", "Zero Boost C"]
  end

  protected

  def store(documents)
    documents.each do |document|
      $index.store document
    end
    $index.refresh
  end

  def store_names(names)
    store names.map{|name| {name: name} }
  end

  def assert_search(term, expected)
    fields = ["name"]
    search =
      Tire.search "products", type: "document" do
        query do
          boolean do
            should do
              match fields, term, boost: 10
            end
            should do
              match fields, term, use_dis_max: false, fuzziness: 0.6, max_expansions: 4, prefix_length: 2
            end
            should do
              nested path: "conversions", score_mode: "total" do
                query do
                  custom_score script: "log(doc['count'].value)" do
                    match "query", term
                  end
                end
              end
            end
          end
        end
        explain true
      end

    assert_equal expected, search.results.map(&:name)
  end

end