Adapt connector tests

pull/15456/head
Javier Goizueta 5 years ago
parent 0463bf036e
commit 91c0abc74c

@ -0,0 +1,94 @@
require 'carto/connector/provider'
class DummyConnectorProvider < Carto::Connector::Provider
metadata id: 'dummy', name: 'Dummy'
required_parameters %I(table req1 req2)
optional_parameters %I(opt1 opt2)
@copies = []
def self.copies
@copies
end
@error_message = nil
def self.failing_with(msg)
prev = @error_message
@error_message = msg
yield
@error_message = prev
end
def self.error_message
@error_message
end
DEFAULT_FEATURES = {
'sql_queries': false,
'list_databases': false,
'list_tables': true,
'preview_table': false,
'dry_run': false,
'list_projects': false
}
def copy_table(schema_name:, table_name:, limits: {})
validate!
raise self.class.error_message if self.class.error_message
self.class.copies << [schema_name, table_name, limits]
@connector_context.execute_in_user_database "CREATE TABLE #{schema_name}.#{table_name}()"
end
def check_connection
self.class.error_message ? false : true
end
def table_name
@params[:table]
end
def remote_data_updated?
true
end
def list_tables(limits: {})
must_be_defined_in_derived_class unless features_information[:list_tables]
[{schema:'s1', name: 't1'}, {schema:'s2', name: 't2'}]
end
def list_databases()
must_be_defined_in_derived_class unless features_information[:list_databases]
['db1', 'db2']
end
def list_projects()
must_be_defined_in_derived_class unless features_information[:list_projects]
[{id: 'project-1', friendly_name: 'Project 1'}, {id: 'project-2', friendly_name: 'Project 2'}]
end
def list_project_datasets(project_id)
must_be_defined_in_derived_class unless features_information[:list_projects]
[{id: 'data-1', qualified_name: "#{project_id}.data-1"}, {id: 'data-2', qualified_name: "#{project_id}.data-2"}]
end
def list_project_dataset_tables(project_id, dataset_id)
must_be_defined_in_derived_class unless features_information[:list_projects]
[{id: 't-1', qualified_name: "#{project_id}.#{dataset_id}.t-1"}, {id: 't-2', qualified_name: "#{project_id}.#{dataset_id}.t-2"}]
end
def dry_run
must_be_defined_in_derived_class unless features_information[:dry_run]
raise self.class.error_message if self.class.error_message
{dry_run_results: '...'}
end
def features_information
DEFAULT_FEATURES
end
end
def dummy_connector_provider_with_id(id, name=nil, features=DummyConnectorProvider::DEFAULT_FEATURES)
Class.new(DummyConnectorProvider) do
metadata id: id, name: name || id
@copies = []
define_method(:features_information){ features }
end
end

