require 'test_helper'

context 'class methods without setup' do
  teardown do
    AllSeeingEye.redis.flushall
  end
  
  test 'models know their own name' do
    assert_equal 'model', AllSeeingEye::Model.model_name
  end
  
  test 'models correctly find their next ID' do
    id = AllSeeingEye::Model.next_id
    (id + 1..(id + 11)).each {|n|assert_equal AllSeeingEye::Model.next_id, n}
  end
  
  test 'models should get fields' do
    assert_equal AllSeeingEye::Model.fields,
      {"uri"=>{"method"=>".request_uri", "object"=>"request"}, "action"=>{"method"=>"[:action]", "object"=>"params"}, "ip"=>{"method"=>".remote_ip", "object"=>"request"}, "controller"=>{"method"=>"[:controller]", "params"=>nil, "object"=>"params"}, "browser"=>{"method"=>".env[\"HTTP_USER_AGENT\"]", "object"=>"request"}, "status"=>{"method"=>".status", "object"=>"response"}}
  end
  
  test 'models should get field names' do
    assert_equal ["uri", "action", "ip", "controller", "browser", "status", "time_spent", "created_at"],
      AllSeeingEye::Model.field_names
  end
  
  test 'return a new model object' do
    time = Time.now
    obj = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => time)
    
    assert_equal '/test/', obj.uri
    assert_equal '127.0.0.1', obj.ip
    assert_equal time, obj.created_at
    assert_equal nil, obj.action
  end
  
  test 'create a model object' do
    obj = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1')
    
    assert_equal '/test/', obj.uri
    assert_equal '127.0.0.1', obj.ip
    assert_equal Marshal.dump(obj), AllSeeingEye.redis["allseeingeye:model:#{AllSeeingEye::Model.next_id - 1}"]
    assert_equal '1', AllSeeingEye.redis.zscore("allseeingeye:model:fields:uri", '/test/')
    assert_equal '1', AllSeeingEye.redis.zscore("allseeingeye:model:fields:ip", '127.0.0.1')
    assert_equal [obj.id.to_s], AllSeeingEye.redis.sort("allseeingeye:model:fields:uri:/test/", :by => 'nosort')
    assert_equal [obj.id.to_s], AllSeeingEye.redis.sort("allseeingeye:model:fields:ip:127.0.0.1", :by => 'nosort')
  end
  
  test 'unmarshal an object with load' do
    time = Time.now
    raw = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => time)
    dump = Marshal.dump(raw)
    obj = AllSeeingEye::Model.load(dump)
    
    assert_equal '/test/', obj.uri
    assert_equal '127.0.0.1', obj.ip
    assert_equal time, obj.created_at
    assert_equal raw.id, obj.id
  end
  
  test 'find an object by its ID' do
    time = Time.now
    obj = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => time)
    found = AllSeeingEye::Model.find(obj.id)
    
    [:uri, :ip, :created_at].each {|f| assert_equal obj.send(f), found.send(f)}
  end
  
  test 'find all objects' do
    obj1 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => Time.now)
    obj2 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => Time.now)
    obj3 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => Time.now)
    
    assert_equal [obj1.id, obj2.id, obj3.id], AllSeeingEye::Model.all.collect(&:id)
  end
  
  test 'find the count of all objects' do
    obj1 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => Time.now)
    obj2 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => Time.now)
    obj3 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => Time.now)
    
    assert_equal 3, AllSeeingEye::Model.count
  end
end

