require 'spec_helper' require 'flydata-core/mysql/compatibility_checker' module FlydataCore module Mysql describe 'CompatibilityChecker' do let(:database) { 'test_db' } let(:mysql_host) { hostname + '.' + domain_name } let(:hostname) { 'test-hostname' } let(:domain_name) { 'flydata.com' } let(:tables) { %w(table_1 table_2 table_3) } let(:option) do { database: database, host: mysql_host, tables: tables } end describe MysqlCompatibilityChecker do let(:subject_object) { described_class.new(option) } describe '#rds?' do subject { subject_object.rds? } context 'When called for non-rds host' do let(:mysql2_error_msg) { "PROCEDURE mysql.rds_show_configuration does not exist" } before do allow(subject_object).to receive(:exec_query).with(MysqlCompatibilityChecker::RDS_CHECK_QUERY).and_raise(Mysql2::Error, mysql2_error_msg) end context 'When called for on-premise host' do let(:hostname) { 'test-hostname' } let(:domain_name) { 'flydata.com' } it { is_expected.to be_falsy } end context 'When called for ec2 host' do let(:hostname) { 'ec2-12-345-678-90.us-west-1' } let(:domain_name) { 'compute.amazonaws.com' } it { is_expected.to be_falsy } end end context 'When called for RDS host' do let(:hostname) { 'test-read-replica.stuvwxyz.us-west-1' } let(:domain_name) { 'rds.amazonaws.com' } it { is_expected.to be_truthy } end context 'when called for secure tunnel user' do let(:hostname) { 'mysql.dse-9999-zzz8xy765' } let(:domain_name) { 'flydata.com' } context 'When called for non-rds host' do let(:mysql2_error_msg) { "PROCEDURE mysql.rds_show_configuration does not exist" } it do expect(subject_object).to receive(:exec_query).with(MysqlCompatibilityChecker::RDS_CHECK_QUERY).and_raise(Mysql2::Error, mysql2_error_msg) is_expected.to be_falsy end end context 'When called for RDS host' do let(:result) { double 'rds query result' } it do expect(subject_object).to receive(:exec_query).with(MysqlCompatibilityChecker::RDS_CHECK_QUERY).and_return(result) is_expected.to be_truthy end end end context 'When unexpected error returned' do let(:mysql2_error_msg) { "Unknown MySQL server host '#{mysql_host}'" } before do allow(subject_object).to receive(:exec_query).with(MysqlCompatibilityChecker::RDS_CHECK_QUERY).and_raise(Mysql2::Error, mysql2_error_msg) end it do expect { subject }.to raise_error(Mysql2::Error, mysql2_error_msg) end end end end describe TableExistenceChecker do let(:subject_object) { described_class.new(option) } describe '#create_query' do subject { subject_object.create_query } it { is_expected.to eq < 'table_1'}, {'table_name' => 'table_2'}, {'table_name' => 'table_3'}, ] end it { expect{subject}.not_to raise_error } end context 'when some tables does not exist' do let(:result) do [{"table_name" => "table_2"}] end it do expect{subject}.to raise_error {|e| expect(e).to be_kind_of(FlydataCore::MysqlCompatibilityError) expect(e.to_s.include?("table_1")).to be_truthy expect(e.to_s.include?("table_2")).to be_falsey expect(e.to_s.include?("table_3")).to be_truthy } end end end end describe PrimaryKeyChecker do let(:subject_object) { described_class.new(option) } describe '#create_query' do subject { subject_object.create_query } it { is_expected.to eq < ['id'] } end it { is_expected.to eq < ['id'] } end it { is_expected.to be_nil } end end describe '#check_reesult' do let(:result) { [] } subject { subject_object.check_result(result) } context 'when all tables have pk' do let(:result) do [] end it { expect{subject}.not_to raise_error } end context 'when some tables does not have pk' do let(:result) do [{"table_name" => "table_1"}, {"table_name" => "table_3"}] end it do expect{subject}.to raise_error {|e| expect(e).to be_kind_of(FlydataCore::MysqlCompatibilityError) expect(e.to_s.include?("table_1")).to be_truthy expect(e.to_s.include?("table_2")).to be_falsey expect(e.to_s.include?("table_3")).to be_truthy } end end end end describe PkOverrideChecker do let(:subject_object) { described_class.new(option) } describe '#create_query' do subject { subject_object.create_query } context 'when pk_override is given' do before do option[:pk_override] = { 'table_1' => ['id'], 'table_2' => ['name'], } end it { is_expected.to eq < "table_1", "column_name" => "id"}, {"table_name" => "table_1", "column_name" => "value"}, {"table_name" => "table_2", "column_name" => "name"}, {"table_name" => "table_2", "column_name" => "age"}, ] } before do option[:pk_override] = pk_override end context 'when given pk_override exist' do let(:pk_override) { { 'table_1' => ['id'], 'table_2' => ['name'], } } it { expect{subject}.not_to raise_error } end context 'when some columns do not exist' do let(:pk_override) { { 'table_1' => ['id'], 'table_2' => ['id'], } } it do expect{subject}.to raise_error {|e| expect(e).to be_kind_of(FlydataCore::MysqlCompatibilityError) expect(e.to_s.include?("table_1")).to be_falsey expect(e.to_s.include?("table_2")).to be_truthy } end end end end end describe BinlogParameterChecker do let(:subject_object) { described_class.new({}) } describe "#check_result" do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'ROW'}, {'Variable_name'=>'binlog_checksum','Value'=>'NONE'}, {'Variable_name'=>'log_bin_use_v1_row_events','Value'=>'ON'}, {'Variable_name'=>'log_slave_updates','Value'=>'ON'}, {'Variable_name'=>'net_read_timeout','Value'=>600}, {'Variable_name'=>'net_write_timeout','Value'=>600}, {'Variable_name'=>'wait_timeout','Value'=>60} ] end subject { subject_object.check_result(sys_var, {}) } context "where parameters are valid" do it do expect{subject}.to_not raise_error end end context "where parameters are invalid" do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'MIXED'}, {'Variable_name'=>'binlog_checksum','Value'=>'NONE'}, {'Variable_name'=>'log_bin_use_v1_row_events','Value'=>'ON'}, {'Variable_name'=>'log_slave_updates','Value'=>'ON'}, {'Variable_name'=>'net_read_timeout','Value'=>30}, {'Variable_name'=>'net_write_timeout','Value'=>600}, ] end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /binlog_format.*\n.*net_read_timeout/) end end context "where parameters are not returned" do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'ROW'}, {'Variable_name'=>'log_slave_updates','Value'=>'ON'}, {'Variable_name'=>'net_read_timeout','Value'=>600}, {'Variable_name'=>'net_write_timeout','Value'=>600}, {'Variable_name'=>'wait_timeout','Value'=>60}, ] end it 'ignores missing parameters' do expect{subject}.not_to raise_error end end end describe "#compare_sys_var_values" do let(:input_val) { nil } let(:expected_val) do { 'value_1' => 'foo', 'value_2' => 50, 'value_3' => {">="=>100} } end let(:errors) { {} } subject { subject_object.send(:compare_sys_var_values, input_val, expected_val) } shared_examples "expect correct errors from values" do it { is_expected.to eq(expecting_errors) } end context "when getting valid parameters" do let(:input_val) do { 'value_1' => 'foo', 'value_2' => 50, 'value_3' => 200 } end let(:expecting_errors) { {} } it_behaves_like "expect correct errors from values" end context "when getting one invalid exact parameters for int" do let(:input_val) do { 'value_1' => 'foo', 'value_2' => 20, 'value_3' => 100 } end let(:expecting_errors) do { 'value_2' => { actual_val: 20, err_reason: "value_2 is '20' but should be '50'" } } end it_behaves_like "expect correct errors from values" end context "when getting one invalid exact parameter for string" do let(:input_val) do { 'value_1' => 'fo', 'value_2' => 50, 'value_3' => 100 } end let(:expecting_errors) do { 'value_1' => { actual_val: 'fo', err_reason: "value_1 is 'fo' but should be 'foo'" } } end it_behaves_like "expect correct errors from values" end context "when getting one invalid comparitive parameter that's lower than required" do let(:input_val) do { 'value_1' => 'foo', 'value_2' => 50, 'value_3' => 1 } end let(:expecting_errors) do { 'value_3' => { actual_val: 1, err_reason: "value_3 is '1' but should be at least '100'" } } end it_behaves_like "expect correct errors from values" end context "when getting a valid comparitive parameter that's higher than required" do let(:input_val) do { 'value_1' => 'foo', 'value_2' => 50, 'value_3' => 101 } end let(:expecting_errors) { {} } it_behaves_like "expect correct errors from values" end context "when getting muliple invalid parameters" do let(:input_val) do { 'value_1' => 'fo', 'value_2' => 50, 'value_3' => 15 } end let(:expecting_errors) { {value_1:'fo', value_3:15} } let(:expecting_errors) do { 'value_1' => { actual_val: 'fo', err_reason: "value_1 is 'fo' but should be 'foo'" }, 'value_3' => { actual_val: 15, err_reason: "value_3 is '15' but should be at least '100'" }, } end it_behaves_like "expect correct errors from values" end end end describe RequiredBinlogParameterChecker do let(:subject_object) { described_class.new({}) } describe "#check_result" do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'ROW'}, {'Variable_name'=>'binlog_checksum','Value'=>'NONE'}, {'Variable_name'=>'log_bin_use_v1_row_events','Value'=>'ON'}, {'Variable_name'=>'log_slave_updates','Value'=>'ON'}, ] end subject { subject_object.check_result(sys_var) } context 'where parameters are valid' do it { expect{subject}.not_to raise_error } end context 'where parameters contain optional paramters' do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'ROW'}, {'Variable_name'=>'binlog_checksum','Value'=>'NONE'}, {'Variable_name'=>'log_bin_use_v1_row_events','Value'=>'ON'}, {'Variable_name'=>'log_slave_updates','Value'=>'ON'}, {'Variable_name'=>'net_read_timeout','Value'=>1}, {'Variable_name'=>'net_write_timeout','Value'=>1}, {'Variable_name'=>'wait_timeout','Value'=>1} ] end it { expect{subject}.not_to raise_error } end context 'where parameters contain invalid parameters' do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'MIXED'}, {'Variable_name'=>'binlog_checksum','Value'=>'NONE'}, {'Variable_name'=>'log_bin_use_v1_row_events','Value'=>'ON'}, {'Variable_name'=>'log_slave_updates','Value'=>'OFF'}, ] end it { expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /binlog_format.*\n.*log_slave_updates/) } end end end describe OptionalBinlogParameterChecker do let(:subject_object) { described_class.new({}) } describe "#check_result" do let(:sys_var) do [ {'Variable_name'=>'net_read_timeout','Value'=>600}, {'Variable_name'=>'net_write_timeout','Value'=>600}, {'Variable_name'=>'wait_timeout','Value'=>60} ] end subject { subject_object.check_result(sys_var) } context 'where parameters are valid' do it { expect{subject}.not_to raise_error } end context 'where parameters contain required paramters' do let(:sys_var) do [ {'Variable_name'=>'binlog_format','Value'=>'ROW'}, {'Variable_name'=>'binlog_checksum','Value'=>'NONE'}, {'Variable_name'=>'log_bin_use_v1_row_events','Value'=>'ON'}, {'Variable_name'=>'log_slave_updates','Value'=>'ON'}, {'Variable_name'=>'net_read_timeout','Value'=>600}, {'Variable_name'=>'net_write_timeout','Value'=>600}, {'Variable_name'=>'wait_timeout','Value'=>60}, ] end it { expect{subject}.not_to raise_error } end context 'where parameters contain invalid parameters' do let(:sys_var) do [ {'Variable_name'=>'net_read_timeout','Value'=>2}, {'Variable_name'=>'net_write_timeout','Value'=>600}, {'Variable_name'=>'wait_timeout','Value'=>1} ] end it { expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /^\[WARNING.*\n.*net_read_timeout.*/) } end end end describe RdsMasterStatusChecker do describe '#check_result' do let(:subject_object) { described_class.new({}) } let(:result) { [] } subject { subject_object.check_result(result) } context 'when result is empty' do it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError) end end context 'when result is not empty' do =begin mysql> show master status; +----------------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +----------------------------+----------+--------------+------------------+-------------------+ | mysql-bin-changelog.026292 | 31300 | | | | +----------------------------+----------+--------------+------------------+-------------------+ 1 row in set (0.00 sec) =end let(:result) do { 'File' => 'mysql-bin-changelog.026292', 'Position' => '31300', 'Binlog_Do_DB' => '', 'Binlog_Ignore_DB' => '', 'Executed_Gtid_Set' => '', } end it do expect{subject}.not_to raise_error end end end end end end