require 'active_support/core_ext' require_relative '../../lib/datasources' require_relative '../doubles/user' include CartoDB::Datasources describe Url::ArcGIS do before(:all) do @url = 'http://myserver/arcgis/rest/services/MyFakeService/featurename' @invalid_url = 'http://myserver/mysite/rest/myfakefolder/MyFakeService/featurename' @user = CartoDB::Datasources::Doubles::User.new end before(:each) do Typhoeus::Expectation.clear end describe '#set_data_from' do it 'tests preparing the correct url from the one given from the UI' do invalid_1 = 'http://myserver/services/MyFakeService/featurename/MapServer' invalid_2 = 'myserver/services/MyFakeService/featurename/MapServer' test1 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer' test2 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/' test3 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0' test4 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0?' test5 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0?q=blablabla' test6 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer?q=blablabla' valid_map = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer' valid_map_trailing_slash = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/' valid_layer = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/MapServer/0' # Should be treated as ok test7 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/2314/' valid_7 = 'http://myserver/arcgis/rest/services/MyFakeService/featurename/2314/' arcgis = Url::ArcGIS.get_new(@user) expect { arcgis.send(:sanitize_id, invalid_1) }.to raise_error InvalidInputDataError expect { arcgis.send(:sanitize_id, invalid_2) }.to raise_error InvalidInputDataError arcgis.send(:sanitize_id, test1).should eq valid_map arcgis.send(:sanitize_id, test2).should eq valid_map_trailing_slash arcgis.send(:sanitize_id, test3).should eq valid_layer arcgis.send(:sanitize_id, test4).should eq valid_layer arcgis.send(:sanitize_id, test5).should eq valid_layer arcgis.send(:sanitize_id, test6).should eq valid_map arcgis.send(:sanitize_id, test7).should eq valid_7 end end describe '#get_resource_metadata' do it 'tests error scenarios' do arcgis = Url::ArcGIS.get_new(@user) sub_id = '0' # 'general http error (non-200)' Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do Typhoeus::Response.new( code: 400, headers: { 'Content-Type' => 'application/json' }, body: '' ) end expect { arcgis.get_resource_metadata(@url) }.to raise_error DataDownloadError # Stub layers request (so now works) Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_layers.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(::JSON.parse(body)) ) end layers_data = arcgis.get_resource_metadata(@url) layers_data_expected = { id: @url, subresources: [{ id: "#{@url}/#{sub_id}", title: 'first layer' }] } layers_data.should eq layers_data_expected # 'fields' part Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json")) body = ::JSON.parse(body) body.delete('fields') Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(body) ) end expect { arcgis.send(:get_subresource_metadata, @url, sub_id) }.to raise_error ResponseError # Another required field Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json")) body = ::JSON.parse(body) body.delete('name') Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(body) ) end expect { arcgis.send(:get_subresource_metadata, @url, sub_id) }.to raise_error ResponseError # Invalid ArcGIS version Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json")) body = ::JSON.parse(body) body['currentVersion'] = 9.0 Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(body) ) end expect { arcgis.send(:get_subresource_metadata, @url, sub_id) }.to raise_error InvalidServiceError # Invalid ArcGIS URL expect { arcgis.send(:get_resource_metadata, @invalid_url) }.to raise_error InvalidInputDataError end it 'tests metadata retrieval' do arcgis = Url::ArcGIS.get_new(@user) # Stub layers request (so now works) Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_layers.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(::JSON.parse(body)) ) end Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0/) do |request| accept = (request.options[:headers]||{})['Accept'] || 'application/json' format = accept.split(',').first body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => format }, body: body ) end expected_metadata = { :arcgis_version=>10.22, :name=>"Test Feature", :description=>"Sample metadata payload", :type=>"Feature Layer", :geometry_type=>"esriGeometryPolygon", :copyright=>"CartoDB", :fields=>[ { :name => "OBJECTID", :type => "esriFieldTypeOID" }, { :name => "NAME", :type => "esriFieldTypeString" } ], :max_records_per_query=>1000, :supported_formats=>["JSON", "AMF"], :advanced_queries_supported=>true } expected_metadata_response = { id: @url + '/0', title: 'Test Feature', url: nil, service: Url::ArcGIS::DATASOURCE_NAME, checksum: nil, size: 0, filename: 'test_feature.json' } # Multi-resource scenario already tested above response = arcgis.get_resource_metadata(@url + '/0') response.nil?.should be false arcgis.metadata.should eq expected_metadata response.should eq expected_metadata_response end end describe '#get_resource' do it 'tests the get_ids_list() private method with error scenarios' do arcgis = Url::ArcGIS.get_new(@user) id = arcgis.send(:sanitize_id, @url) # 'general http error (non-200)' Typhoeus.stub(/\/arcgis\/rest\//) do Typhoeus::Response.new( code: 400, headers: { 'Content-Type' => 'application/json' }, body: '' ) end expect { arcgis.send(:get_ids_list, id) }.to raise_error DataDownloadError # 'objectIds' not present Typhoeus::Expectation.clear Typhoeus.stub(/\/arcgis\/rest\//) do body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list.json")) body = ::JSON.parse(body) body.delete('objectIds') Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(body) ) end expect { arcgis.send(:get_ids_list, id) }.to raise_error ResponseError # 'objectIds' empty Typhoeus::Expectation.clear Typhoeus.stub(/\/arcgis\/rest\//) do body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list.json")) body = ::JSON.parse(body) body['objectIds'] = [] Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(body) ) end expect { arcgis.send(:get_ids_list, id) }.to raise_error ResponseError end it 'tests the get_ids_list() private method' do arcgis = Url::ArcGIS.get_new(@user) id = arcgis.send(:sanitize_id, @url) Typhoeus.stub(/\/arcgis\/rest\//) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: body ) end expected_ids = [1,2,3,4,5,6,7,8,9,10] respose_ids = arcgis.send(:get_ids_list, id) respose_ids.nil?.should be false respose_ids.should eq expected_ids end it 'tests the get_ids_list() private method on out-of-order ids' do arcgis = Url::ArcGIS.get_new(@user) id = arcgis.send(:sanitize_id, @url) Typhoeus.stub(/\/arcgis\/rest\//) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_unordered_ids_list.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: body ) end expected_ids = [1,2,3,4,5,6,7,8,9,10] respose_ids = arcgis.send(:get_ids_list, id) respose_ids.nil?.should be false respose_ids.should eq expected_ids end it 'tests the get_by_ids() private method with error scenarios' do arcgis = Url::ArcGIS.get_new(@user) id = arcgis.send(:sanitize_id, @url) # Empty ids expect { arcgis.send(:get_by_ids, id, [], [{ key: 'value' }]) }.to raise_error InvalidInputDataError # Empty fields expect { arcgis.send(:get_by_ids, id, [1], []) }.to raise_error InvalidInputDataError # 'general http error (non-200)' Typhoeus.stub(/\/arcgis\/rest\//) do Typhoeus::Response.new( code: 400, headers: { 'Content-Type' => 'application/json' }, body: '' ) end expect { arcgis.send(:get_by_ids, id, [1], [{ key: 'value' }]) }.to raise_error DataDownloadError end it 'tests the get_by_ids() private method' do arcgis = Url::ArcGIS.get_new(@user) id = arcgis.send(:sanitize_id, @url) Typhoeus.stub(/\/arcgis\/rest\//) do body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_data_01.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: body ) end expected_response_data = { geometryType: "esriGeometryPolygon", spatialReference: { "wkid" => 4326, "latestWkid" => 4326 }, fields: [ { "name" => "OBJECTID", "type" => "esriFieldTypeOID", "alias" => "OBJECTID" }, { "name" => "WDPAID", "type" => "esriFieldTypeInteger", "alias" => "WDPAID" }, { "name" => "NAME", "type" => "esriFieldTypeString", "alias" => "NAME", "length" => 254 } ], features: [ {"attributes"=>{"OBJECTID"=>1, "NAME"=>"Name of object 1"}, "geometry"=>{"fake"=>"geom"}}, {"attributes"=>{"OBJECTID"=>2, "NAME"=>"Name of object 2"}, "geometry"=>{"fake"=>"geom"}}, {"attributes"=>{"OBJECTID"=>3, "NAME"=>"Name of object 3"}, "geometry"=>{"fake"=>"geom"}} ] } ids_to_retrieve = [1,2,3] # WDPAID also present, but left on purpose untouched fields_to_retrieve = [{ name: 'OBJECTID', type: 'esriFieldTypeOID' }, { name: 'NAME', type: 'esriFieldTypeString' }] response_data = arcgis.send(:get_by_ids, id, ids_to_retrieve, fields_to_retrieve) response_data.nil?.should eq false response_data[:features].length.should eq 3 response_data.should eq expected_response_data end it 'tests retrieval of data' do arcgis = Url::ArcGIS.get_new(@user) feature_names = [] # Layers request Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/layers/) do |request| body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_layers.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(::JSON.parse(body)) ) end # Metadata of a layer Typhoeus.stub(/\/arcgis\/rest\/services\/MyFakeService\/featurename\/0\?f=json/) do body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_metadata_minimal.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: body ) end # IDs list of a layer Typhoeus.stub(/\/arcgis\/rest\/(.*)query\?where=/) do body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_ids_list_01.json")) Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: body ) end Typhoeus.stub(/\/arcgis\/rest\/(.*)query$/) do |response| if response.options[:body][:objectIds].to_i == 1 # First item fetch of a layer body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_data_01.json")) body = ::JSON.parse(body) feature_names.push body['features'][0]['attributes']['NAME'] body['features'] = [ body['features'][0] ] else # Remaining items fetch of a layer, will not use :objectIds body = File.read(File.join(File.dirname(__FILE__), "../fixtures/arcgis_data_01.json")) body = ::JSON.parse(body) feature_names.push body['features'][1]['attributes']['NAME'] feature_names.push body['features'][2]['attributes']['NAME'] body['features'] = [ body['features'][1], body['features'][2] ] end Typhoeus::Response.new( code: 200, headers: { 'Content-Type' => 'application/json' }, body: ::JSON.dump(body) ) end # 1) Retrieve lists of layers metadata = arcgis.get_resource_metadata(@url) # 2) Retrieve metadata of a specific layer (doesn't adds much, but to replicate flow) item_metadata = arcgis.get_resource_metadata(metadata[:subresources].first[:id]) item_metadata.nil?.should eq false # No more checks as will fail later if missing something spatial_ref_expectation = {"wkid"=>4326, "latestWkid"=>4326} initial_stream_data = arcgis.initial_stream(item_metadata[:id]) initial_stream_data.nil?.should eq false initial_stream_data = ::JSON.parse(initial_stream_data) initial_stream_data['geometryType'].should eq 'esriGeometryPolygon' initial_stream_data['spatialReference'].should eq spatial_ref_expectation initial_stream_data['fields'].count.should eq 3 initial_stream_data['fields'][0]['name'].should eq 'OBJECTID' initial_stream_data['fields'][1]['name'].should eq 'WDPAID' initial_stream_data['fields'][2]['name'].should eq 'NAME' initial_stream_data['features'].count.should eq 1 initial_stream_data['features'][0]['attributes'].nil?.should eq false initial_stream_data['features'][0]['attributes']['NAME'].should eq feature_names[0] initial_stream_data['features'][0]['geometry'].nil?.should eq false streamed_data = arcgis.stream_resource(item_metadata[:id]) streamed_data.nil?.should eq false streamed_data = ::JSON.parse(streamed_data) streamed_data['geometryType'].should eq 'esriGeometryPolygon' streamed_data['spatialReference'].should eq spatial_ref_expectation streamed_data['fields'].count.should eq 3 streamed_data['fields'][0]['name'].should eq 'OBJECTID' streamed_data['fields'][1]['name'].should eq 'WDPAID' streamed_data['fields'][2]['name'].should eq 'NAME' streamed_data['features'].count.should eq 2 streamed_data['features'][0]['attributes']['NAME'].should eq feature_names[1] streamed_data['features'][1]['attributes']['NAME'].should eq feature_names[2] end end end