context 'class methods with setup' do
  setup do
    @time = Time.now
    
    @obj1 = AllSeeingEye::Model.create(:uri => '/', :ip => '127.0.0.1', :created_at => @time - 10 * 60)
    @obj2 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => @time - 5 * 60)
    @obj3 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => @time - 3 * 60)
    @obj4 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '192.168.0.1', :created_at => @time - 1 * 60)
    @obj5 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '127.0.0.1', :created_at => @time + 2 * 60)
    @obj6 = AllSeeingEye::Model.create(:uri => '/test/', :ip => '192.168.0.1', :created_at => @time + 2 * 60)
  end
  
  teardown do
    AllSeeingEye.redis.flushall
  end
  
  test 'find all objects in a date range with a start and a stop' do
    results = AllSeeingEye::Model.all(:start => @time - 11 * 60, :stop => @time - 4 * 60)
    
    assert_equal 2, results.count
    assert_equal @obj1.id, results[0].id
    assert_equal @obj2.id, results[1].id
  end
  
  test 'find all objects in a date range with only a start' do
    results = AllSeeingEye::Model.all(:start => @time)
    
    assert_equal 2, results.count
    assert_equal @obj5.id, results[0].id
    assert_equal @obj6.id, results[1].id
  end
  
  test 'find all objects in a date range with only a stop' do
    results = AllSeeingEye::Model.all(:stop => @time)
    
    assert_equal 4, results.count
    assert_equal @obj1.id, results[0].id
    assert_equal @obj2.id, results[1].id   
    assert_equal @obj3.id, results[2].id
    assert_equal @obj4.id, results[3].id   
  end
  
  test 'find model objects by a string field' do
    results = AllSeeingEye::Model.find_by_field('uri', :value => '/test/')

    assert_equal 5, results.count
    assert results.collect(&:id).include?(@obj2.id)
    assert results.collect(&:id).include?(@obj3.id)
    assert results.collect(&:id).include?(@obj4.id)
    assert results.collect(&:id).include?(@obj5.id)
    assert results.collect(&:id).include?(@obj6.id)
  end
  
  test 'find model objects by a string field with a start' do
    results = AllSeeingEye::Model.find_by_field('uri', :value => '/test/', :start => @time - 4 * 60)
    
    assert_equal 4, results.count
    assert results.collect(&:id).include?(@obj3.id)
    assert results.collect(&:id).include?(@obj4.id)
    assert results.collect(&:id).include?(@obj5.id)
    assert results.collect(&:id).include?(@obj6.id)
  end
  
  test 'find model objects by a string field with a stop' do
    results = AllSeeingEye::Model.find_by_field('uri', :value => '/test/', :stop => @time)
    
    assert_equal 3, results.count
    assert results.collect(&:id).include?(@obj2.id)
    assert results.collect(&:id).include?(@obj3.id)
    assert results.collect(&:id).include?(@obj4.id)
  end
  
  test 'find model objects by a string field with a start and a stop' do
    results = AllSeeingEye::Model.find_by_field('uri', :value => '/test/', :start => @time - 4 * 60, :stop => @time)
    
    assert_equal 2, results.count
    assert results.collect(&:id).include?(@obj3.id)
    assert results.collect(&:id).include?(@obj4.id)
  end
  
  test 'find model objects by a date field' do
    results = AllSeeingEye::Model.find_by_field('created_at', :start => @obj1.created_at, :stop => @obj4.created_at)
    
    assert_equal 4, results.count
    assert_equal results.first.id, @obj1.id
    assert_equal results.last.id, @obj4.id
  end
  
  test 'count model objects' do
    assert_equal 2, AllSeeingEye::Model.count(:start => @time - 11 * 60, :stop => @time - 4 * 60)
    assert_equal 2, AllSeeingEye::Model.count(:start => @time + 2 * 60, :stop => @time + 2 * 60)
    assert_equal 4, AllSeeingEye::Model.count(:stop => @time)
    assert_equal 2, AllSeeingEye::Model.count(:start => @time)
    assert_equal 6, AllSeeingEye::Model.count
  end
  
  test 'return the first object' do
    assert_equal @obj1.id, AllSeeingEye::Model.first.id
  end
  
  test 'return the last object' do
    assert_equal @obj6.id, AllSeeingEye::Model.last.id
  end
  
  test 'return count in a field' do
    counts = AllSeeingEye::Model.count_by_field('uri')
    
    assert_equal '/test/', counts.first.first
    assert_equal 5, counts.first.last
    assert_equal '/', counts.last.first
    assert_equal 1, counts.last.last
  end
  
  test 'search with Chronic' do
    results = AllSeeingEye::Model.search('last friday to five minutes ago')
    
    assert_equal 2, results.size
    assert_equal @obj1.id, results[0].id
    assert_equal @obj2.id, results[1].id
  end
  
  test 'search by a specific field' do
    results = AllSeeingEye::Model.search('uri:/test/')
    
    assert_equal 5, results.count
    assert results.collect(&:id).include?(@obj2.id)
    assert results.collect(&:id).include?(@obj3.id)
    assert results.collect(&:id).include?(@obj4.id)
    assert results.collect(&:id).include?(@obj5.id)
    assert results.collect(&:id).include?(@obj6.id)
  end
  
  test 'search without a specific field' do
    results = AllSeeingEye::Model.search('192.168.0.1')
    
    assert_equal 2, results.count
    assert results.collect(&:id).include?(@obj4.id)
    assert results.collect(&:id).include?(@obj6.id)
  end
