require 'rails_helper'
require "#{Rails.root}/app/services/snowflake.rb"
require "#{Rails.root}/app/services/snowflake_query.rb"

describe 'SnowflakeQuery' do
  before(:each) do
    # We basically mock out all of ruby-odbc here.
    fake_column1 = double('column1', type: ODBC::SQL_VARCHAR)
    fake_column2 = double('column2', type: ODBC::SQL_LONGVARCHAR)
    fake_column3 = double('column3', type: ODBC::SQL_NUMERIC, scale: 0)
    fake_column4 = double('column4', type: ODBC::SQL_DECIMAL, scale: 2)

    @fake_statement = double('statement', columns: { 'name' => fake_column1,
                                                     'description' => fake_column2,
                                                     'intval' => fake_column3,
                                                     'floatval' => fake_column4 })

    allow(@fake_statement).to receive(:drop)
    allow(@fake_statement).to receive(:fetch_all).
      and_return([['foo', 'foo row', '5', '2.2'],
                  ['bar', 'bar row', '499', '5']])

    @fake_conn = double('connection', run: @fake_statement)
    allow(@fake_conn).to receive(:transaction).and_yield(@fake_conn)
    allow(@fake_conn).to receive(:disconnect)

    allow(Snowflake).to receive(:conn).and_return(@fake_conn)

    @fake_logger = double
    allow(@fake_logger).to receive(:step)
    allow(@fake_logger).to receive(:done)
    allow(TimerLogger).to receive(:new).and_return(@fake_logger)
  end

  context 'query' do
    DB_ENVS.keys.each do |db_env|
      it "sets the database for a query on #{db_env}" do
        expect(@fake_conn).to receive(:run).with("USE DATABASE #{DB_ENVS[db_env]}")
        Snowflake.columns(db_env: db_env,
                          sql: 'SELECT * FROM und.table')
      end
    end

    it 'calls .run on the specified query' do
      expect(@fake_conn).to receive(:run).with('SELECT * FROM und.table')
      Snowflake.columns(db_env: 'prod',
                        sql: 'SELECT * FROM und.table')
    end

    it 'runs exactly two statements and closes its connection' do
      expect(@fake_conn).to receive(:run).exactly(2).times
      expect(@fake_conn).to receive(:disconnect).once
      Snowflake.columns(db_env: 'prod',
                        sql: 'SELECT * FROM und.table')
    end

    it 'calls done() on its TimerLogger' do
      expect(@fake_logger).to receive(:done)
      Snowflake.columns(db_env: 'prod',
                        sql: 'SELECT * FROM und.table')
    end

    it 'errors if given a nonexistent DB environment' do
      expect do
        Snowflake.columns(db_env: 'foo',
                          sql: 'SELECT * FROM und.table')
      end.
        to raise_error(StandardError)
    end

    it 'disconnects, logs the error message, then re-raises the error, if something crashes' do
      class HydraError < StandardError; end
      allow(@fake_statement).to receive(:columns).and_raise(HydraError.new('IM IN UR DATABASE KILLIN UR MODELZ'))
      expect(@fake_conn).to receive(:disconnect)
      expect(@fake_logger).to receive(:step).with(include('IM IN UR DATABASE KILLIN UR MODELZ'))
      expect(@fake_logger).to receive(:done)
      expect do
        Snowflake.columns(db_env: 'prod',
                          sql: 'SELECT * FROM und.table')
      end.
        to raise_error(HydraError)
    end

    it 'correctly processes data passed back from the connection' do
      output = Snowflake.columns(db_env: 'prod',
                                 sql: 'SELECT * FROM und.table')
      expect(output).to eq(%w[name description intval floatval])
    end
  end

  context 'query' do
    DB_ENVS.keys.each do |db_env|
      it "sets the database for a query on #{db_env}" do
        expect(@fake_conn).to receive(:run).with("USE DATABASE #{DB_ENVS[db_env]}")
        Snowflake.query(db_env: db_env,
                        sql: 'SELECT * FROM und.table',
                        net_id: 'egrantha',
                        param_values: {})
      end
    end

    it 'invokes Iron Man' do
      expect(@fake_conn).to receive(:run).with(APPLY_USER_SNIPPET, 'egrantha')
      Snowflake.query(db_env: 'prod',
                      sql: 'SELECT * FROM und.table',
                      net_id: 'egrantha',
                      param_values: {})
    end

    it 'makes the specified query' do
      expect(@fake_conn).to receive(:run).with('SELECT * FROM und.table')
      Snowflake.query(db_env: 'prod',
                      sql: 'SELECT * FROM und.table',
                      net_id: 'egrantha',
                      param_values: {})
    end

    it 'runs exactly three statements and closes its connection' do
      expect(@fake_conn).to receive(:run).exactly(3).times
      expect(@fake_conn).to receive(:disconnect).once
      Snowflake.query(db_env: 'prod',
                      sql: 'SELECT * FROM und.table',
                      net_id: 'egrantha',
                      param_values: {})
    end

    it 'calls done() on its TimerLogger' do
      expect(@fake_logger).to receive(:done)
      Snowflake.query(db_env: 'prod',
                      sql: 'SELECT * FROM und.table',
                      net_id: 'egrantha',
                      param_values: {})
    end

    it 'makes all run calls in order' do
      expect(@fake_conn).to receive(:run).with("USE DATABASE #{DB_ENVS['prod']}").ordered
      expect(@fake_conn).to receive(:run).with(APPLY_USER_SNIPPET, 'egrantha').ordered
      expect(@fake_conn).to receive(:run).with('SELECT * FROM und.table').ordered
      Snowflake.query(db_env: 'prod',
                      sql: 'SELECT * FROM und.table',
                      net_id: 'egrantha',
                      param_values: {})
    end

    it 'errors if given a nonexistent DB environment' do
      expect do
        Snowflake.query(db_env: 'foo',
                        sql: 'SELECT * FROM und.table',
                        net_id: 'egrantha',
                        param_values: {})
      end.
        to raise_error(StandardError)
    end

    it 'disconnects, logs the error message, then re-raises the error, if something crashes' do
      class HydraError < StandardError; end
      allow(@fake_statement).to receive(:fetch_all).and_raise(HydraError.new('IM IN UR DATABASE KILLIN UR MODELZ'))
      expect(@fake_conn).to receive(:disconnect)
      expect(@fake_logger).to receive(:step).with(include('IM IN UR DATABASE KILLIN UR MODELZ'))
      expect(@fake_logger).to receive(:done)
      expect do
        Snowflake.query(db_env: 'prod',
                        sql: 'SELECT * FROM und.table',
                        net_id: 'egrantha',
                        param_values: {})
      end.
        to raise_error(HydraError)
    end

    it 'correctly processes data passed back from the connection' do
      output = Snowflake.query(db_env: 'prod',
                               sql: 'SELECT * FROM und.table',
                               net_id: 'egrantha',
                               param_values: {})
      expect(output.rows).to eq(
        [{ 'name' => 'foo', 'description' => 'foo row', 'intval' => 5, 'floatval' => 2.2 },
         { 'name' => 'bar', 'description' => 'bar row', 'intval' => 499, 'floatval' => 5.0 }]
      )
    end

    it 'inserts parameters correctly' do
      expect(@fake_conn).to receive(:run).with("USE DATABASE #{DB_ENVS['prod']}")
      expect(@fake_conn).to receive(:run).with(APPLY_USER_SNIPPET, 'egrantha')
      expect(@fake_conn).to receive(:run).with(
        'SELECT ? AS first, ? AS second FROM und.table WHERE foo = ? AND bar = ? AND foo <> ?',
        'first_fixed', 'second_fixed', 'foo_value', 'bar_value', 'first_fixed'
      )
      Snowflake.query(db_env: 'prod',
                      sql: 'SELECT @fix_1 AS first, @fix_2 AS second ' \
                           'FROM und.table WHERE foo = @val AND bar = @val_2 AND foo <> @fix_1',
                      net_id: 'egrantha',
                      param_values: { 'fix_1' => 'first_fixed',
                                      'fix_2' => 'second_fixed',
                                      'val' => 'foo_value',
                                      'val_2' => 'bar_value' })
    end
  end
end
