require "helper" class Item < Struct.new(:id) def self.[](id) new(id) end end class Person < Struct.new(:id) def self.[](id) new(id) end end class LunarSearchTest < Test::Unit::TestCase setup do Lunar.redis(Redis.new(:host => "127.0.0.1", :port => 6380)) Lunar.redis.flushdb end context "searching when there exists no index yet" do should "return an empty set" do items = Lunar.search(Item, "foobar") assert_equal 0, items.size end end context "when searching a non existent term, with an index existing" do setup do Lunar::Index.create "Item" do |i| i.key 1001 i.attr :name, 'iphone 3GS' end end should "still return an empty set" do items = Lunar.search(Item, "foobar") assert_equal 0, items.size end end # This is how the scheme appears # Item:name:iphone => 1004 1001 # Item:name:3gs => 1001 # Item:name:apple => 1004 1003 1002 # Item:name:macbook => 1003 1002 # Item:name:pro => 1003 1002 # Item:name:17 => 1003 1002 # Item:desc:iphone => 1004 1003 1002 # Item:desc:4g => 1004 1003 # context "given the schema appearing above" do setup do Lunar::Index.create "Item" do |i| i.key 1001 i.attr :name, 'iphone 3GS' end Lunar::Index.create "Item" do |i| i.key 1002 i.attr :name, 'apple macbook pro 17"' i.attr :desc, 'iphone' end Lunar::Index.create "Item" do |i| i.key 1003 i.attr :name, 'apple macbook pro 17"' i.attr :desc, 'iphone 4G' i.integer :cost, '2300' i.float :voting_quotient, '35.5' end Lunar::Index.create "Item" do |i| i.key 1004 i.attr :name, 'iphone apple' i.attr :desc, 'iphone 4G' end end should "return all 4 when searching iphone" do items = Lunar.search(Item, "iphone") assert_equal %w(1001 1002 1003 1004), items.map(&:id).sort end should "return only 1001 when searching 3gs" do items = Lunar.search(Item, '3gs') assert_equal %w(1001), items.map(&:id) end should "return only 1002 1003 1004 when searching apple" do items = Lunar.search(Item, 'APPle') assert_equal %w(1002 1003 1004), items.map(&:id).sort end should "return only 1002 1003 when searching macbook, pro, or 17" do macbook = Lunar.search(Item, 'macbook') pro = Lunar.search(Item, 'pro') _17 = Lunar.search(Item, '17') all = Lunar.search(Item, 'macbook pro 17') assert_equal %w(1002 1003), macbook.map(&:id).sort assert_equal %w(1002 1003), pro.map(&:id).sort assert_equal %w(1002 1003), _17.map(&:id).sort assert_equal %w(1002 1003), all.map(&:id).sort end should "return only 1003 1004 when searching 4g" do items = Lunar.search(Item, '4g') assert_equal %w(1003 1004), items.map(&:id).sort end should "return 1001 1004 given name: iphone" do items = Lunar.search(Item, :name => 'iphone') assert_equal %w(1001 1004), items.map(&:id).sort end should "return 1004 given name: iphone, desc: iphone" do items = Lunar.search(Item, :name => 'iphone', :desc => 'iphone') assert_equal %w(1004), items.map(&:id).sort end should "return 1004 given name: iphone, desc: 4g" do items = Lunar.search(Item, :name => 'iphone', :desc => '4g') assert_equal %w(1004), items.map(&:id).sort end should "return 1002 1003 1004 given name: apple, desc: iphone" do items = Lunar.search(Item, :name => 'apple', :desc => 'iphone') assert_equal %w(1002 1003 1004), items.map(&:id).sort end should "return 1002 1003 given name: apple macbook pro, desc: iphone" do items = Lunar.search(Item, :name => 'apple macbook pro', :desc => 'iphone') assert_equal %w(1002 1003), items.map(&:id).sort end should "return 1003 given name: apple macbook pro 17, desc: 4g" do items = Lunar.search(Item, :name => 'apple macbook pro 17', :desc => '4g') assert_equal %w(1003), items.map(&:id).sort end should "be able to search by cost range: 2000..2700" do items = Lunar.search(Item, :cost => 2000..2700) assert items.map(&:id).include?('1003') end should "be able to exclude by cost range: 2000..2200" do items = Lunar.search(Item, :cost => 2000..2200) assert ! items.map(&:id).include?('1003') end should "be able to search by range of cost and name" do items = Lunar.search(Item, :cost => 2000..2300, :name => 'apple') assert items.map(&:id).include?('1003') end should "be able to search by range of cost and exclude by name" do items = Lunar.search(Item, :cost => 2000..2200, :name => 'foobar') assert ! items.map(&:id).include?('1003') end should "be able to search by voting_quotient" do items = Lunar.search(Item, :voting_quotient => 35..40) assert items.map(&:id).include?('1003') end end context "given Martin Fowler, Chad Fowler, and Frank Macallen" do setup do Lunar::Index.create "Person" do |i| i.key 1001 i.fuzzy :name, 'Martin Fowler' end Lunar::Index.create "Person" do |i| i.key 1002 i.fuzzy :name, 'Chad Fowler' end Lunar::Index.create "Person" do |i| i.key 1003 i.fuzzy :name, 'Frank Macallen' end end should "return Martin and Frank when searching M, and Ma" do res1 = Lunar.search(Person, :fuzzy => { :name => 'M' }) res2 = Lunar.search(Person, :fuzzy => { :name => 'Ma' }) assert_equal %w{1001 1003}, res1.map(&:id) assert_equal %w{1001 1003}, res2.map(&:id) end should "return only Martin when searching Mar up to Martin" do res1 = Lunar.search(Person, :fuzzy => { :name => 'Mar' }) res2 = Lunar.search(Person, :fuzzy => { :name => 'Mart' }) res3 = Lunar.search(Person, :fuzzy => { :name => 'Marti' }) res4 = Lunar.search(Person, :fuzzy => { :name => 'Martin' }) assert_equal %w{1001}, res1.map(&:id) assert_equal %w{1001}, res2.map(&:id) assert_equal %w{1001}, res3.map(&:id) assert_equal %w{1001}, res4.map(&:id) end should "return only Frank when searching Mac up to Macallen" do res1 = Lunar.search(Person, :fuzzy => { :name => 'Mac' }) res2 = Lunar.search(Person, :fuzzy => { :name => 'Maca' }) res3 = Lunar.search(Person, :fuzzy => { :name => 'Macal' }) res4 = Lunar.search(Person, :fuzzy => { :name => 'Macall' }) res5 = Lunar.search(Person, :fuzzy => { :name => 'Macalle' }) res6 = Lunar.search(Person, :fuzzy => { :name => 'Macallen' }) assert_equal %w{1003}, res1.map(&:id) assert_equal %w{1003}, res2.map(&:id) assert_equal %w{1003}, res3.map(&:id) assert_equal %w{1003}, res4.map(&:id) assert_equal %w{1003}, res5.map(&:id) assert_equal %w{1003}, res6.map(&:id) end should "return the three of them when searching F" do res1 = Lunar.search(Person, :fuzzy => { :name => 'F' }) assert_equal %w{1001 1002 1003}, res1.map(&:id) end should "return the 2 fowlers when searching fo up to fowler" do res1 = Lunar.search(Person, :fuzzy => { :name => 'Fo' }) res2 = Lunar.search(Person, :fuzzy => { :name => 'Fow' }) res3 = Lunar.search(Person, :fuzzy => { :name => 'Fowl' }) res4 = Lunar.search(Person, :fuzzy => { :name => 'Fowle' }) res5 = Lunar.search(Person, :fuzzy => { :name => 'Fowler' }) assert_equal %w{1001 1002}, res1.map(&:id) assert_equal %w{1001 1002}, res2.map(&:id) assert_equal %w{1001 1002}, res3.map(&:id) assert_equal %w{1001 1002}, res4.map(&:id) assert_equal %w{1001 1002}, res5.map(&:id) end should "return be able to expire the stored union" do Lunar.stubs(:ttl).returns(1) search = Lunar::Search.new(Person, :fuzzy => { :name => 'Fo' }) search.results assert Lunar.redis.exists search.send(:dist_key) sleep 2 assert ! Lunar.redis.exists(search.send(:dist_key)) end end end