Initial commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"host" : "localhost",
|
||||
"port" : 5432,
|
||||
"user" : "postgres",
|
||||
"password" : "",
|
||||
"database" : "testgeocoder"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"host" : "localhost",
|
||||
"port" : 5432,
|
||||
"user" : "test",
|
||||
"password" : "",
|
||||
"database" : "test"
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
require 'pg'
|
||||
require 'sequel'
|
||||
require 'json'
|
||||
|
||||
module CartoDB
|
||||
module Importer2
|
||||
module Factories
|
||||
class PGConnection
|
||||
def initialize
|
||||
raise(
|
||||
"Please configure your database settings " +
|
||||
"in spec/factories/database.json"
|
||||
) unless File.exists?(configuration_file)
|
||||
@pg_options = ::JSON.parse(File.read(configuration_file))
|
||||
create_db
|
||||
end #initialize
|
||||
|
||||
def create_db
|
||||
conn = Sequel.postgres(pg_options.reject{|k,v| k == :database})
|
||||
begin
|
||||
conn.run("CREATE DATABASE \"#{ pg_options[:database] }\"
|
||||
WITH TEMPLATE = template_postgis
|
||||
OWNER = #{ pg_options[:user] }
|
||||
ENCODING = 'UTF8'
|
||||
CONNECTION LIMIT=-1")
|
||||
rescue Sequel::DatabaseError => e
|
||||
raise unless e.message =~ /database .* already exists/
|
||||
end
|
||||
begin
|
||||
conn.run("CREATE EXTENSION postgis")
|
||||
rescue Sequel::DatabaseError => e
|
||||
raise unless e.message =~ /extension \"postgis\" already exists/
|
||||
end
|
||||
end
|
||||
|
||||
def connection
|
||||
Sequel.postgres(pg_options)
|
||||
end #connection
|
||||
|
||||
def pg_options
|
||||
Hash[@pg_options.map { |k, v| [k.to_sym, v] }]
|
||||
end #pg_options
|
||||
|
||||
private
|
||||
|
||||
def configuration_file
|
||||
File.join(File.dirname("#{__FILE__}"), 'database.json')
|
||||
end #configuration_file
|
||||
end # PGConnection
|
||||
end # Factories
|
||||
end # Importer2
|
||||
end # CartoDB
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
1,"Netherlands"
|
||||
2,"Spain"
|
||||
3,"The Netherlands"
|
||||
4,"Russia"
|
||||
5,"Japan"
|
||||
6,"France"
|
||||
7,"Italy"
|
||||
8,"Sweden"
|
||||
9," Norway"
|
||||
10,"Canada"
|
||||
11,"Brazil"
|
||||
12,"Spain"
|
||||
13,"Spain"
|
||||
14,"Spain"
|
||||
|
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"address_components": [
|
||||
{
|
||||
"long_name": "1600",
|
||||
"short_name": "1600",
|
||||
"types": [
|
||||
"street_number"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "Amphitheatre Pkwy",
|
||||
"short_name": "Amphitheatre Pkwy",
|
||||
"types": [
|
||||
"route"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "Mountain View",
|
||||
"short_name": "Mountain View",
|
||||
"types": [
|
||||
"locality",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "Santa Clara County",
|
||||
"short_name": "Santa Clara County",
|
||||
"types": [
|
||||
"administrative_area_level_2",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "California",
|
||||
"short_name": "CA",
|
||||
"types": [
|
||||
"administrative_area_level_1",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "United States",
|
||||
"short_name": "US",
|
||||
"types": [
|
||||
"country",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "94043",
|
||||
"short_name": "94043",
|
||||
"types": [
|
||||
"postal_code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA",
|
||||
"geometry": {}
|
||||
}
|
||||
],
|
||||
"status": "ZERO_RESULTS"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"status": "UNKNOWN_ERROR"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"error_message": "dummy error message",
|
||||
"status": "UNKNOWN_ERROR"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"address_components": [
|
||||
{
|
||||
"long_name": "1600",
|
||||
"short_name": "1600",
|
||||
"types": [
|
||||
"street_number"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "Amphitheatre Pkwy",
|
||||
"short_name": "Amphitheatre Pkwy",
|
||||
"types": [
|
||||
"route"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "Mountain View",
|
||||
"short_name": "Mountain View",
|
||||
"types": [
|
||||
"locality",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "Santa Clara County",
|
||||
"short_name": "Santa Clara County",
|
||||
"types": [
|
||||
"administrative_area_level_2",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "California",
|
||||
"short_name": "CA",
|
||||
"types": [
|
||||
"administrative_area_level_1",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "United States",
|
||||
"short_name": "US",
|
||||
"types": [
|
||||
"country",
|
||||
"political"
|
||||
]
|
||||
},
|
||||
{
|
||||
"long_name": "94043",
|
||||
"short_name": "94043",
|
||||
"types": [
|
||||
"postal_code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"formatted_address": "1600 Amphitheatre Parkway, Mountain View, CA 94043, USA",
|
||||
"geometry": {
|
||||
"location": {
|
||||
"lat": 37.4224764,
|
||||
"lng": -122.0842499
|
||||
},
|
||||
"location_type": "ROOFTOP",
|
||||
"viewport": {
|
||||
"northeast": {
|
||||
"lat": 37.4238253802915,
|
||||
"lng": -122.0829009197085
|
||||
},
|
||||
"southwest": {
|
||||
"lat": 37.4211274197085,
|
||||
"lng": -122.0855988802915
|
||||
}
|
||||
}
|
||||
},
|
||||
"place_id": "ChIJ2eUgeAK6j4ARbn5u_wAGqWA",
|
||||
"types": [
|
||||
"street_address"
|
||||
]
|
||||
}
|
||||
],
|
||||
"status": "OK"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
recid,searchtext
|
||||
"Østerbrogade 43, 2100 KØBENHAVN, DNK","Østerbrogade 43, 2100 KØBENHAVN, DNK"
|
||||
"Amster dam, NLD","Amster dam, NLD"
|
||||
"León, ESP","León, ESP"
|
||||
"Villablino, León","Villablino, León"
|
||||
"Leganés , Madrid","Leganés , Madrid"
|
||||
", ESP",", ESP"
|
||||
"Groningen, Netherlands,","Groningen, Netherlands,"
|
||||
"Leganés, Madrid","Leganés, Madrid"
|
||||
"Leganes, Madrid","Leganes, Madrid"
|
||||
|
@@ -0,0 +1,38 @@
|
||||
recid,searchtext
|
||||
5498,"Agadez, Niger"
|
||||
6604,"Tabriz, Iran"
|
||||
206,"Ames, United States"
|
||||
1760,"Apsheronsk, Russia"
|
||||
2012,"Agapa, Russia"
|
||||
2163,"Guasave, Mexico"
|
||||
2225,"Pochutla, Mexico"
|
||||
2633,"Tongling, China"
|
||||
2985,"Anlu, China"
|
||||
3327,"Berekum, Ghana"
|
||||
3452,"Melun, French Republic"
|
||||
3666,"Shahrud, Iran"
|
||||
4081,"Sumy, Ukraine"
|
||||
4167,"Pinheiro, Brazil"
|
||||
4400,"Hinton, Canada"
|
||||
4509,"Salluit, Canada"
|
||||
5005,"Baranavichy, Belarus"
|
||||
5098,"Katoomba, Australia"
|
||||
5217,"Smithton, Australia"
|
||||
5487,"Bilbao, Kingdom of Spain"
|
||||
5826,"La Paz, Mexico"
|
||||
5886,"Wonsan, Korea, North"
|
||||
5997,"Amman, Jordan"
|
||||
6397,"Sekondi, Ghana"
|
||||
6532,"Kirkuk, Iraq"
|
||||
6841,"Dawson, Canada"
|
||||
6870,"Fredericton, Canada"
|
||||
6927,"Santa Fe, Argentina"
|
||||
7129,"Whyalla, Australia"
|
||||
7176,"Sittwe, Myanmar"
|
||||
7266,"Uummannaq, Denmark"
|
||||
887,"My Tho, Vietnam"
|
||||
3232,"Otsu, Japan"
|
||||
3602,"Pori, Finland"
|
||||
5319,"Paramaribo, Suriname"
|
||||
5693,"Tver, Russia"
|
||||
7313,"Oymyakon, Russia"
|
||||
|
@@ -0,0 +1,44 @@
|
||||
"Agadez, Niger",1,1,17.0926609,7.5755601
|
||||
"Agapa, Russia",1,1,53.3041992,59.1366501
|
||||
"Ames, United States",1,5,42.02535,-93.6202
|
||||
"Ames, United States",2,5,30.05339,-94.74227
|
||||
"Ames, United States",3,5,36.24605,-98.18511
|
||||
"Ames, United States",4,5,42.83659,-74.59919
|
||||
"Ames, United States",5,5,41.45263,-96.62508
|
||||
"Amman, Jordan",1,1,31.95181,35.94042
|
||||
"Anlu, China",1,1,31.25582,113.68918
|
||||
"Apsheronsk, Russia",1,1,44.46301,39.72893
|
||||
"Baranavichy, Belarus",1,1,53.13963,26.02236
|
||||
"Berekum, Ghana",1,1,7.45498,-2.58425
|
||||
"Bilbao, Kingdom of Spain",1,1,37.2187881,-5.3498902
|
||||
"Dawson, Canada",1,2,64.06332,-139.43439
|
||||
"Dawson, Canada",2,2,48.72222,-94.48447
|
||||
"Fredericton, Canada",1,1,45.96063,-66.63911
|
||||
"Guasave, Mexico",1,1,25.5706,-108.47102
|
||||
"Hinton, Canada",1,1,53.40095,-117.57697
|
||||
"Katoomba, Australia",1,1,-33.71181,150.31056
|
||||
"Kirkuk, Iraq",1,1,35.4687309,44.3895187
|
||||
"La Paz, Mexico",1,2,19.35847,-98.95884
|
||||
"La Paz, Mexico",2,2,24.15464,-110.31023
|
||||
"My Tho, Vietnam",1,1,10.35595,106.36643
|
||||
"Otsu, Japan",1,1,42.73321,143.59012
|
||||
"Oymyakon, Russia",1,1,63.4606133,142.786026
|
||||
"Paramaribo, Suriname",1,1,5.82031,-55.16542
|
||||
"Pinheiro, Brazil",1,1,-2.52138,-45.0832
|
||||
"Pochutla, Mexico",1,1,15.7434,-96.46694
|
||||
"Pori, Finland",1,1,61.48596,21.79537
|
||||
"Salluit, Canada",1,1,62.20523,-75.64252
|
||||
"Santa Fe, Argentina",1,1,-31.63666,-60.712
|
||||
"Sekondi, Ghana",1,1,4.9340301,-1.70972
|
||||
"Shahrud, Iran",1,2,30.9944096,50.0435715
|
||||
"Shahrud, Iran",2,2,30.1367893,52.5212402
|
||||
"Sittwe, Myanmar",1,1,20.13819,92.88672
|
||||
"Smithton, Australia",1,1,-40.85546,145.12035
|
||||
"Sumy, Ukraine",1,1,50.90787,34.79759
|
||||
"Tabriz, Iran",1,1,38.0797081,46.3001518
|
||||
"Tongling, China",1,1,30.94496,117.81258
|
||||
"Tver, Russia",1,1,56.85238,35.93375
|
||||
"Whyalla, Australia",1,1,-33.03351,137.58416
|
||||
"Wonsan, Korea, North",1,3,34.5083313,126.3166122
|
||||
"Wonsan, Korea, North",2,3,36.1985207,127.0890808
|
||||
"Wonsan, Korea, North",3,3,35.188839,126.947731
|
||||
@@ -0,0 +1,19 @@
|
||||
1,"Amster
|
||||
dam",NLD
|
||||
2,"Groningen, Netherlands",
|
||||
3,León,ESP
|
||||
4,"Østerbrogade 43,
|
||||
2100 KØBENHAVN",DNK
|
||||
5,León,"ESP
|
||||
|
||||
|
||||
"
|
||||
6,Villablino,León
|
||||
7,Villablino,León
|
||||
8,Leganés,Madrid
|
||||
9,Leganés ,Madrid
|
||||
10,,
|
||||
11, ,
|
||||
12,,ESP
|
||||
13, ,ESP
|
||||
14,Leganes, Madrid
|
||||
|
+45
@@ -0,0 +1,45 @@
|
||||
206,1,5,42.02535,-93.6202
|
||||
206,2,5,30.05339,-94.74227
|
||||
206,3,5,36.24605,-98.18511
|
||||
206,4,5,42.83659,-74.59919
|
||||
206,5,5,41.45263,-96.62508
|
||||
887,1,1,10.35855,106.35897
|
||||
1760,1,1,44.46301,39.72893
|
||||
2012,1,1,44.6720581,-73.792572
|
||||
2163,1,1,25.5706,-108.47102
|
||||
2225,1,1,15.7434,-96.46694
|
||||
2633,1,1,30.94496,117.81258
|
||||
2985,1,1,31.25582,113.68918
|
||||
3232,1,1,42.73321,143.59012
|
||||
3327,1,1,7.45498,-2.58425
|
||||
3602,1,1,61.48596,21.79537
|
||||
3666,1,4,30.9944096,50.0435715
|
||||
3666,2,4,30.1367893,52.5212402
|
||||
3666,3,4,30.4089508,57.7633095
|
||||
3666,4,4,38.1983109,44.7774811
|
||||
4081,1,1,50.90787,34.79759
|
||||
4167,1,1,-2.52138,-45.0832
|
||||
4400,1,1,53.40095,-117.57697
|
||||
4509,1,1,62.20523,-75.64252
|
||||
5005,1,1,53.13963,26.02236
|
||||
5098,1,1,-33.71181,150.31056
|
||||
5217,1,1,-40.85546,145.12035
|
||||
5319,1,1,5.82031,-55.16542
|
||||
5487,1,1,37.2187881,-5.3498902
|
||||
5498,1,1,17.0926609,7.5755601
|
||||
5693,1,1,56.85238,35.93375
|
||||
5826,1,2,19.35842,-98.95862
|
||||
5826,2,2,24.15464,-110.31023
|
||||
5886,1,2,34.5083313,126.3166122
|
||||
5886,2,2,36.2474098,128.2595367
|
||||
5997,1,1,31.95181,35.94042
|
||||
6397,1,1,4.9360099,-1.72823
|
||||
6532,1,1,35.4687309,44.3895187
|
||||
6604,1,1,38.0797081,46.3001518
|
||||
6841,1,2,64.06332,-139.43439
|
||||
6841,2,2,48.72225,-94.48446
|
||||
6870,1,1,45.96063,-66.63911
|
||||
6927,1,1,-31.63666,-60.712
|
||||
7129,1,1,-33.03351,137.58416
|
||||
7176,1,1,20.13819,92.88672
|
||||
7313,1,1,63.4606133,142.786026
|
||||
@@ -0,0 +1,73 @@
|
||||
require 'open3'
|
||||
require_relative '../lib/table_geocoder.rb'
|
||||
require_relative 'factories/pg_connection'
|
||||
require_relative '../../../spec/rspec_configuration.rb'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.mock_with :mocha
|
||||
end
|
||||
|
||||
describe CartoDB::GeocoderCache do
|
||||
before do
|
||||
conn = CartoDB::Importer2::Factories::PGConnection.new
|
||||
@db = conn.connection
|
||||
@pg_options = conn.pg_options
|
||||
@table_name = "ne_10m_populated_places_simple"
|
||||
@usage_metrics_stub = stub
|
||||
@log = mock
|
||||
@log.stubs(:append)
|
||||
@log.stubs(:append_and_store)
|
||||
|
||||
# Avoid issues on some machines if postgres system account can't read fixtures subfolder for the COPY
|
||||
filename = 'populated_places_short.csv'
|
||||
stdout, stderr, status = Open3.capture3("cp #{path_to(filename)} /tmp/#{filename}")
|
||||
raise if stderr != ''
|
||||
load_csv "/tmp/#{filename}"
|
||||
end
|
||||
|
||||
after do
|
||||
@db.drop_table @table_name
|
||||
end
|
||||
|
||||
let(:default_params) { {
|
||||
table_name: @table_name,
|
||||
formatter: "concat(name, iso3)",
|
||||
connection: @db, sql_api: { table_name: '' },
|
||||
qualified_table_name: @table_name,
|
||||
usage_metrics: @usage_metrics_stub,
|
||||
log: @log
|
||||
} }
|
||||
|
||||
describe '#get_cache_results' do
|
||||
it "runs the query in batches" do
|
||||
cache = CartoDB::GeocoderCache.new(default_params.merge(batch_size: 5))
|
||||
@db.run("alter table #{@table_name} add column cartodb_georef_status BOOLEAN DEFAULT NULL")
|
||||
cache.expects(:run_query).times(3).returns('')
|
||||
cache.get_cache_results
|
||||
end
|
||||
|
||||
it "honors max_rows" do
|
||||
@db.run("alter table #{@table_name} add column cartodb_georef_status BOOLEAN DEFAULT NULL")
|
||||
cache = CartoDB::GeocoderCache.new(default_params.merge(
|
||||
batch_size: 5,
|
||||
max_rows: 10
|
||||
))
|
||||
cache.expects(:run_query).times(2).returns('')
|
||||
cache.get_cache_results
|
||||
end
|
||||
end #run
|
||||
|
||||
|
||||
|
||||
def path_to(filepath = '')
|
||||
File.expand_path(
|
||||
File.join(File.dirname(__FILE__), "../spec/fixtures/#{filepath}")
|
||||
)
|
||||
end #path_to
|
||||
|
||||
def load_csv(path)
|
||||
@db.run("CREATE TABLE #{@table_name} (the_geom geometry, cartodb_id integer, name text, iso3 text)")
|
||||
@db.run("COPY #{Sequel.lit(@table_name)}(cartodb_id, name, iso3) FROM '#{path}' DELIMITER ',' CSV")
|
||||
end # create_table
|
||||
|
||||
end # CartoDB::GeocoderCache
|
||||
@@ -0,0 +1,77 @@
|
||||
require_relative '../../lib/internal-geocoder/input_type_resolver.rb'
|
||||
require_relative '../../../../spec/rspec_configuration.rb'
|
||||
|
||||
class String
|
||||
# We just need this instead of adding the whole rails thing
|
||||
def present?
|
||||
self && !empty?
|
||||
end
|
||||
end
|
||||
|
||||
describe CartoDB::InternalGeocoder::InputTypeResolver do
|
||||
|
||||
before(:each) do
|
||||
@internal_geocoder = mock
|
||||
@input_type_resolver = CartoDB::InternalGeocoder::InputTypeResolver.new(@internal_geocoder)
|
||||
end
|
||||
|
||||
describe '#type' do
|
||||
|
||||
it 'should return an array characterizing the inputs for <namedplace, country_name, region_name, point>' do
|
||||
@internal_geocoder.stubs('kind').once.returns(:namedplace)
|
||||
@internal_geocoder.stubs('geometry_type').once.returns(:point)
|
||||
@internal_geocoder.stubs('country_column').once.returns(nil)
|
||||
@internal_geocoder.stubs('region_column').once.returns(nil)
|
||||
@internal_geocoder.stubs('regions').once.returns('region')
|
||||
|
||||
@input_type_resolver.type.should == [:namedplace, :text, :text, :point]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#kind' do
|
||||
it 'should return the type of the internal geocoding: namedplace' do
|
||||
@internal_geocoder.stubs('kind').once.returns(:namedplace)
|
||||
@input_type_resolver.kind.should == :namedplace
|
||||
end
|
||||
end
|
||||
|
||||
describe '#geometry_type' do
|
||||
it 'should return the type of the geometry to be geocoded: polygon' do
|
||||
@internal_geocoder.stubs('geometry_type').once.returns(:polygon)
|
||||
@input_type_resolver.geometry_type.should == :polygon
|
||||
end
|
||||
end
|
||||
|
||||
describe '#country_input_type' do
|
||||
it 'should return column if a column was passed' do
|
||||
@internal_geocoder.stubs('country_column').once.returns('any_column_name')
|
||||
@input_type_resolver.country_input_type.should == :column
|
||||
end
|
||||
|
||||
it 'should return column if no column was passed' do
|
||||
@internal_geocoder.stubs('country_column').once.returns(nil)
|
||||
@input_type_resolver.country_input_type.should == :text
|
||||
end
|
||||
end
|
||||
|
||||
describe '#region_input_type' do
|
||||
it 'should return :column if a column is present' do
|
||||
@internal_geocoder.stubs('region_column').once.returns('wadus')
|
||||
@input_type_resolver.region_input_type.should == :column
|
||||
end
|
||||
|
||||
it 'should return :text if regions are present' do
|
||||
@internal_geocoder.stubs('region_column').once.returns(nil)
|
||||
@internal_geocoder.stubs('regions').once.returns('minnesota')
|
||||
@input_type_resolver.region_input_type.should == :text
|
||||
end
|
||||
|
||||
it 'should return nil if no column or text are present' do
|
||||
@internal_geocoder.stubs('region_column').once.returns(nil)
|
||||
@internal_geocoder.stubs('regions').once.returns('')
|
||||
@input_type_resolver.region_input_type.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,118 @@
|
||||
require_relative '../../lib/internal-geocoder/query_generator_factory.rb'
|
||||
require_relative '../../lib/internal-geocoder/abstract_query_generator.rb'
|
||||
require_relative '../../../../spec/rspec_configuration.rb'
|
||||
require 'active_support/core_ext' # Needed for string.blank?
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.mock_with :mocha
|
||||
end
|
||||
|
||||
=begin
|
||||
The class should generate queries to be used by the InternalGeocoder depending on the inputs.
|
||||
|
||||
* Different types of inputs:
|
||||
- kind: namedplace, ipaddress, postalcode, admin0, admin1
|
||||
- country: column, freetext
|
||||
- geometry_type: point, polygon
|
||||
|
||||
* Where queries are needed:
|
||||
- to query the data-services
|
||||
- to import results into table
|
||||
=end
|
||||
|
||||
describe CartoDB::InternalGeocoder::QueryGeneratorFactory do
|
||||
|
||||
before(:each) do
|
||||
@internal_geocoder = mock
|
||||
end
|
||||
|
||||
describe '#dataservices' do
|
||||
|
||||
it 'should return a query template suitable for <namedplace, country_name, region_name, point>' do
|
||||
query_generator = CartoDB::InternalGeocoder::QueryGeneratorFactory.get(@internal_geocoder, [:namedplace, :text, :text, :point])
|
||||
query_generator.should be_a_kind_of CartoDB::InternalGeocoder::AbstractQueryGenerator
|
||||
@internal_geocoder.expects('countries').once.returns(%Q{'Spain'})
|
||||
@internal_geocoder.expects('regions').once.returns(%Q{'Madrid'})
|
||||
search_terms = [{city: %Q{'Madrid'}}, {city: %Q{'Granada'}}]
|
||||
|
||||
query = query_generator.dataservices_query(search_terms)
|
||||
|
||||
query.should == "WITH geo_function AS (SELECT (geocode_namedplace(Array['Madrid','Granada'], 'Madrid', 'Spain')).*) SELECT q, c, a1, geom, success FROM geo_function"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#search_terms_query' do
|
||||
it 'should get the search terms for <namedplace, country_name, region_name, point>' do
|
||||
@internal_geocoder.stubs('column_name').once.returns('city')
|
||||
@internal_geocoder.stubs('qualified_table_name').once.returns(%Q{"public"."untitled_table"})
|
||||
@internal_geocoder.stubs('batch_size').returns(5000)
|
||||
query_generator = CartoDB::InternalGeocoder::QueryGeneratorFactory.get(@internal_geocoder, [:namedplace, :text, :text, :point])
|
||||
|
||||
query = query_generator.search_terms_query(0)
|
||||
|
||||
query.squish.should == 'SELECT DISTINCT(trim(quote_nullable("city"))) AS city FROM "public"."untitled_table" WHERE cartodb_georef_status IS NULL LIMIT 5000 OFFSET 0'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#copy_results_to_table_query' do
|
||||
it 'should generate a suitable query to update geocoded table with temp table' do
|
||||
@internal_geocoder.stubs('qualified_table_name').returns(%Q{"public"."untitled_table"})
|
||||
@internal_geocoder.stubs('temp_table_name').once.returns('any_temp_table')
|
||||
@internal_geocoder.stubs('column_name').once.returns('any_column_name')
|
||||
query_generator = CartoDB::InternalGeocoder::QueryGeneratorFactory.get(@internal_geocoder, [:namedplace, :text, :text, :point])
|
||||
|
||||
query = query_generator.copy_results_to_table_query
|
||||
|
||||
query.squish.should == %Q{
|
||||
UPDATE "public"."untitled_table" AS dest
|
||||
SET the_geom = CASE WHEN orig.cartodb_georef_status THEN orig.the_geom ELSE dest.the_geom END,
|
||||
cartodb_georef_status = orig.cartodb_georef_status
|
||||
FROM any_temp_table AS orig
|
||||
WHERE trim(dest."any_column_name"::text) = trim(orig.geocode_string) AND dest.cartodb_georef_status IS NULL
|
||||
}.squish
|
||||
end
|
||||
end
|
||||
|
||||
describe 'CDB-4269' do
|
||||
it 'should generate a suitable query generator for [:admin1, :column, nil, :polygon]' do
|
||||
@internal_geocoder.stubs('column_name').twice.returns('region_column_name')
|
||||
@internal_geocoder.stubs('qualified_table_name').returns('any_table_name')
|
||||
@internal_geocoder.stubs('country_column').twice.returns('country_column_name')
|
||||
@internal_geocoder.stubs('batch_size').twice.returns(10)
|
||||
@internal_geocoder.stubs('temp_table_name').once.returns('any_temp_tablename')
|
||||
|
||||
query_generator = CartoDB::InternalGeocoder::QueryGeneratorFactory.get(@internal_geocoder, [:admin1, :column, nil, :polygon])
|
||||
|
||||
query_generator.search_terms_query(0).squish.should == %Q{
|
||||
SELECT DISTINCT
|
||||
trim(quote_nullable(region_column_name)) as region,
|
||||
trim(quote_nullable(country_column_name)) as country
|
||||
FROM any_table_name
|
||||
WHERE cartodb_georef_status IS NULL
|
||||
LIMIT 10 OFFSET 0
|
||||
}.squish
|
||||
|
||||
search_terms = [
|
||||
{ region: %Q{'New York'}, country: %Q{'USA'}},
|
||||
{ region: %Q{'Sucumbios'}, country: %Q{'Ecuador'}}
|
||||
]
|
||||
|
||||
query_generator.dataservices_query(search_terms).squish.should == %Q{
|
||||
WITH geo_function AS (SELECT (geocode_admin1_polygons(Array['New York','Sucumbios'], Array['USA','Ecuador'])).*)
|
||||
SELECT q, c, null AS a1, geom, success FROM geo_function
|
||||
}.squish
|
||||
|
||||
query_generator.copy_results_to_table_query.squish.should == %Q{
|
||||
UPDATE any_table_name AS dest
|
||||
SET the_geom = CASE WHEN orig.cartodb_georef_status THEN orig.the_geom ELSE dest.the_geom END,
|
||||
cartodb_georef_status = orig.cartodb_georef_status
|
||||
FROM any_temp_tablename AS orig
|
||||
WHERE trim(dest.region_column_name::text) = trim(orig.geocode_string)
|
||||
AND trim(dest.country_column_name::text) = trim(orig.country)
|
||||
AND dest.cartodb_georef_status IS NULL
|
||||
}.squish
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
require 'open3'
|
||||
require_relative 'factories/pg_connection'
|
||||
require_relative '../lib/internal_geocoder.rb'
|
||||
require_relative '../../../spec/rspec_configuration.rb'
|
||||
|
||||
describe CartoDB::InternalGeocoder::Geocoder do
|
||||
before do
|
||||
conn = CartoDB::Importer2::Factories::PGConnection.new
|
||||
@db = conn.connection
|
||||
@pg_options = conn.pg_options
|
||||
end
|
||||
|
||||
let(:default_params) { { connection: @db, internal: { username: 'geocoding' }, kind: 'admin0', geometry_type: 'polygon' } }
|
||||
|
||||
describe '#download_results' do
|
||||
before do
|
||||
# Avoid issues on some machines if postgres system account can't read fixtures subfolder for the COPY
|
||||
filename = 'adm0.csv'
|
||||
stdout, stderr, status = Open3.capture3("cp #{path_to(filename)} /tmp/#{filename}")
|
||||
raise if stderr != ''
|
||||
load_csv "/tmp/#{filename}", 'adm0'
|
||||
end
|
||||
|
||||
after do
|
||||
@db.drop_table 'adm0'
|
||||
end
|
||||
|
||||
it "generates a csv with geocoded data" do
|
||||
ig = CartoDB::InternalGeocoder.new(default_params.merge(table_name: 'adm0', formatter: 'geo_string'))
|
||||
ig.ensure_georef_status_colummn_valid
|
||||
results = ig.download_results
|
||||
`wc -l #{results} 2>&1`.to_i.should eq 11
|
||||
ig.processed_rows.should eq 11
|
||||
end
|
||||
end #run
|
||||
|
||||
def path_to(filepath = '')
|
||||
File.expand_path(
|
||||
File.join(File.dirname(__FILE__), "../spec/fixtures/#{filepath}")
|
||||
)
|
||||
end #path_to
|
||||
|
||||
def load_csv(path , table_name)
|
||||
@db.run("DROP TABLE IF EXISTS #{table_name}")
|
||||
@db.run("CREATE TABLE #{table_name} (the_geom geometry, cartodb_id integer, geo_string text)")
|
||||
@db.run("COPY #{Sequel.lit(table_name)}(cartodb_id, geo_string) FROM '#{path}' DELIMITER ',' CSV")
|
||||
end # create_table
|
||||
|
||||
end # CartoDB::GeocoderCache
|
||||
@@ -0,0 +1,82 @@
|
||||
require_relative '../factories/pg_connection'
|
||||
require_relative '../../lib/abstract_table_geocoder'
|
||||
require_relative '../../../../spec/rspec_configuration.rb'
|
||||
|
||||
describe CartoDB::AbstractTableGeocoder do
|
||||
|
||||
before(:all) do
|
||||
class ConcreteTableGeocoder < CartoDB::AbstractTableGeocoder; end
|
||||
|
||||
conn = CartoDB::Importer2::Factories::PGConnection.new
|
||||
@db = conn.connection
|
||||
@pg_options = conn.pg_options
|
||||
@table_name = "ne_10m_populated_places_simple"
|
||||
load_csv path_to("populated_places_short.csv")
|
||||
end
|
||||
|
||||
after(:all) do
|
||||
@db.drop_table @table_name
|
||||
end
|
||||
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets the connection timeout to 5 hours' do
|
||||
tg = ConcreteTableGeocoder.new({
|
||||
connection: @db,
|
||||
table_name: @table_name,
|
||||
qualified_table_name: @table_name
|
||||
})
|
||||
timeout = @db.fetch("SHOW statement_timeout").first.fetch(:statement_timeout)
|
||||
timeout.should == '5h'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe '#ensure_georef_status_colummn_valid' do
|
||||
|
||||
before(:each) do
|
||||
@tg = ConcreteTableGeocoder.new({
|
||||
connection: @db,
|
||||
table_name: @table_name,
|
||||
qualified_table_name: @table_name
|
||||
})
|
||||
end
|
||||
|
||||
it 'adds a georef_status_column if it does not exists' do
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
assert_correctness_of_georef_status_column
|
||||
end
|
||||
|
||||
it 'does nothing if the column already exists' do
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
assert_correctness_of_georef_status_column
|
||||
end
|
||||
|
||||
it 'casts its type if the column exists and is not bool' do
|
||||
@db.add_column @table_name, :georef_status_column, :text
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
assert_correctness_of_georef_status_column
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
def assert_correctness_of_georef_status_column
|
||||
georef_status_column = @db.schema(@table_name, reload: true).select {|c| c[0] == :cartodb_georef_status}.first
|
||||
georef_status_column.nil?.should == false
|
||||
georef_status_column[1][:db_type].should == 'boolean'
|
||||
end
|
||||
|
||||
def load_csv(path)
|
||||
@db.run("CREATE TABLE #{@table_name} (the_geom geometry, cartodb_id integer, name text, iso3 text)")
|
||||
@db.run("COPY #{Sequel.lit(@table_name)}(cartodb_id, name, iso3) FROM '#{path}' DELIMITER ',' CSV")
|
||||
end
|
||||
|
||||
def path_to(filepath = '')
|
||||
File.expand_path(
|
||||
File.join(File.dirname(__FILE__), "../fixtures/#{filepath}")
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,246 @@
|
||||
require 'open3'
|
||||
require_relative '../../../lib/gme/table_geocoder'
|
||||
require_relative '../../../../../lib/url_signer'
|
||||
require_relative '../../../lib/gme/exceptions'
|
||||
require_relative '../../factories/pg_connection'
|
||||
require_relative '../../../../../spec/spec_helper.rb'
|
||||
require_relative '../../../../../spec/rspec_configuration.rb'
|
||||
|
||||
describe Carto::Gme::TableGeocoder do
|
||||
before(:all) do
|
||||
connection_stub = mock
|
||||
connection_stub.stubs(:run)
|
||||
@usage_metrics_stub = stub
|
||||
@log = mock
|
||||
@log.stubs(:append)
|
||||
@log.stubs(:append_and_store)
|
||||
@geocoding_model = FactoryGirl.create(:geocoding, kind: 'high-resolution', formatter: '{street}')
|
||||
|
||||
@mandatory_args = {
|
||||
connection: connection_stub,
|
||||
original_formatter: '{mock}',
|
||||
client_id: 'my_client_id',
|
||||
private_key: 'my_private_key',
|
||||
usage_metrics: @usage_metrics_stub,
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model
|
||||
}
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'returns an object that responds to AbstractTableGeocoder interface' do
|
||||
table_geocoder = Carto::Gme::TableGeocoder.new(@mandatory_args)
|
||||
interface_methods = [:ensure_georef_status_colummn_valid,
|
||||
:cancel,
|
||||
:run,
|
||||
:remote_id,
|
||||
:update_geocoding_status,
|
||||
:process_results]
|
||||
interface_methods.each do |method|
|
||||
table_geocoder.respond_to?(method, true).should == true
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an exception if not fed with mandatory arguments' do
|
||||
expect { Carto::Gme::TableGeocoder.new }.to raise_error(ArgumentError)
|
||||
|
||||
@mandatory_args.each do |arg|
|
||||
args_missing_one = @mandatory_args.dup
|
||||
args_missing_one.delete(arg[0])
|
||||
lambda { Carto::Gme::TableGeocoder.new(args_missing_one) }.should raise_error(KeyError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'creates a client with the provided credentials' do
|
||||
gme_client_mock = mock
|
||||
Carto::Gme::Client.expects(:new).with('my_client_id', 'my_private_key').once.returns(gme_client_mock)
|
||||
Carto::Gme::GeocoderClient.expects(:new).with(gme_client_mock).once
|
||||
Carto::Gme::TableGeocoder.new(@mandatory_args)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
before(:each) do
|
||||
Carto::UrlSigner.any_instance.stubs(:sign_url).returns('https://maps.googleapis.com/maps/api/geocode/json')
|
||||
@table_geocoder = Carto::Gme::TableGeocoder.new(@mandatory_args)
|
||||
end
|
||||
|
||||
it "set's the state to 'processing' when it starts" do
|
||||
pending 'actually as a requirement this does not make much sense'
|
||||
end
|
||||
|
||||
it "set's the state to 'completed' when it ends" do
|
||||
# TODO: there's something weird that needs review here
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :total_requests, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :success_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :empty_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :failed_responses, 0)
|
||||
|
||||
@table_geocoder.stubs(:ensure_georef_status_colummn_valid)
|
||||
@table_geocoder.stubs(:data_input_blocks).returns([])
|
||||
|
||||
@table_geocoder.run
|
||||
@geocoding_model.state.should == 'completed'
|
||||
end
|
||||
|
||||
it "if there's an uncontrolled exception, sets the state to 'failed' and raises it" do
|
||||
# TODO: there's something weird that needs review here
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :total_requests, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :success_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :empty_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :failed_responses, 0)
|
||||
|
||||
@table_geocoder.stubs(:ensure_georef_status_aolummn_valid)
|
||||
@table_geocoder.stubs(:data_input_blocks).returns([{ cartodb_id: 1, searchtext: 'dummy text' }])
|
||||
@table_geocoder.stubs(:geocode).raises(StandardError, 'unexpected exception')
|
||||
|
||||
expect { @table_geocoder.run }.to raise_error('unexpected exception')
|
||||
@geocoding_model.state.should == 'failed'
|
||||
end
|
||||
|
||||
it "processes 1 block at a time, keeping track of processed rows in each block" do
|
||||
# TODO: there's something weird that needs review here
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :total_requests, 4)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :success_responses, 4)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :empty_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :failed_responses, 0)
|
||||
|
||||
@table_geocoder.stubs(:ensure_georef_status_colummn_valid)
|
||||
mocked_input = Enumerator.new do |enum|
|
||||
# 2 blocks of 2 rows each as input
|
||||
enum.yield [{ cartodb_id: 1, searchtext: 'dummy text' }, { cartodb_id: 2, searchtext: 'dummy text' }]
|
||||
enum.yield [{ cartodb_id: 3, searchtext: 'dummy text' }, { cartodb_id: 4, searchtext: 'dummy text' }]
|
||||
end
|
||||
@table_geocoder.stubs(:data_input_blocks).returns(mocked_input)
|
||||
response = Typhoeus::Response.new(code: 200, body: read_fixture_file('gme_output_ok.json'))
|
||||
Typhoeus.stub('https://maps.googleapis.com/maps/api/geocode/json', method: :get).and_return(response)
|
||||
@table_geocoder.expects(:update_table).twice
|
||||
|
||||
@table_geocoder.run
|
||||
@table_geocoder.processed_rows.should == 4
|
||||
end
|
||||
|
||||
it "processes empty response" do
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :total_requests, 1)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :success_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :empty_responses, 1)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :failed_responses, 0)
|
||||
|
||||
@table_geocoder.stubs(:ensure_georef_status_colummn_valid)
|
||||
mocked_input = Enumerator.new do |enum|
|
||||
enum.yield [{ cartodb_id: 1, searchtext: 'dummy text' }]
|
||||
end
|
||||
@table_geocoder.stubs(:data_input_blocks).returns(mocked_input)
|
||||
response = Typhoeus::Response.new(code: 200, body: read_fixture_file('gme_output_empty.json'))
|
||||
Typhoeus.stub('https://maps.googleapis.com/maps/api/geocode/json', method: :get).and_return(response)
|
||||
@table_geocoder.expects(:update_table).once
|
||||
|
||||
@table_geocoder.run
|
||||
@table_geocoder.processed_rows.should == 1
|
||||
end
|
||||
|
||||
it "processes error rows response" do
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :total_requests, 1)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :success_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :empty_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :failed_responses, 1)
|
||||
|
||||
@table_geocoder.stubs(:ensure_georef_status_colummn_valid)
|
||||
mocked_input = Enumerator.new do |enum|
|
||||
enum.yield [{ cartodb_id: 1, searchtext: 'dummy text' }]
|
||||
end
|
||||
@table_geocoder.stubs(:data_input_blocks).returns(mocked_input)
|
||||
response = Typhoeus::Response.new(code: 200, body: read_fixture_file('gme_output_error.json'))
|
||||
Typhoeus.stub('https://maps.googleapis.com/maps/api/geocode/json', method: :get).and_return(response)
|
||||
@table_geocoder.expects(:update_table).once
|
||||
|
||||
@table_geocoder.run
|
||||
@table_geocoder.processed_rows.should == 1
|
||||
end
|
||||
|
||||
it "processes error with message response" do
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :total_requests, 1)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :success_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :empty_responses, 0)
|
||||
@usage_metrics_stub.expects(:incr).with(:geocoder_google, :failed_responses, 1)
|
||||
|
||||
@table_geocoder.stubs(:ensure_georef_status_colummn_valid)
|
||||
mocked_input = Enumerator.new do |enum|
|
||||
enum.yield [{ cartodb_id: 1, searchtext: 'dummy text' }]
|
||||
end
|
||||
@table_geocoder.stubs(:data_input_blocks).returns(mocked_input)
|
||||
response = Typhoeus::Response.new(code: 200, body: read_fixture_file('gme_output_error_with_message.json'))
|
||||
Typhoeus.stub('https://maps.googleapis.com/maps/api/geocode/json', method: :get).and_return(response)
|
||||
@table_geocoder.expects(:update_table).once
|
||||
# CartoDB.expects(:notify_error).once
|
||||
|
||||
@table_geocoder.run
|
||||
@table_geocoder.processed_rows.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
describe '#data_input_blocks' do
|
||||
before do
|
||||
conn = CartoDB::Importer2::Factories::PGConnection.new
|
||||
@db = conn.connection
|
||||
@pg_options = conn.pg_options
|
||||
@table_name = "ne_10m_populated_places_simple_#{rand.to_s[2..11]}"
|
||||
|
||||
# Avoid issues on some machines if postgres system account can't read fixtures subfolder for the COPY
|
||||
filename = 'populated_places_short.csv'
|
||||
_stdout, stderr, _status = Open3.capture3("cp #{path_to(filename)} /tmp/#{filename}")
|
||||
raise if stderr != ''
|
||||
load_csv "/tmp/#{filename}"
|
||||
|
||||
params = {
|
||||
connection: @db,
|
||||
table_name: @table_name,
|
||||
qualified_table_name: @table_name,
|
||||
sequel_qualified_table_name: @table_name,
|
||||
original_formatter: "{name}, {iso3}",
|
||||
client_id: 'my_client_id',
|
||||
private_key: 'my_private_key',
|
||||
max_block_size: 4,
|
||||
usage_metrics: @usage_metrics_stub,
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model
|
||||
}
|
||||
|
||||
@table_geocoder = Carto::Gme::TableGeocoder.new(params)
|
||||
end
|
||||
|
||||
after do
|
||||
@db.drop_table @table_name
|
||||
end
|
||||
|
||||
it 'performs (floor(rows / max_block_size) + 1) queries' do
|
||||
rows = @db[@table_name.to_sym].count
|
||||
@table_geocoder.send(:ensure_georef_status_colummn_valid)
|
||||
|
||||
count = 0
|
||||
@table_geocoder.send(:data_input_blocks).each do |data_block|
|
||||
data_block.each do |row|
|
||||
row.merge!(cartodb_georef_status: false)
|
||||
end
|
||||
@table_geocoder.send(:update_table, data_block)
|
||||
count += 1
|
||||
end
|
||||
count.should == (rows / @table_geocoder.max_block_size).floor + 1
|
||||
end
|
||||
end
|
||||
|
||||
def path_to(filepath = '')
|
||||
File.expand_path(
|
||||
File.join(File.dirname(__FILE__), "../../fixtures/#{filepath}")
|
||||
)
|
||||
end
|
||||
|
||||
def read_fixture_file(filename)
|
||||
File.read(path_to(filename))
|
||||
end
|
||||
|
||||
def load_csv(path)
|
||||
@db.run("CREATE TABLE #{@table_name} (the_geom geometry, cartodb_id integer, name text, iso3 text)")
|
||||
@db.run("COPY #{Sequel.lit(@table_name)}(cartodb_id, name, iso3) FROM '#{path}' DELIMITER ',' CSV")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,286 @@
|
||||
require 'open3'
|
||||
require_relative '../lib/table_geocoder'
|
||||
require_relative 'factories/pg_connection'
|
||||
require 'set'
|
||||
require_relative '../../../spec/rspec_configuration'
|
||||
require_relative '../../../spec/spec_helper'
|
||||
|
||||
describe CartoDB::TableGeocoder do
|
||||
let(:default_params) {{
|
||||
app_id: '',
|
||||
token: '',
|
||||
mailto: '',
|
||||
usage_metrics: mock('usage_metrics')
|
||||
}}
|
||||
before do
|
||||
conn = CartoDB::Importer2::Factories::PGConnection.new
|
||||
@db = conn.connection
|
||||
@pg_options = conn.pg_options
|
||||
@table_name = "ne_10m_populated_places_simple_#{rand.to_s[2..11]}"
|
||||
@log = mock
|
||||
@log.stubs(:append)
|
||||
@log.stubs(:append_and_store)
|
||||
@geocoding_model = FactoryGirl.create(:geocoding, kind: 'high-resolution',
|
||||
formatter: '{street}', remote_id: 'dummy_request_id')
|
||||
|
||||
# Avoid issues on some machines if postgres system account can't read fixtures subfolder for the COPY
|
||||
filename = 'populated_places_short.csv'
|
||||
_stdout, stderr, _status = Open3.capture3("cp #{path_to(filename)} /tmp/#{filename}")
|
||||
raise if stderr != ''
|
||||
load_csv "/tmp/#{filename}"
|
||||
end
|
||||
|
||||
after do
|
||||
@db.drop_table @table_name
|
||||
end
|
||||
|
||||
describe '#run' do
|
||||
before do
|
||||
# TODO: Note the coupling of the geocoder object and the metrics
|
||||
success_rows = 10
|
||||
empty_rows = 3
|
||||
failed_rows = 4
|
||||
total = success_rows + empty_rows + failed_rows
|
||||
default_params[:usage_metrics].expects(:incr).with(:geocoder_here, :success_responses, success_rows)
|
||||
default_params[:usage_metrics].expects(:incr).with(:geocoder_here, :empty_responses, empty_rows)
|
||||
default_params[:usage_metrics].expects(:incr).with(:geocoder_here, :failed_responses, failed_rows)
|
||||
default_params[:usage_metrics].expects(:incr).with(:geocoder_here, :total_requests, total)
|
||||
|
||||
@tg = CartoDB::TableGeocoder.new(default_params.merge(table_name: @table_name,
|
||||
qualified_table_name: @table_name,
|
||||
sequel_qualified_table_name: @table_name,
|
||||
formatter: "name, ', ', iso3",
|
||||
connection: @db,
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model,
|
||||
max_rows: 1000))
|
||||
geocoder = mock
|
||||
geocoder.stubs(:upload).returns(true)
|
||||
geocoder.stubs(:request_id).returns('dummy_request_id')
|
||||
geocoder.stubs(:run).returns(true)
|
||||
geocoder.stubs(:status).returns('foo')
|
||||
|
||||
# TODO: Note the coupling of the geocoder object and the metrics
|
||||
geocoder.stubs(:successful_processed_rows).returns(success_rows)
|
||||
geocoder.stubs(:empty_processed_rows).returns(empty_rows)
|
||||
geocoder.stubs(:failed_processed_rows).returns(failed_rows)
|
||||
|
||||
@tg.stubs(:geocoder).returns(geocoder)
|
||||
@tg.stubs(:cache_disabled?).returns(true)
|
||||
@tg.run
|
||||
end
|
||||
|
||||
it "generates a csv file for uploading" do
|
||||
expected = Set.new(File.readlines(path_to('nokia_input.csv')))
|
||||
actual = Set.new(File.readlines("#{@tg.working_dir}/wadus.csv"))
|
||||
actual.should == expected
|
||||
end
|
||||
|
||||
it "assigns a remote_id" do
|
||||
@tg.remote_id.should == 'dummy_request_id'
|
||||
end
|
||||
|
||||
it "holds a db connection with the specified statement timeout" do
|
||||
timeout = @tg.connection.fetch("SHOW statement_timeout").all[0][:statement_timeout]
|
||||
timeout.should == '5h'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#generate_csv' do
|
||||
before do
|
||||
@tg = CartoDB::TableGeocoder.new(default_params.merge(table_name: @table_name,
|
||||
qualified_table_name: @table_name,
|
||||
sequel_qualified_table_name: @table_name,
|
||||
formatter: "name, ', ', iso3",
|
||||
connection: @db,
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model,
|
||||
max_rows: 1000))
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
end
|
||||
|
||||
it "generates a csv file with the correct format" do
|
||||
@tg.send(:mark_rows_to_geocode)
|
||||
@tg.send(:generate_csv)
|
||||
File.readlines("#{@tg.working_dir}/wadus.csv").to_set.should == File.readlines(path_to('nokia_input.csv')).to_set
|
||||
end
|
||||
|
||||
it "honors max_rows" do
|
||||
max_rows = 10
|
||||
@tg.stubs(:max_rows).returns max_rows
|
||||
@tg.send(:mark_rows_to_geocode)
|
||||
@tg.send(:generate_csv)
|
||||
|
||||
# Note there might be duplicate input strings but we send unique inputs to the geocoder api.
|
||||
# Also note the csv file has a header.
|
||||
File.readlines("#{@tg.working_dir}/wadus.csv").count.should <= (max_rows + 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#download_results' do
|
||||
it 'gets the geocoder results' do
|
||||
tg = CartoDB::TableGeocoder.new(table_name: 'a', connection: @db, max_rows: 1000,
|
||||
usage_metrics: nil, log: @log, geocoding_model: @geocoding_model)
|
||||
geocoder = mock
|
||||
geocoder.expects(:result).times(1).returns('a')
|
||||
tg.stubs(:geocoder).returns(geocoder)
|
||||
tg.send(:download_results)
|
||||
tg.result.should == 'a'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deflate_results' do
|
||||
it 'does not raise an error if no results file' do
|
||||
dir = Dir.mktmpdir
|
||||
tg = CartoDB::TableGeocoder.new(table_name: 'a',
|
||||
connection: @db, working_dir: dir, max_rows: 1000,
|
||||
usage_metrics: nil, log: @log, geocoding_model: @geocoding_model)
|
||||
expect { tg.send(:deflate_results) }.to_not raise_error
|
||||
end
|
||||
|
||||
it 'extracts nokia result files' do
|
||||
dir = Dir.mktmpdir
|
||||
`cp #{path_to('kXYkQhuDfxnUSmWFP3dmq6TzTZAzwy4x.zip')} #{dir}`
|
||||
tg = CartoDB::TableGeocoder.new(table_name: 'a',
|
||||
connection: @db, working_dir: dir, max_rows: 1000,
|
||||
usage_metrics: nil, log: @log, geocoding_model: @geocoding_model)
|
||||
tg.send(:deflate_results)
|
||||
filename = 'result_20130919-04-55_6.2.46.1_out.txt'
|
||||
destfile = File.open(File.join(dir, filename))
|
||||
destfile.read.should eq File.open(path_to(filename)).read
|
||||
end
|
||||
end
|
||||
|
||||
describe '#create_temp_table' do
|
||||
it 'raises error if no remote_id' do
|
||||
tg = CartoDB::TableGeocoder.new(table_name: 'a', connection: @db, max_rows: 1000,
|
||||
usage_metrics: nil, log: @log, geocoding_model: @geocoding_model)
|
||||
expect { tg.send(:create_temp_table) }.to raise_error(Sequel::DatabaseError)
|
||||
end
|
||||
|
||||
it 'creates a temporary table' do
|
||||
tg = CartoDB::TableGeocoder.new(table_name: 'a',
|
||||
connection: @db,
|
||||
remote_id: 'geo_HvyxzttLyFhaQ7JKmnrZxdCVySd8N0Ua',
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model,
|
||||
schema: 'public', max_rows: 1000, usage_metrics: nil)
|
||||
tg.send(:drop_temp_table)
|
||||
tg.send(:create_temp_table)
|
||||
@db.fetch("select * from #{tg.send(:temp_table_name)}").all.should eq []
|
||||
end
|
||||
end
|
||||
|
||||
describe '#import_results_to_temp_table' do
|
||||
before do
|
||||
@tg = CartoDB::TableGeocoder.new(table_name: 'a',
|
||||
connection: @db,
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model,
|
||||
remote_id: 'temp_table', schema: 'public', max_rows: 1000, usage_metrics: nil)
|
||||
@tg.send(:create_temp_table)
|
||||
end
|
||||
|
||||
after do
|
||||
@tg.send(:drop_temp_table)
|
||||
end
|
||||
|
||||
it 'loads the Nokia output format to an existing temp table' do
|
||||
@tg.stubs(:deflated_results_path).returns(path_to('nokia_output.txt'))
|
||||
@tg.send(:import_results_to_temp_table)
|
||||
@db.fetch(%{
|
||||
SELECT count(*) FROM #{@tg.send(:temp_table_name)}
|
||||
WHERE displayLatitude IS NOT NULL AND displayLongitude IS NOT NULL
|
||||
}).first[:count].should eq 44
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ensure_georef_status_colummn_valid' do
|
||||
before do
|
||||
table_name = 'wwwwww'
|
||||
@db.run("create table #{table_name} (id integer)")
|
||||
@tg = CartoDB::TableGeocoder.new(table_name: 'wwwwww',
|
||||
qualified_table_name: table_name,
|
||||
sequel_qualified_table_name: table_name,
|
||||
connection: @db,
|
||||
remote_id: 'wadus',
|
||||
max_rows: 1000,
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model,
|
||||
usage_metrics: nil)
|
||||
end
|
||||
|
||||
after do
|
||||
@db.run("drop table wwwwww")
|
||||
end
|
||||
|
||||
it 'adds a boolean cartodb_georef_status column' do
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
@db.run("select cartodb_georef_status from wwwwww").should eq nil
|
||||
end
|
||||
|
||||
it 'does nothing when the column already exists' do
|
||||
@tg.expects(:cast_georef_status_column).once
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
end
|
||||
|
||||
it 'casts cartodb_georef_status to boolean if needed' do
|
||||
@db.run('alter table wwwwww add column cartodb_georef_status text')
|
||||
@tg.send(:ensure_georef_status_colummn_valid)
|
||||
sql_query = "select data_type from information_schema.columns " \
|
||||
"where table_name = 'wwwwww' and column_name = 'cartodb_georef_status'"
|
||||
@db.fetch(sql_query)
|
||||
.first[:data_type].should eq 'boolean'
|
||||
end
|
||||
end
|
||||
|
||||
it "Geocodes a table using the batch geocoder API" do
|
||||
config = YAML.load_file("#{File.dirname(__FILE__)}/../../../config/app_config.yml")["test"]["geocoder"]
|
||||
pending "This is a System E2E test that can be useful for development but not suitable for CI"
|
||||
pending "No Geocoder config found for test environment" unless config['app_id'] != ''
|
||||
config = config.inject({}) do |memo, (k, v)|
|
||||
memo[k.to_sym] = v
|
||||
memo
|
||||
end
|
||||
config[:cache] = config[:cache].inject({}) do |memo, (k, v)|
|
||||
memo[k.to_sym] = v
|
||||
memo
|
||||
end
|
||||
t = CartoDB::TableGeocoder.new(config.merge(table_name: @table_name,
|
||||
qualified_table_name: @table_name,
|
||||
sequel_qualified_table_name: @table_name,
|
||||
formatter: "name, ', ', iso3",
|
||||
connection: @db,
|
||||
schema: 'public',
|
||||
log: @log,
|
||||
geocoding_model: @geocoding_model,
|
||||
max_rows: 1000))
|
||||
t.geocoder.stubs("use_batch_process?").returns(true)
|
||||
|
||||
@db.fetch("select count(*) from #{@table_name} where the_geom is null").first[:count].should eq 14
|
||||
t.run
|
||||
until t.geocoder.status == 'completed' do
|
||||
t.geocoder.update_status
|
||||
puts "#{t.geocoder.status} #{t.geocoder.processed_rows}/#{t.geocoder.total_rows}"
|
||||
sleep(2)
|
||||
end
|
||||
t.process_results
|
||||
t.geocoder.status.should eq 'completed'
|
||||
t.geocoder.processed_rows.to_i.should eq 0
|
||||
t.cache.hits.should eq 10
|
||||
@db.fetch("select count(*) from #{@table_name} where the_geom is null").first[:count].should eq 0
|
||||
@db.fetch("select count(*) from #{@table_name} where cartodb_georef_status is false").first[:count].should eq 0
|
||||
end
|
||||
|
||||
def path_to(filepath = '')
|
||||
File.expand_path(
|
||||
File.join(File.dirname(__FILE__), "../spec/fixtures/#{filepath}")
|
||||
)
|
||||
end
|
||||
|
||||
def load_csv(path)
|
||||
@db.run("CREATE TABLE #{@table_name} (the_geom geometry, cartodb_id integer, name text, iso3 text)")
|
||||
@db.run("COPY #{Sequel.lit(@table_name)}(cartodb_id, name, iso3) FROM '#{path}' DELIMITER ',' CSV")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user