# encoding: utf-8
require 'test_helper'

class ClientTest < TinyTds::TestCase
  describe 'with valid credentials' do
    before do
      @client = new_connection
    end

    it 'must not be closed' do
      assert !@client.closed?
      assert @client.active?
    end

    it 'allows client connection to be closed' do
      assert @client.close
      assert @client.closed?
      assert !@client.active?
      assert @client.dead?
      action = lambda { @client.execute('SELECT 1 as [one]').each }
      assert_raise_tinytds_error(action) do |e|
        assert_match %r{closed connection}i, e.message, 'ignore if non-english test run'
      end
    end

    it 'has getters for the tds version information (brittle since conf takes precedence)' do
      if sybase_ase?
        assert_equal 7, @client.tds_version
        assert_equal 'DBTDS_5_0 - 5.0 SQL Server', @client.tds_version_info
      elsif @client.tds_73?
        assert_equal 11, @client.tds_version
        assert_equal 'DBTDS_7_3 - Microsoft SQL Server 2008', @client.tds_version_info
      else
        assert_equal 9, @client.tds_version
        assert_equal 'DBTDS_7_1/DBTDS_8_0 - Microsoft SQL Server 2000', @client.tds_version_info
      end
    end

    it 'uses UTF-8 client charset/encoding by default' do
      assert_equal 'UTF-8', @client.charset
      assert_equal Encoding.find('UTF-8'), @client.encoding
    end

    it 'has a #escape method used for quote strings' do
      assert_equal "''hello''", @client.escape("'hello'")
    end

    ['CP850', 'CP1252', 'ISO-8859-1'].each do |encoding|
      it "allows valid iconv character set - #{encoding}" do
        begin
          client = new_connection(:encoding => encoding)
          assert_equal encoding, client.charset
          assert_equal Encoding.find(encoding), client.encoding
        ensure
          client.close if client
        end
      end
    end

    it 'must be able to use :host/:port connection' do
      host = ENV['TINYTDS_UNIT_HOST_TEST'] || ENV['TINYTDS_UNIT_HOST'] || 'localhost'
      port = ENV['TINYTDS_UNIT_PORT_TEST'] || ENV['TINYTDS_UNIT_PORT'] || 1433
      begin
        client = new_connection dataserver: nil, host: host, port: port
      ensure
        client.close if client
      end
    end unless sqlserver_azure?
  end

  describe 'With in-valid options' do
    before(:all) do
      init_toxiproxy
    end

    it 'raises an argument error when no :host given and :dataserver is blank' do
      assert_raises(ArgumentError) { new_connection :dataserver => nil, :host => nil }
    end

    it 'raises an argument error when no :username is supplied' do
      assert_raises(ArgumentError) { TinyTds::Client.new :username => nil }
    end

    it 'raises TinyTds exception with undefined :dataserver' do
      options = connection_options :login_timeout => 1, :dataserver => 'DOESNOTEXIST'
      action = lambda { new_connection(options) }
      assert_raise_tinytds_error(action) do |e|
        # Not sure why tese are different.
        if ruby_darwin?
          assert_equal 20009, e.db_error_number
          assert_equal 9, e.severity
          assert_match %r{is unavailable or does not exist}i, e.message, 'ignore if non-english test run'
        else
          assert_equal 20012, e.db_error_number
          assert_equal 2, e.severity
          assert_match %r{server name not found in configuration files}i, e.message, 'ignore if non-english test run'
        end
      end
      assert_new_connections_work
    end

    it 'raises TinyTds exception with long query past :timeout option' do
      client = new_connection :timeout => 1
      action = lambda { client.execute("WaitFor Delay '00:00:02'").do }
      assert_raise_tinytds_error(action) do |e|
        assert_equal 20003, e.db_error_number
        assert_equal 6, e.severity
        assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
      end
      assert_client_works(client)
      close_client(client)
      assert_new_connections_work
    end

    it 'must not timeout per sql batch when not under transaction' do
      client = new_connection :timeout => 2
      client.execute("WaitFor Delay '00:00:01'").do
      client.execute("WaitFor Delay '00:00:01'").do
      client.execute("WaitFor Delay '00:00:01'").do
      close_client(client)
    end

    it 'must not timeout per sql batch when under transaction' do
      client = new_connection :timeout => 2
      begin
        client.execute("BEGIN TRANSACTION").do
        client.execute("WaitFor Delay '00:00:01'").do
        client.execute("WaitFor Delay '00:00:01'").do
        client.execute("WaitFor Delay '00:00:01'").do
      ensure
        client.execute("COMMIT TRANSACTION").do
        close_client(client)
      end
    end

    it 'raises TinyTds exception with tcp socket network failure' do
      begin
        client = new_connection timeout: 2, port: 1234, host: ENV['TOXIPROXY_HOST']
        assert_client_works(client)
        action = lambda { client.execute("waitfor delay '00:00:05'").do }

        # Use toxiproxy to close the TCP socket after 1 second.
        # We want TinyTds to execute the statement, hit the timeout configured above, and then not be able to use the network to cancel
        # the network connection needs to close after the sql batch is sent and before the timeout above is hit
        Toxiproxy[:sqlserver_test].toxic(:slow_close, delay: 1000).apply do
          assert_raise_tinytds_error(action) do |e|
            assert_equal 20003, e.db_error_number
            assert_equal 6, e.severity
            assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
          end
        end
      ensure
        assert_new_connections_work
      end
    end

    it 'raises TinyTds exception with dead connection network failure' do
      skip if ruby_windows?

      begin
        client = new_connection timeout: 2, port: 1234, host: ENV['TOXIPROXY_HOST']
        assert_client_works(client)
        action = lambda { client.execute("waitfor delay '00:00:05'").do }

        # Use toxiproxy to close the network connection after 1 second.
        # We want TinyTds to execute the statement, hit the timeout configured above, and then not be able to use the network to cancel
        # the network connection needs to close after the sql batch is sent and before the timeout above is hit
        Toxiproxy[:sqlserver_test].toxic(:timeout, timeout: 1000).apply do
          assert_raise_tinytds_error(action) do |e|
            assert_equal 20047, e.db_error_number
            assert_includes [1,9], e.severity
            assert_match %r{dead or not enabled}i, e.message, 'ignore if non-english test run'
          end
        end
      ensure
        assert_new_connections_work
      end
    end

    it 'raises TinyTds exception with login timeout' do
      begin
        action = lambda do
          Toxiproxy[:sqlserver_test].toxic(:timeout, timeout: 0).apply do
            new_connection login_timeout: 1, port: 1234, host: ENV['TOXIPROXY_HOST']
          end
        end
        assert_raise_tinytds_error(action) do |e|
          assert_equal 20003, e.db_error_number
          assert_equal 6, e.severity
          assert_match %r{timed out}i, e.message, 'ignore if non-english test run'
        end
      ensure
        assert_new_connections_work
      end
    end

    it 'raises TinyTds exception with wrong :username' do
      skip if ENV['CI'] && sqlserver_azure? # Some issue with db_error_number.
      options = connection_options :username => 'willnotwork'
      action = lambda { new_connection(options) }
      assert_raise_tinytds_error(action) do |e|
        assert_equal sybase_ase? ? 4002 : 18456, e.db_error_number
        assert_equal 14, e.severity
        assert_match %r{login failed}i, e.message, 'ignore if non-english test run'
      end
      assert_new_connections_work
    end

  end

  describe '#parse_username' do
    let(:client) { @client = new_connection }

    it 'returns username if azure is not true' do
      _(
        client.send(:parse_username, username: 'user@abc123.database.windows.net')
      ).must_equal 'user@abc123.database.windows.net'
    end

    it 'returns short username if azure is true' do
      _(
        client.send(
          :parse_username,
          username: 'user@abc123.database.windows.net',
          host:     'abc123.database.windows.net',
          azure:    true
        )
      ).must_equal 'user@abc123'
    end

    it 'returns full username if azure is false' do
      _(
        client.send(
          :parse_username,
          username: 'user@abc123.database.windows.net',
          host:     'abc123.database.windows.net',
          azure:    false
        )
      ).must_equal 'user@abc123.database.windows.net'
    end

    it 'returns short username if passed and azure is true' do
      _(
        client.send(
          :parse_username,
          username: 'user@abc123',
          host:     'abc123.database.windows.net',
          azure:    true
        )
      ).must_equal 'user@abc123'
    end

    it 'returns username with servername if passed and azure is true' do
      _(
        client.send(
          :parse_username,
          username: 'user',
          host:     'abc123.database.windows.net',
          azure:    true
        )
      ).must_equal 'user@abc123'
    end

    it 'returns username with servername if passed and azure is false' do
      _(
        client.send(
          :parse_username,
          username: 'user',
          host:     'abc123.database.windows.net',
          azure:    false
        )
      ).must_equal 'user'
    end
  end
end