@ -8,9 +8,8 @@ require_relative '../doubles/log'
require_relative '../doubles/indexer'
require_relative '../factories/pg_connection'
require_relative '../doubles/user'
require_relative '../doubles/input_file_size_limit'
require_relative '../doubles/table_row_count_limit'
require_relative '../../../../spec/helpers/feature_flag_helper'
require_relative '../doubles/connector'
describe CartoDB::Importer2::ConnectorRunner do
before(:all) do
@ -19,8 +18,9 @@ describe CartoDB::Importer2::ConnectorRunner do
@pg_options = @user.db_service.db_configuration_for
@feature_flag = FactoryGirl.create(:feature_flag, name: 'carto-connectors', restricted: true)
@fake_log = CartoDB::Importer2::Doubles::Log.new(@user)
@providers = %w(mysql postgres sqlserver hive)
@providers = %w(dummy)
@fake_log.clear
Carto::Connector::PROVIDERS << DummyConnectorProvider
Carto::Connector.providers(all: true).keys.each do |provider_name|
Carto::ConnectorProvider.create! name: provider_name
end
@ -37,30 +37,23 @@ describe CartoDB::Importer2::ConnectorRunner do
Carto::Connector.providers(all: true).keys.each do |provider_name|
Carto::ConnectorProvider.find_by_name(provider_name).destroy
end
Carto::Connector::PROVIDERS.delete DummyConnectorProvider
end
after(:each) do
DummyConnectorProvider.copies.clear
end
include FeatureFlagHelper
describe 'with working connectors' do
before(:all) do
# Simulate connector success by ignoring all db opeartions
Carto::Connector::Context.any_instance.stubs(:execute_as_superuser).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute_as_superuser_with_timeout).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute_with_timeout).returns(nil)
end
it "Succeeds if parameters are correct" do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding'
req1: 'a',
req2: 'b',
opt1: 'c'
}
options = {
pg: @pg_options,
@ -76,20 +69,18 @@ describe CartoDB::Importer2::ConnectorRunner do
connector.provider_name.should eq provider
end
end
DummyConnectorProvider.copies.size.should eq 1
DummyConnectorProvider.copies[0][0].should eq 'cdb_importer'
DummyConnectorProvider.copies[0][1].should match /\Aimporter_/
end
end
it "Fails if parameters are invalid" do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding',
req1: 'a',
req2: 'b',
invalid_parameter: 'xyz'
}
options = {
@ -110,17 +101,37 @@ describe CartoDB::Importer2::ConnectorRunner do
end
end
it "Fails if parameters are missing" do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
table: 'thetable',
req1: 'a',
opt1: 'c'
}
options = {
pg: @pg_options,
log: @fake_log,
user: @user
}
@providers.each do |provider|
config = { provider => { 'enabled' => true } }
Cartodb.with_config connectors: config do
connector = CartoDB::Importer2::ConnectorRunner.new(parameters.merge(provider: provider).to_json, options)
connector.run
connector.success?.should be false
connector.provider_name.should eq provider
@fake_log.to_s.should match /Error Missing required parameters req2/m
end
end
end
end
it "Fails without the feature flag" do
with_feature_flag @user, 'carto-connectors', false do
parameters = {
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding'
req1: 'a',
req2: 'b'
}
options = {
pg: @pg_options,
@ -142,14 +153,9 @@ describe CartoDB::Importer2::ConnectorRunner do
it "Fails if provider is not available" do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding'
req1: 'a',
req2: 'b',
}
options = {
pg: @pg_options,
@ -170,25 +176,12 @@ describe CartoDB::Importer2::ConnectorRunner do
end
describe 'with failing connectors' do
before(:all) do
# Simulate connector success when executing non-privileged SQL
Carto::Connector::Context.any_instance.stubs(:execute_as_superuser).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute_as_superuser_with_timeout).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute_with_timeout).raises("SQL EXECUTION ERROR")
end
it "Always fails" do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding'
req1: 'a',
req2: 'b'
}
options = {
pg: @pg_options,
@ -196,13 +189,15 @@ describe CartoDB::Importer2::ConnectorRunner do
user: @user
}
@providers.each do |provider|
config = { provider => { 'enabled' => true } }
Cartodb.with_config connectors: config do
connector = CartoDB::Importer2::ConnectorRunner.new(parameters.merge(provider: provider).to_json, options)
connector.run
connector.success?.should be false
connector.provider_name.should eq provider
@fake_log.to_s.should match /SQL EXECUTION ERROR/m
Carto::Connector.provider_class(provider).failing_with('COPY ERROR') do
config = { provider => { 'enabled' => true } }
Cartodb.with_config connectors: config do
connector = CartoDB::Importer2::ConnectorRunner.new(parameters.merge(provider: provider).to_json, options)
connector.run
connector.success?.should be false
connector.provider_name.should eq provider
@fake_log.to_s.should match /COPY ERROR/m
end
end
end
end
@ -210,24 +205,14 @@ describe CartoDB::Importer2::ConnectorRunner do
end
describe 'with invalid provider' do
Carto::Connector::Context.any_instance.stubs(:execute_as_superuser).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute_as_superuser_with_timeout).returns(nil)
Carto::Connector::Context.any_instance.stubs(:execute_with_timeout).returns(nil)
it "Fails at creation" do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
provider: 'invalid_provider',
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding'
req1: 'a',
req2: 'b'
}
options = {
pg: @pg_options,
@ -254,15 +239,10 @@ describe CartoDB::Importer2::ConnectorRunner do
with_feature_flag @user, 'carto-connectors', true do
parameters = {
connection: {
server: 'theserver',
username: 'theuser',
password: 'thepassword',
database: 'thedatabase'
},
table: 'thetable',
encoding: 'theencoding'
}
req1: 'a',
req2: 'b'
}
options = {
pg: @pg_options,
log: @fake_log,

File diff suppressed because it is too large Load Diff

@ -1,126 +0,0 @@
def match_sql_command(sql)
options_pattern = %q{
(?:\s+OPTIONS\s*\((?<options>
(?:
\s*
(?:\"(?:[^\"]+)\"|(?:[^\s]+))
\s+
(?:\'(?:[^\']*)\'|(?:[^\'].+))
)*
\s*
)\))?
}
patterns = {
create_server: %r{
CREATE\s+SERVER\s+(?<server_name>[^\s]+)
\s+
FOREIGN\s+DATA\s+WRAPPER\s+(?<fdw_name>[^\s]+)
#{options_pattern}
}xi,
create_user_mapping: %r{
CREATE\s+USER\s+MAPPING\s+FOR\s+\"?(?<user_name>[^\s\"]+)\"?
\s+
SERVER\s+(?<server_name>[^\s]+)
#{options_pattern}
}xi,
import_foreign_schema: %r{
IMPORT\s+FOREIGN\s+SCHEMA\s+\"?(?<remote_schema_name>[^\s\"]+)\"?
\s+
FROM\s+SERVER\s+(?<server_name>[^\s]+)
\s+
INTO\s+\"?(?<schema_name>[^\s\"]+)\"?
#{options_pattern}
}xi,
import_foreign_schema_limited: %r{
IMPORT\s+FOREIGN\s+SCHEMA\s+\"?(?<remote_schema_name>[^\s\"]+)\"?
\s+
LIMIT\s+TO\s+\((?<limited_to>.+)\)
\s+
FROM\s+SERVER\s+(?<server_name>[^\s]+)
\s+
INTO\s+\"?(?<schema_name>[^\s\"]+)\"?
#{options_pattern}
}xi,
create_foreign_table: %r{
CREATE\+FOREIGN\+TABLE\s+(?<table_name>.+)\s*\((?<columns>.+)\)
\s+
SERVER\s+(?<server_name>[^\s]+)
#{options_pattern}
}xi,
grant_select: %r{
GRANT\s+SELECT\s+ON\s+(?<table_name>[^\s]+)\s+TO\s+\"?(?<user_name>[^\s\"]+)\"?
}xi,
create_table_as_select: %r{
CREATE\s+TABLE\s+(?<table_name>[^\s]+)\s+AS\s+SELECT\s+(?<select>.+)(?:\s+LIMIT\s+(?<limit>\d+))?
}xi,
drop_foreign_table_if_exists: %r{
DROP\s+FOREIGN\s+TABLE\s+IF\s+EXISTS\s+(?<table_name>[^\s]+)(?:\s+(?<cascade>CASCADE))?
}xi,
drop_server_if_exists: %r{
DROP\s+SERVER\s+IF\s+EXISTS\s+(?<server_name>[^\s]+)(?:\s+(?<cascade>CASCADE))?
}xi,
drop_usermapping_if_exists: %r{
DROP\s+USER\s+MAPPING\s+IF\s+EXISTS\s+FOR\s+\"?(?<user_name>[^\s\"]+)\"?\s+SERVER\s+(?<server_name>[^\s]+)
}xi,
rename_foreign_table: %r{
ALTER\s+FOREIGN\s+TABLE\s+(?<table_name>.+)\s+RENAME\s+TO\s+(?<new_name>.+)
}xi,
select_all: %r{
SELECT\s+\*\s+FROM\s+(?<from>.+)
}xi
}
option_pair = %r{
\A\s*
(?:\"(?<quoted_name>[^\"]+)\"|(?<name>[^\s]+))
\s+
(?:\'(?<quoted_value>[^\']*)\'|(?<value>[^\'].+))
}x
result = nil
patterns.each do |command, regexp|
match = regexp.match(sql)
if match
result = {
command: command
}
match.names.each do |name|
value = match[name]
if value
if name.in? ['options', 'columns']
value = Hash[
value.split(',').map { |opt|
match = opt.match(option_pair)
[match[:name] || match[:quoted_name], match[:value] || match[:quoted_value]] if match
}.compact
]
end
result[name.to_sym] = value
end
end
break
end
end
result
end
def match_sql(sql)
sql.scan(/(?:'[^']*'|[^;])+/).map { |command| match_sql_command(command) }.compact
end
def expect_sql(sql, expectactions = [])
match_sql(sql).zip(expectactions).each do |parsed_sql, sql_expectactions|
sql_expectactions.each do |key, expected_value|
if expected_value.nil?
parsed_sql[key].should be_nil, "#{key.inspect} wasn't expected in SQL"
else
parsed_sql[key].should_not be_nil, "#{key.inspect} was expected in SQL"
if expected_value.is_a?(Regexp)
parsed_sql[key].should match expected_value
else
parsed_sql[key].should == expected_value
end
end
end
end
end

@ -1,5 +1,6 @@
require 'spec_helper_min'
require 'support/helpers'
require_relative '../../../../services/importer/spec/doubles/connector'
describe Carto::Api::ConnectorsController do
include HelperMethods
@ -8,6 +9,17 @@ describe Carto::Api::ConnectorsController do
before(:all) do
FactoryGirl.create(:carto_feature_flag, name: 'carto-connectors', restricted: false)
@user = FactoryGirl.create(:carto_user)
Carto::Connector::PROVIDERS << dummy_connector_provider_with_id('postgres', 'PostgreSQL')
Carto::Connector::PROVIDERS << dummy_connector_provider_with_id('hive', 'Hive')
Carto::Connector::PROVIDERS << dummy_connector_provider_with_id('bigquery', 'BigQuery',
'sql_queries': false,
'list_databases': true,
'list_tables': true,
'preview_table': true,
'dry_run': true,
'list_projects': true
)
@connector_provider_postgres = FactoryGirl.create(:connector_provider, name: 'postgres')
@connector_provider_hive = FactoryGirl.create(:connector_provider, name: 'hive')
@connector_config_user = FactoryGirl.create(:connector_configuration,
@ -69,12 +81,13 @@ describe Carto::Api::ConnectorsController do
it 'returns provider information for regular user' do
get_json api_v1_connectors_show_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), {}, @headers do |response|
response.status.should be_success
response.body[:features][:sql_queries].should eq true
response.body[:features][:sql_queries].should eq false
response.body[:features][:list_tables].should eq true
response.body[:parameters][:table][:required].should eq false
response.body[:parameters][:connection][:database][:required].should eq true
response.body[:parameters][:connection][:username][:required].should eq true
response.body[:parameters][:connection][:port][:required].should eq false
response.body[:parameters][:table][:required].should eq true
response.body[:parameters][:req1][:required].should eq true
response.body[:parameters][:req2][:required].should eq true
response.body[:parameters][:opt1][:required].should eq false
response.body[:parameters][:opt2][:required].should eq false
end
end
it 'returns 422 if provider doesn\'t exists' do
@ -85,13 +98,10 @@ describe Carto::Api::ConnectorsController do
end
describe '#tables' do
before(:each) do
pending "Provision odbc_fdw in CI server"
end
it 'returns connector tables list' do
get_json api_v1_connectors_tables_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key, server: 'localhost', port: '5432', database: 'carto_db_test', username: 'postgres'), {}, @headers do |response|
get_json api_v1_connectors_tables_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), {}, @headers do |response|
response.status.should be_success
response.body[0]["schema"].should eq "public"
response.body[0]["schema"].should eq "s1"
response.body[0]["name"].blank?.should eq false
end
end
@ -103,11 +113,8 @@ describe Carto::Api::ConnectorsController do
end
describe '#connect' do
before(:each) do
pending "Provision odbc_fdw in CI server"
end
it 'returns true if connection went ok' do
get_json api_v1_connectors_connect_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key, server: 'localhost', port: '5432', database: 'carto_db_test', username: 'postgres'), {}, @headers do |response|
get_json api_v1_connectors_connect_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), {}, @headers do |response|
response.status.should be_success
response.body[:connected].should eq true
end
@ -117,50 +124,54 @@ describe Carto::Api::ConnectorsController do
response.status.should eq 422
end
end
it 'returns 400 if connection went ko' do
get_json api_v1_connectors_connect_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key, server: 'localhost', port: '5432', database: 'unknown_db', username: 'postgres'), {}, @headers do |response|
response.status.should be 400
response.body[:errors].present?.should eq true
it 'returns false if connection went ko' do
Carto::Connector.provider_class('postgres').failing_with('CONNECTION PROBLEM') do
get_json api_v1_connectors_connect_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), {}, @headers do |response|
response.body[:connected].should eq false
end
end
end
end
describe '#dryrun' do
it 'returns 422 if not supported' do
params = {
connection: {
server: 'localhost', port: '5432', database: 'carto_db_test', username: 'postgres'
}
}
post_json api_v1_connectors_dryrun_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), params do |response|
post_json api_v1_connectors_dryrun_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), {} do |response|
response.status.should eq 422
end
end
it 'returns dry-run information' do
pending "Provision odbc_fdw & OAuth in CI server"
params = {
}
post_json api_v1_connectors_dryrun_url(provider_id: 'bigquery', user_domain: @user.username, api_key: @user.api_key), params do |response|
post_json api_v1_connectors_dryrun_url(provider_id: 'bigquery', user_domain: @user.username, api_key: @user.api_key), {} do |response|
response.status.should be_success
# response.body.keys.should include(:total_bytes_processed)
response.body.keys.should include(:dry_run_results)
end
end
it 'returns 400 in case of failure' do
Carto::Connector.provider_class('bigquery').failing_with('BIG PROBLEM') do
post_json api_v1_connectors_dryrun_url(provider_id: 'bigquery', user_domain: @user.username, api_key: @user.api_key), {} do |response|
response.status.should be 400
response.body[:errors].present?.should eq true
response.body[:errors].should match /BIG PROBLEM/m
end
end
end
end
describe '#projects' do
it 'returns 422 if not supported' do
get_json api_v1_connectors_projects_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key, server: 'localhost', port: '5432', database: 'carto_db_test', username: 'postgres'), {}, @headers do |response|
get_json api_v1_connectors_projects_url(provider_id: 'postgres', user_domain: @user.username, api_key: @user.api_key), {}, @headers do |response|
response.status.should eq 422
end
end
it 'returns connector projects list' do
pending "Provision odbc_fdw & OAuth in CI server"
get_json api_v1_connectors_projects_url(provider_id: 'bigquery', user_domain: @user.username, api_key: @user.api_key), {}, @headers do |response|
response.status.should be_success
# response.body[0]["id"].should eq "..."
# response.body[0]["friendly_nameme"].should eq "..."
response.body.should eq [
{ 'id' => 'project-1', 'friendly_name' => "Project 1" },
{ 'id' => 'project-2', 'friendly_name' => "Project 2" }
]
end
end
@ -178,12 +189,13 @@ describe Carto::Api::ConnectorsController do
end
end
it 'returns connector projects list' do
pending "Provision odbc_fdw & OAuth in CI server"
it 'returns connector project datasets list' do
get_json api_v1_connectors_project_datasets_url(provider_id: 'bigquery', user_domain: @user.username, api_key: @user.api_key, project_id: 'my-project'), {}, @headers do |response|
response.status.should be_success
# response.body[0]["id"].should eq "my-dataset"
# response.body[0]["qualified_name"].should eq "my-project.my-dataset"
response.body.should eq [
{ 'id' => 'data-1', 'qualified_name' => "my-project.data-1" },
{ 'id' => 'data-2', 'qualified_name' => "my-project.data-2" }
]
end
end
@ -201,12 +213,13 @@ describe Carto::Api::ConnectorsController do
end
end
it 'returns connector projects list' do
pending "Provision odbc_fdw & OAuth in CI server"
it 'returns connector project dataset tables list' do
get_json api_v1_connectors_project_dataset_tables_url(provider_id: 'bigquery', user_domain: @user.username, api_key: @user.api_key, project_id: 'my-project', dataset_id: 'my-dataset'), {}, @headers do |response|
response.status.should be_success
# response.body[0]["id"].should eq "my-table"
# response.body[0]["qualified_name"].should eq "my-project.my-dataset.my-table"
response.body.should eq [
{ 'id' => 't-1', 'qualified_name' => "my-project.my-dataset.t-1" },
{ 'id' => 't-2', 'qualified_name' => "my-project.my-dataset.t-2" }
]
end
end

Loading…
Cancel
Save