require 'spec_helper' describe Mongo::ServerSelector do include_context 'server selector' describe '.get' do let(:selector) do described_class.get(:mode => name, :tag_sets => tag_sets) end context 'when a server selector object is passed' do let(:name) do :primary end it 'returns the object' do expect(described_class.get(selector)).to be(selector) end end context 'when the mode is primary' do let(:name) do :primary end it 'returns a read preference of class Primary' do expect(selector).to be_a(Mongo::ServerSelector::Primary) end end context 'when the mode is primary_preferred' do let(:name) do :primary_preferred end it 'returns a read preference of class PrimaryPreferred' do expect(selector).to be_a(Mongo::ServerSelector::PrimaryPreferred) end end context 'when the mode is secondary' do let(:name) do :secondary end it 'returns a read preference of class Secondary' do expect(selector).to be_a(Mongo::ServerSelector::Secondary) end end context 'when the mode is secondary_preferred' do let(:name) do :secondary_preferred end it 'returns a read preference of class SecondaryPreferred' do expect(selector).to be_a(Mongo::ServerSelector::SecondaryPreferred) end end context 'when the mode is nearest' do let(:name) do :nearest end it 'returns a read preference of class Nearest' do expect(selector).to be_a(Mongo::ServerSelector::Nearest) end end context 'when a mode is not provided' do let(:selector) { described_class.get } it 'returns a read preference of class Primary' do expect(selector).to be_a(Mongo::ServerSelector::Primary) end end context 'when tag sets are provided' do let(:selector) do described_class.get(:mode => :secondary, :tag_sets => tag_sets) end let(:tag_sets) do [{ 'test' => 'tag' }] end it 'sets tag sets on the read preference object' do expect(selector.tag_sets).to eq(tag_sets) end end context 'when server_selection_timeout is specified' do let(:selector) do described_class.get(:mode => :secondary, :server_selection_timeout => 1) end it 'sets server selection timeout on the read preference object' do expect(selector.server_selection_timeout).to eq(1) end end context 'when server_selection_timeout is not specified' do let(:selector) do described_class.get(:mode => :secondary) end it 'sets server selection timeout to the default' do expect(selector.server_selection_timeout).to eq(Mongo::ServerSelector::SERVER_SELECTION_TIMEOUT) end end context 'when local_threshold is specified' do let(:selector) do described_class.get(:mode => :secondary, :local_threshold => 0.010) end it 'sets local_threshold on the read preference object' do expect(selector.local_threshold).to eq(0.010) end end context 'when local_threshold is not specified' do let(:selector) do described_class.get(:mode => :secondary) end it 'sets local threshold to the default' do expect(selector.local_threshold).to eq(Mongo::ServerSelector::LOCAL_THRESHOLD) end end end describe "#select_server" do context 'when #select returns a list of nils' do let(:servers) do [ make_server(:primary) ] end let(:cluster) do double('cluster').tap do |c| allow(c).to receive(:topology).and_return(topology) allow(c).to receive(:servers).and_return(servers) allow(c).to receive(:single?).and_return(false) allow(c).to receive(:sharded?).and_return(false) allow(c).to receive(:unknown?).and_return(false) allow(c).to receive(:scan!).and_return(true) allow(c).to receive(:options).and_return(server_selection_timeout: 0.1) end end let(:read_pref) do described_class.get(mode: :primary).tap do |pref| allow(pref).to receive(:select).and_return([ nil, nil ]) end end it 'raises a NoServerAvailable error' do expect do read_pref.select_server(cluster) end.to raise_exception(Mongo::Error::NoServerAvailable) end end context 'when the cluster has a server_selection_timeout set' do let(:servers) do [ make_server(:secondary), make_server(:primary) ] end let(:cluster) do double('cluster').tap do |c| allow(c).to receive(:topology).and_return(topology) allow(c).to receive(:servers).and_return(servers) allow(c).to receive(:single?).and_return(false) allow(c).to receive(:sharded?).and_return(false) allow(c).to receive(:unknown?).and_return(false) allow(c).to receive(:scan!).and_return(true) allow(c).to receive(:options).and_return(server_selection_timeout: 0) end end let(:read_pref) do described_class.get(mode: :nearest) end it 'uses the server_selection_timeout of the cluster' do expect{ read_pref.select_server(cluster) }.to raise_exception(Mongo::Error::NoServerAvailable) end end context 'when the cluster has a local_threshold set' do let(:near_server) do make_server(:secondary).tap do |s| allow(s).to receive(:connectable?).and_return(true) allow(s).to receive(:average_round_trip_time).and_return(100) allow(s).to receive(:check_driver_support!).and_return(true) end end let(:far_server) do make_server(:secondary).tap do |s| allow(s).to receive(:connectable?).and_return(true) allow(s).to receive(:average_round_trip_time).and_return(200) allow(s).to receive(:check_driver_support!).and_return(true) end end let(:servers) do [ near_server, far_server ] end let(:cluster) do double('cluster').tap do |c| allow(c).to receive(:topology).and_return(topology) allow(c).to receive(:servers).and_return(servers) allow(c).to receive(:single?).and_return(false) allow(c).to receive(:sharded?).and_return(false) allow(c).to receive(:unknown?).and_return(false) allow(c).to receive(:scan!).and_return(true) allow(c).to receive(:options).and_return(local_threshold: 0.050) end end let(:read_pref) do described_class.get(mode: :nearest) end it 'uses the local_threshold of the cluster' do expect(read_pref.select_server(cluster)).to eq(near_server) end end end shared_context 'a ServerSelector' do context 'when cluster#servers is empty' do let(:servers) do [] end let(:cluster) do double('cluster').tap do |c| allow(c).to receive(:topology).and_return(topology) allow(c).to receive(:servers).and_return(servers) allow(c).to receive(:single?).and_return(single) allow(c).to receive(:sharded?).and_return(sharded) allow(c).to receive(:unknown?).and_return(false) allow(c).to receive(:scan!).and_return(true) allow(c).to receive(:options).and_return(server_selection_timeout: 0.1) end end let(:read_pref) do described_class.get(mode: :primary) end it 'raises a NoServerAvailable error' do expect do read_pref.select_server(cluster) end.to raise_exception(Mongo::Error::NoServerAvailable) end end end context 'when the cluster has a Single topology' do let(:single) { true } let(:sharded) { false } it_behaves_like 'a ServerSelector' end context 'when the cluster has a ReplicaSet topology' do let(:single) { false } let(:sharded) { false } it_behaves_like 'a ServerSelector' end context 'when the cluster has a Sharded topology' do let(:single) { false } let(:sharded) { true } it_behaves_like 'a ServerSelector' end describe '#inspect' do let(:options) do {} end let(:read_pref) do described_class.get({ mode: mode }.merge(options)) end context 'when the mode is primary' do let(:mode) do :primary end it 'includes the mode in the inspect string' do expect(read_pref.inspect).to match(/#{mode.to_s}/i) end end context 'when there are tag sets' do let(:mode) do :secondary end let(:options) do { tag_sets: [{ 'data_center' => 'nyc' }] } end it 'includes the tag sets in the inspect string' do expect(read_pref.inspect).to include(options[:tag_sets].inspect) end end context 'when there is a max staleness set' do let(:mode) do :secondary end let(:options) do { max_staleness: 123 } end it 'includes the tag sets in the inspect string' do expect(read_pref.inspect).to match(/max_staleness/i) expect(read_pref.inspect).to match(/123/) end end end end