end

context 'many objects' do
  setup do
    @time = Time.now
    
    2000.times do |n|
      instance_variable_set("@obj#{n}".to_sym, AllSeeingEye::Model.create(:uri => '/', :ip => '127.0.0.1', :created_at => @time - (n * (1999 - n)) * 60))
    end
  end
  
  test 'conglomerate an array successfully' do
    results = AllSeeingEye::Model.all
    counts = AllSeeingEye::Model.conglomerate(results)
    
    assert_equal 500, counts.count
    assert_equal 2000, counts.inject(0) {|sum, k| sum + k.last}
    assert_equal @obj999.created_at.to_i, counts.first.first
  end
end

context 'instance methods' do  
  teardown do
    AllSeeingEye.redis.flushall
  end
  
  test 'assign all attr_accessors from fields at initialization' do
    obj = AllSeeingEye::Model.new
    
    AllSeeingEye::Model.field_names.each {|f| assert obj.respond_to?(f.to_sym); assert obj.respond_to?("#{f}=".to_sym)}
  end
  
  test 'assign an object an ID if it does not have one' do
    obj = AllSeeingEye::Model.new
    
    assert_equal nil, obj.instance_variable_get(:@id)
    assert_equal 1, obj.id
    assert_equal 1, obj.instance_variable_get(:@id)
  end
  
  test 'save a new model object' do
    obj = AllSeeingEye::Model.new(:uri => '/test/', :ip => '127.0.0.1')
    
    assert obj.save
    assert_equal '/test/', obj.uri
    assert_equal '127.0.0.1', obj.ip
    assert_equal Marshal.dump(obj), AllSeeingEye.redis["allseeingeye:model:#{AllSeeingEye::Model.next_id - 1}"]
    assert_equal '1', AllSeeingEye.redis.zscore("allseeingeye:model:fields:uri", '/test/')
    assert_equal '1', AllSeeingEye.redis.zscore("allseeingeye:model:fields:ip", '127.0.0.1')
    assert_equal [obj.id.to_s], AllSeeingEye.redis.sort("allseeingeye:model:fields:uri:/test/", :by => 'nosort')
  end
  
  test 'save a new model object with date attributes' do
    time = Time.now
    obj = AllSeeingEye::Model.new(:created_at => time)
    
    assert obj.save
    assert_equal time, obj.created_at
    assert_equal Marshal.dump(obj), AllSeeingEye.redis["allseeingeye:model:#{AllSeeingEye::Model.next_id - 1}"]
    assert_equal time.to_i.to_s, AllSeeingEye.redis.zscore("allseeingeye:model:fields:created_at", obj.id)
  end
  
  test 'ignore empty and nil attributes in model object creation' do
    time = Time.now
    obj = AllSeeingEye::Model.new(:created_at => time, :uri => '/test/', :ip => nil, :action => '')
    
    assert obj.save
    assert_equal time, obj.created_at
    assert_equal '/test/', obj.uri
    assert_equal nil, obj.ip
    assert_equal '', obj.action
    assert_equal Marshal.dump(obj), AllSeeingEye.redis["allseeingeye:model:#{AllSeeingEye::Model.next_id - 1}"]
    assert_equal time.to_i.to_s, AllSeeingEye.redis.zscore("allseeingeye:model:fields:created_at", obj.id)
    assert_equal '1', AllSeeingEye.redis.zscore("allseeingeye:model:fields:uri", '/test/')
    assert_equal nil, AllSeeingEye.redis.zscore("allseeingeye:model:fields:ip", nil)
    assert_equal nil, AllSeeingEye.redis.zscore("allseeingeye:model:fields:action", '')
    assert_equal [obj.id.to_s], AllSeeingEye.redis.sort("allseeingeye:model:fields:uri:/test/", :by => 'nosort')
    assert_equal [], AllSeeingEye.redis.sort("allseeingeye:model:fields:ip:", :by => 'nosort')
    assert_equal [], AllSeeingEye.redis.sort("allseeingeye:model:fields:action:", :by => 'nosort')
  end
  
end