require 'spec_helper'

describe ActiveRecordViews::ChecksumCache do
  let(:connection) { ActiveRecord::Base.connection }

  describe 'initialisation' do
    def metadata_table_exists?
      if Rails::VERSION::MAJOR >= 5
        connection.data_source_exists?('active_record_views')
      else
        connection.table_exists?('active_record_views')
      end
    end

    context 'no existing table' do
      it 'creates the table' do
        expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ACREATE TABLE active_record_views/).once.and_call_original

        expect(metadata_table_exists?).to eq false
        ActiveRecordViews::ChecksumCache.new(connection)
        expect(metadata_table_exists?).to eq true

        expect(connection.column_exists?('active_record_views', 'class_name')).to eq true
        expect(connection.column_exists?('active_record_views', 'options')).to eq true
      end
    end

    context 'existing table with current structure' do
      before do
        ActiveRecordViews::ChecksumCache.new(connection)
        expect(metadata_table_exists?).to eq true
      end

      it 'does not recreate the table' do
        expect(ActiveRecord::Base.connection).to receive(:execute).never

        ActiveRecordViews::ChecksumCache.new(connection)
      end
    end

    context 'existing table without class name' do
      before do
        connection.execute 'CREATE TABLE active_record_views(name text PRIMARY KEY, checksum text NOT NULL);'

        connection.execute 'CREATE VIEW test_view AS SELECT 42 AS id;'
        connection.execute "INSERT INTO active_record_views VALUES ('test_view', 'dummy');"

        connection.execute 'CREATE VIEW other_view AS SELECT 123 AS id;'
      end

      it 'drops existing managed views recreates the table' do
        expect(connection.column_exists?('active_record_views', 'class_name')).to eq false
        expect(ActiveRecordViews.view_exists?(connection, 'test_view')).to eq true
        expect(ActiveRecordViews.view_exists?(connection, 'other_view')).to eq true

        sql_statements.clear

        ActiveRecordViews::ChecksumCache.new(connection)

        expect(sql_statements.grep_v(/^\s*SELECT/)).to match [
          'BEGIN',
          'DROP VIEW IF EXISTS test_view CASCADE;',
          'DROP TABLE active_record_views;',
          /^CREATE TABLE active_record_views/,
          'COMMIT',
        ]

        expect(connection.column_exists?('active_record_views', 'class_name')).to eq true
        expect(ActiveRecordViews.view_exists?(connection, 'test_view')).to eq false
        expect(ActiveRecordViews.view_exists?(connection, 'other_view')).to eq true
      end
    end

    context 'existing table without options or refreshed at timestamp' do
      before do
        connection.execute 'CREATE TABLE active_record_views(name text PRIMARY KEY, class_name text NOT NULL UNIQUE, checksum text NOT NULL);'

        connection.execute 'CREATE VIEW test_view AS SELECT 42 AS id;'
        connection.execute "INSERT INTO active_record_views VALUES ('test_view', 'dummy', 'Dummy');"
      end

      it 'upgrades the table without losing data' do
        expect(connection.column_exists?('active_record_views', 'options')).to eq false
        expect(connection.column_exists?('active_record_views', 'refreshed_at')).to eq false
        expect(ActiveRecordViews.view_exists?(connection, 'test_view')).to eq true

        expect(ActiveRecord::Base.connection).to receive(:execute).with("ALTER TABLE active_record_views ADD COLUMN options json NOT NULL DEFAULT '{}';").once.and_call_original
        expect(ActiveRecord::Base.connection).to receive(:execute).with("ALTER TABLE active_record_views ADD COLUMN refreshed_at timestamp;").once.and_call_original

        ActiveRecordViews::ChecksumCache.new(connection)

        expect(connection.column_exists?('active_record_views', 'options')).to eq true
        expect(connection.column_exists?('active_record_views', 'refreshed_at')).to eq true
        expect(ActiveRecordViews.view_exists?(connection, 'test_view')).to eq true
      end
    end
  end

  describe 'options serialization' do
    let(:dummy_data) { {class_name: 'Test', checksum: '12345'} }
    it 'errors if setting with non-symbol keys' do
      cache = ActiveRecordViews::ChecksumCache.new(connection)
      expect {
        cache.set 'test', dummy_data.merge(options: {'blah' => 123})
      }.to raise_error ArgumentError, 'option keys must be symbols'
    end

    it 'retrieves hash with symbolized keys and preserves value types' do
      options = {foo: 'hi', bar: 123, baz: true}

      cache = ActiveRecordViews::ChecksumCache.new(connection)
      cache.set 'test', dummy_data.merge(options: options)
      expect(cache.get('test')[:options]).to eq options
    end
  end
end