jam-cloud/ruby/spec/jam_ruby/models/get_work_spec.rb

539 lines
26 KiB
Ruby

require 'spec_helper'
describe GetWork do
let(:austin_geo) { austin_geoip }
let(:dallas_geo) { dallas_geoip }
before(:each) do
create_phony_database
end
describe "get_work_list" do
it "selects no score when no other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
end
it "selects unscored location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == [other_connection.client_id]
GetWork.get_work_list(other_connection).should == [my_connection.client_id]
end
it "skips scored location" do
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
end
it "selects scored location with old scores" do
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
# age the scores
Score.connection.execute("UPDATE scores SET score_dt = score_dt - INTERVAL '200 hours'")
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == [other_connection.client_id]
GetWork.get_work_list(other_connection).should == [my_connection.client_id]
end
it "skips scored location with old and new scores" do
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
# age the scores
Score.connection.execute("UPDATE scores SET score_dt = score_dt - INTERVAL '200 hours'")
# create some newer ones
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
end
it "skips scores regardess of scoring direction" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(dallas_geo[:locidispid], austin_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
Score.connection.execute('DELETE from scores').check
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection).should == []
end
it "selects client even if client has scores to self" do
# this test just verifies that a bit of data in the db doesn't trip up the query
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(dallas_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == [other_connection.client_id]
GetWork.get_work_list(other_connection).should == [my_connection.client_id]
end
it "selects only one client from a given remote location" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 3)
list = GetWork.get_work_list(my_connection)
(list == [other_connection1.client_id] || list == [other_connection2.client_id]).should be_true # we don't know which one it'll pick
GetWork.get_work_list(other_connection1).should =~ [my_connection.client_id, other_connection2.client_id]
GetWork.get_work_list(other_connection2).should =~ [my_connection.client_id, other_connection1.client_id]
end
it "selects no clients when multiple clients in same location have a score" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 3)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
GetWork.get_work_list(my_connection).should == []
GetWork.get_work_list(other_connection1).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection2).should == [other_connection1.client_id]
end
it "selects two clients from differing, unscored locations" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should =~ [other_connection1.client_id, other_connection2.client_id]
GetWork.get_work_list(other_connection1).should =~ [my_connection.client_id, other_connection2.client_id]
GetWork.get_work_list(other_connection2).should =~ [my_connection.client_id, other_connection1.client_id]
end
it "ignores client with the same addr" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 1)
GetWork.get_work_list(my_connection).should == []
end
it "ignores client with the same addr" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 1)
GetWork.get_work_list(my_connection).should == []
end
it "randomizes ordering of selected locations" do
# if two clients have the same locidispid, only one is meant to be selected for scoring
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection1 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
initial_ordering = GetWork.get_work_list(my_connection)
initial_ordering.should =~ [other_connection1.client_id, other_connection2.client_id]
swapped = false
100.times do
# it's randomized results, so we have to let probability win out here. eventually, though (within 100 times? surely), we should see the ordering of work switch up
swapped = (GetWork.get_work_list(my_connection) == [initial_ordering[1], initial_ordering[0]])
break if swapped
end
swapped.should be_true
end
it "excludes udp unreachable clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, udp_reachable: false)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
it "excludes scoring_timeout clients (1)" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, scoring_timeout: 1.days.from_now)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
it "excludes scoring_timeout clients (2)" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, scoring_timeout: 1.days.from_now)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 3)
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
it "excludes connections in a session" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2,)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
music_session = FactoryGirl.create(:active_music_session, creator: my_connection.user)
other_connection.music_session = music_session
other_connection.save!
GetWork.get_work_list(my_connection).should == [other_connection2.client_id]
GetWork.get_work_list(other_connection).should == []
GetWork.get_work_list(other_connection2).should == [my_connection.client_id]
end
end
describe "record" do
let(:connection1) { FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: austin_ip_as_num, ip_address: austin_ip) }
let(:connection2) { FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: dallas_ip_as_num, ip_address: dallas_ip) }
it "records client errors if no score" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
result.should == {message: 'udpReachable is false (to=f)', error: true}
connection1.reload
connection2.reload
connection1.scoring_failures.should == 1
connection1.scoring_timeout.should == original_timeout1
connection1.scoring_timeout_occurrences.should == 0
connection2.scoring_failures.should == 1
connection2.scoring_timeout.should == original_timeout2
connection1.scoring_timeout_occurrences.should == 0
end
it "records client errors if addr == addr" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection1.ip_address, nil, '', false)
result.should == {message: 'aAddr and bAddr are the same (to=f)', error: true}
connection1.reload
connection2.reload
connection1.scoring_failures.should == 1
connection1.scoring_timeout.should == original_timeout1
connection1.scoring_timeout_occurrences.should == 0
connection2.scoring_failures.should == 1
connection2.scoring_timeout.should == original_timeout2
connection1.scoring_timeout_occurrences.should == 0
end
it "records success if valid" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
result.should == {message: 'OK (to=f)', error: false}
connection1.reload
connection2.reload
connection1.scoring_failures.should == 0
connection1.scoring_timeout.should == original_timeout1
connection1.scoring_timeout_occurrences.should == 0
connection2.scoring_failures.should == 0
connection2.scoring_timeout.should == original_timeout2
connection1.scoring_timeout_occurrences.should == 0
end
it "puts in doghouse after enough scoring errors" do
last_result = nil
APP_CONFIG.scoring_timeout_threshold.times do
last_result = Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
last_result.should == {message: 'udpReachable is false (to=t)', error: true}
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection1.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection2.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 1
end
describe "while in the doghouse" do
before(:each) do
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
end
it "another bad score comes in" do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 1
end
it "a good score comes in" do
# this has no effect when in the dog house
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 1
end
end
describe "after doghouse expires" do
before(:each) do
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
# bring scoring_timeout to the past
connection1.scoring_timeout = (APP_CONFIG.scoring_timeout_minutes * 2).minutes.ago
connection1.save!
connection2.scoring_timeout = (APP_CONFIG.scoring_timeout_minutes * 2).minutes.ago
connection2.save!
end
it "another bad score comes in" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
connection1.reload
connection2.reload
# failures shold keep increment, but the user should not yet in_scoring_timeout? because it's only one failure
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
connection1.in_scoring_timeout?.should be_false
expect(connection1.scoring_timeout).to be_within(1.second).of(original_timeout1)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold + 1
connection2.in_scoring_timeout?.should be_false
expect(connection2.scoring_timeout).to be_within(1.second).of(original_timeout2)
connection2.scoring_timeout_occurrences.should == 1
end
it "a good score comes in" do
original_timeout1 = connection1.scoring_timeout
original_timeout2 = connection2.scoring_timeout
# this has no effect when in the dog house
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
connection1.reload
connection2.reload
connection1.scoring_failures.should == 0
connection1.in_scoring_timeout?.should be_false
expect(connection1.scoring_timeout).to be_within(1.second).of(original_timeout1)
connection1.scoring_timeout_occurrences.should == 1
connection2.scoring_failures.should == 0
connection2.in_scoring_timeout?.should be_false
expect(connection2.scoring_timeout).to be_within(1.second).of(original_timeout2)
connection2.scoring_timeout_occurrences.should == 1
end
it "a good score comes in, then enough bad scores to be put back into timeout" do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, 20, '', true)
# if a good score comes in while in the dog house, everything should be set back to 0, and bad counting resumes
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection1.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
connection1.in_scoring_timeout?.should be_true
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 2
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold
connection2.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold
connection2.in_scoring_timeout?.should be_true
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 2
end
it "enough bad scores come in to put back into timeout" do
APP_CONFIG.scoring_timeout_threshold.times do
Score.record(connection1.user, connection1.client_id, connection1.ip_address, connection2.client_id, connection2.ip_address, nil, '', false)
end
connection1.reload
connection2.reload
connection1.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection1.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection1.in_scoring_timeout?.should be_true
expect(connection1.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection1.scoring_timeout_occurrences.should == 2
connection2.scoring_failures.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection2.scoring_failures_offset.should == APP_CONFIG.scoring_timeout_threshold * 2 # because this user keeps failing with no good scores
connection2.in_scoring_timeout?.should be_true
expect(connection2.scoring_timeout).to be_within(1.second).of(APP_CONFIG.scoring_timeout_minutes.minutes.from_now)
connection2.scoring_timeout_occurrences.should == 2
end
end
end
describe "summary" do
it "selects no score when no other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 1
summary[0].work_count.should == '0'
summary[0].client_id.should == my_connection.client_id
summary[0].email.should == my_connection.user.email
summary[0].user_id.should == my_connection.user.id
end
it "selects no score when no other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1, udp_reachable:true)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 1
summary[0].work_count.should == '0'
summary[0].client_id.should == my_connection.client_id
summary[0].email.should == my_connection.user.email
summary[0].user_id.should == my_connection.user.id
end
it "selects unscored location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
#score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 2
summary[0].work_count.should == '1'
summary[1].work_count.should == '1'
end
it "does not count scored location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 2
summary[0].work_count.should == '0'
summary[1].work_count.should == '0'
end
it "does not count duplicate location" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
score_location(austin_geo[:locidispid], dallas_geo[:locidispid], 20)
summary = GetWork.summary
summary.length.should == 3
summary[0].work_count.should == '0'
summary[1].work_count.should == '0'
summary[2].work_count.should == '0'
end
it "does not count udp_reachable" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, udp_reachable: false)
summary = GetWork.summary
summary.length.should == 2
summary[0].work_count.should == '0'
summary[1].work_count.should == '0'
end
it "does not count udp_reachable with 2 other clients" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2, udp_reachable: false)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
summary = GetWork.summary
summary.length.should == 3
summary[0].work_count.should == '1'
summary[1].work_count.should == '1'
summary[2].work_count.should == '0'
end
it "counts with 3" do
my_connection = FactoryGirl.create(:connection, locidispid: austin_geo[:locidispid], addr: 1)
other_connection = FactoryGirl.create(:connection, locidispid: dallas_geo[:locidispid], addr: 2)
other_connection2 = FactoryGirl.create(:connection, locidispid: houston_geoip[:locidispid], addr: 3)
#Database.dump('select * FROM get_work_summary_no_agg(INTERVAL \'120 hours\')');
summary = GetWork.summary
summary.length.should == 3
summary[0].work_count.should == '2'
summary[1].work_count.should == '2'
summary[2].work_count.should == '2'
end
end
end