require 'spec_helper' describe IcecastMount do let(:icecast_mount) { FactoryGirl.create(:icecast_mount) } let(:output) { StringIO.new } let(:builder) { ::Builder::XmlMarkup.new(:target => output, :indent => 1) } it "save error" do mount = IcecastMount.new mount.save.should be_false mount.errors[:name].should == ["can't be blank", "must start with /"] end it "save" do mount = IcecastMount.new mount.name = "/" + Faker::Lorem.characters(10) mount.stream_name = Faker::Lorem.characters(10) mount.stream_description = Faker::Lorem.characters(10) mount.stream_url = Faker::Lorem.characters(10) mount.genre = Faker::Lorem.characters(10) mount.source_username = Faker::Lorem.characters(10) mount.source_pass = Faker::Lorem.characters(10) mount.intro = Faker::Lorem.characters(10) mount.fallback_mount = Faker::Lorem.characters(10) mount.on_connect = Faker::Lorem.characters(10) mount.on_disconnect = Faker::Lorem.characters(10) mount.fallback_override = true mount.fallback_when_full = true mount.max_listeners = 1000 mount.max_listener_duration = 3600 mount.authentication = FactoryGirl.create(:icecast_user_authentication) mount.server = FactoryGirl.create(:icecast_server_with_overrides) mount.save! mount.dumpXml(builder) output.rewind xml = Nokogiri::XML(output) xml.css('mount mount-name').text.should == mount.name xml.css('mount username').text.should == mount.source_username xml.css('mount password').text.should == mount.source_pass xml.css('mount max-listeners').text.should == mount.max_listeners.to_s xml.css('mount max-listener-duration').text.should == mount.max_listener_duration.to_s xml.css('mount intro').text.should == mount.intro xml.css('mount fallback-mount').text.should == mount.fallback_mount xml.css('mount fallback-override').text.should == mount.fallback_override.to_s xml.css('mount fallback-when-full').text.should == mount.fallback_when_full.to_s xml.css('mount stream-name').text.should == mount.stream_name xml.css('mount stream-description').text.should == mount.stream_description xml.css('mount stream-url').text.should == mount.stream_url xml.css('mount genre').text.should == mount.genre xml.css('mount bitrate').length.should == 0 xml.css('mount charset').text == mount.charset xml.css('mount public').text == mount.is_public.to_s xml.css('mount type').text == mount.mime_type xml.css('mount subtype').text == mount.subtype xml.css('mount burst-size').length.should == 0 xml.css('mount mp3-metadata-interval').length.should == 0 xml.css('mount hidden').text.should == mount.hidden.to_s xml.css('mount on-connect').text.should == mount.on_connect xml.css('mount on-disconnect').text.should == mount.on_disconnect xml.css('mount dump-file').length.should == 0 xml.css('mount authentication').length.should == 1 # no reason to test futher; it's tested in that model end describe "override xml over mount template" do let(:mount) {FactoryGirl.create(:iceast_mount_with_template)} it "should allow override by mount" do mount.dumpXml(builder) output.rewind xml = Nokogiri::XML(output) xml.css('mount mount-name').text.should == mount.name xml.css('mount username').text.should == mount.source_username xml.css('mount bitrate').text.should == mount.bitrate.to_s xml.css('mount type').text.should == mount.mount_template.mime_type xml.css('mount stream-url').text.should == mount.stream_url # now see the stream_url, and bitrate, go back to the template's value because we set it to nil mount.bitrate = nil mount.stream_url = nil mount.save! output = StringIO.new builder = ::Builder::XmlMarkup.new(:target => output, :indent => 1) mount.dumpXml(builder) output.rewind xml = Nokogiri::XML(output) xml.css('mount bitrate').text.should == mount.mount_template.bitrate.to_s xml.css('mount stream-url').text.should == mount.mount_template.stream_url end end describe "poke configs" do let(:server) { a = FactoryGirl.create(:icecast_server_with_overrides); a.config_updated; IcecastServer.find(a.id) } before(:each) do server.mounts << FactoryGirl.create(:icecast_mount, server: server) server.save! server.config_updated server.reload server.config_changed.should == 0 end it "success via server" do server.mounts.first.save! server.reload server.config_changed.should == 1 end it "success when deleted" do server.mounts.first.destroy server.reload server.config_changed.should == 1 end end describe "icecast server callbacks" do it "source up" do icecast_mount.source_up end end describe "listener/source" do let(:mount) {FactoryGirl.create(:iceast_mount_with_template)} describe "listeners" do it "listener_add" do mount.listener_add mount.listeners.should == 1 end it "listener_remove when at 0" do mount.listener_remove mount.listeners.should == 0 end it "listener_remove" do mount.listener_add mount.listener_remove mount.listeners.should == 0 end end describe "sources" do it "source_up" do mount.source_up mount.sourced.should == true end end describe "sources" do it "source_down" do mount.source_up mount.source_down mount.sourced.should == false end end end describe "build_session_mount" do let(:server1) {FactoryGirl.create(:icecast_server_minimal)} let(:server2) {FactoryGirl.create(:icecast_server_with_overrides)} let(:server3) {FactoryGirl.create(:icecast_server_with_overrides)} let(:hidden_music_session) { FactoryGirl.create(:active_music_session, :fan_access => false)} let(:public_music_session) { FactoryGirl.create(:active_music_session, :fan_access => true)} let(:public_music_session2) { FactoryGirl.create(:active_music_session, :fan_access => true)} let(:public_music_session3) { FactoryGirl.create(:active_music_session, :fan_access => true)} before(:each) do end it "no fan access means no mount" do mount = IcecastMount.build_session_mount(hidden_music_session.music_session, hidden_music_session, IcecastServer.find_best_server_for_user(hidden_music_session.creator)) mount.should be_nil end it "with no servers" do IcecastServer.count.should == 0 mount = IcecastMount.build_session_mount(public_music_session.music_session, public_music_session, IcecastServer.find_best_server_for_user(public_music_session.creator)) mount.should be_nil end it "with a server that has a mount template" do server1.mount_template.should_not be_nil mount = IcecastMount.build_session_mount(public_music_session.music_session, public_music_session, IcecastServer.find_best_server_for_user(public_music_session.creator)) mount.should_not be_nil mount.save! end it "with a server that already has an associated mount" do server1.mount_template.should_not be_nil mount = IcecastMount.build_session_mount(public_music_session.music_session, public_music_session, IcecastServer.find_best_server_for_user(public_music_session.creator)) mount.save! mount = IcecastMount.build_session_mount(public_music_session2.music_session, public_music_session2, IcecastServer.find_best_server_for_user(public_music_session2.creator)) mount.save! server1.reload server1.mounts.length.should == 2 end it "picks a second server once the 1st has been chosen" do server1.touch mount = IcecastMount.build_session_mount(public_music_session.music_session, public_music_session, IcecastServer.find_best_server_for_user(public_music_session.creator)) mount.listeners = 1 # affect the weight mount.save! server2.touch mount = IcecastMount.build_session_mount(public_music_session2.music_session, public_music_session2, IcecastServer.find_best_server_for_user(public_music_session2.creator)) mount.save! server1.reload server1.mounts.length.should == 1 server2.reload server2.mounts.length.should == 1 end it "picks the 1st server again once the 2nd has higher weight" do server1.touch mount = IcecastMount.build_session_mount(public_music_session.music_session, public_music_session, IcecastServer.find_best_server_for_user(public_music_session.creator)) mount.listeners = 1 # affect the weight mount.save! server2.touch mount = IcecastMount.build_session_mount(public_music_session2.music_session, public_music_session2, IcecastServer.find_best_server_for_user(public_music_session2.creator)) mount.sourced = 1 mount.save! mount = IcecastMount.build_session_mount(public_music_session3.music_session, public_music_session3, IcecastServer.find_best_server_for_user(public_music_session3.creator)) mount.listeners = 1 mount.save! server1.reload server1.mounts.length.should == 2 server2.reload server2.mounts.length.should == 1 end end describe "source_changes are deleted if source_direction transitions" do it "no change if same source_direction" do mount = FactoryGirl.create(:iceast_mount_with_music_session, source_direction: true) change1 = FactoryGirl.create(:icecast_source_change, mount: mount, source_direction: true, success:true) mount.source_changes.size.should == 1 mount.source_direction = true mount.save! mount = IcecastMount.find(mount.id) mount.source_changes.size.should == 1 end it "NOT deleted on transition to down" do mount = FactoryGirl.create(:iceast_mount_with_music_session, source_direction: true) change1 = FactoryGirl.create(:icecast_source_change, mount: mount, source_direction: true, success:true) mount.source_changes.size.should == 1 mount.source_direction = false mount.save! mount = IcecastMount.find(mount.id) mount.source_changes.size.should == 1 end it "not deleted on transition to up" do mount = FactoryGirl.create(:iceast_mount_with_music_session, source_direction: false) change1 = FactoryGirl.create(:icecast_source_change, mount: mount, source_direction: true, success:true) mount.source_changes.size.should == 1 mount.source_direction = true mount.save! mount.reload mount.source_changes.size.should == 1 end end describe "state" do def success_state(reason, detail = nil) {success: true, reason: reason, detail:detail} end def fail_state(reason, detail = nil) {success: false, reason: reason, detail:detail} end let(:sourced_mount) {FactoryGirl.create(:iceast_mount_with_music_session, sourced: true, sourced_needs_changing_at: nil, listeners: 1)} let(:mount_needing_source) {FactoryGirl.create(:iceast_mount_with_music_session, sourced: false, sourced_needs_changing_at: Time.now, listeners: 1)} it "happy sourced mount" do sourced_mount.state.should == success_state('source_up') end it "just transitioned" do mount_needing_source.state.should == success_state('transition_down', 'initial') end it "transition timeout" do #change1 = FactoryGirl.create(:icecast_source_change, mount: mount, source_direction: true, success:true) mount_needing_source.sourced_needs_changing_at = (APP_CONFIG.source_changes_missing_secs + 1).seconds.ago mount_needing_source.save! mount_needing_source.state.should == fail_state('transition_timeout_down', 'initial') end describe "client attempted transition and" do let!(:change1) { FactoryGirl.create(:icecast_source_change, mount: mount_needing_source, source_direction: true, success:true) } it "happy sourced mount" do mount_needing_source.sourced = true mount_needing_source.save! mount_needing_source.state.should == success_state('source_up', change1.user.id) end it "succeeded recently in correct transition" do mount_needing_source.state.should == success_state('transition_down') end it "succeeded a while ago in correct transition" do change1.update_attribute(:created_at, (APP_CONFIG.source_changes_missing_secs + 1).seconds.ago) mount_needing_source.state.should == fail_state('transition_timeout_down') end it "failed recently in correct transition" do change1.success = false change1.reason = 'bleh' change1.source_direction = true change1.save! mount_needing_source.state.should == fail_state('source_wrong_down', change1.reason) end it "failed a while ago in correct transition" do change1.success = false change1.reason = 'bleh' change1.source_direction = true change1.created_at = (APP_CONFIG.source_changes_missing_secs + 1).seconds.ago change1.save! mount_needing_source.state.should == fail_state('source_wrong_down', change1.reason) end it "failed recently in wrong transition" do change1.success = false change1.reason = 'bleh' change1.source_direction = false change1.save! mount_needing_source.state.should == fail_state('transition_down') end it "failed a while ago in wrong transition" do change1.success = false change1.reason = 'bleh' change1.source_direction = false change1.save! mount_needing_source.sourced_needs_changing_at = (APP_CONFIG.source_changes_missing_secs + 1).seconds.ago mount_needing_source.save! mount_needing_source.state.should == fail_state('source_wrong_down', 'no_client') end end end end