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