My team has been at a standstill on this issue for some time and does not understand where to look next. The spectrum below works correctly when launched individually, however, when we run it in our package through bundle exec ./bin/rspec spec, these two tests fail each time:
- GET / external-products /: id / deals
- GET / external products / search / deals
We have tried many different ways to approach this problem, and I am beginning to suspect something else outside the specified specification. Therefore, I must turn to the gods of the stack and ask that someone there better suited or even better posed this problem.
Rspec Errors:
8) Retailigence Products and Locations GET /external-products/search/deals Search a given region for related deals by query string
Failure/Error: expect(response_body).to have_json_type(Integer).at_path('deals/0/id')
JsonSpec::MissingPath:
Missing JSON path "deals/0/id"
9) Retailigence Products and Locations GET /external-products/:id/deals Search a given region for deals related to a particular product
Failure/Error: expect(response_body).to have_json_type(Integer).at_path('deals/0/id')
JsonSpec::MissingPath:
Missing JSON path "deals/0/id"
Here is our spec_helper.rb:
require 'rubygems'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rspec/rails'
require 'email_spec'
require 'pry'
require 'rspec_api_documentation/dsl'
require 'sunspot/rails/spec_helper'
require 'sunspot_test/rspec'
Dir[Rails.root.join('spec/concerns/**/*.rb')].each { |f| require f }
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
config.order = 'random'
config.seed = srand % 0xFFFF
config.infer_spec_type_from_file_location!
config.use_transactional_fixtures = false
config.infer_base_class_for_anonymous_controllers = false
config.before(:each) { GC.disable }
config.after(:each) { GC.enable }
config.include FactoryGirl::Syntax::Methods
config.include JsonSpec::Helpers
config.include Stubs
config.include LoginHelper
config.include SolrSpecHelper
config.include SunspotMatchers
config.include Devise::TestHelpers, type: :controller
config.before do
Sunspot.session = SunspotMatchers::SunspotSessionSpy.new(Sunspot.session)
end
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end
RspecApiDocumentation.configure do |config|
config.format = :json
end
Functions / external_product_service_spec.rb
resource 'Retailigence Products and Locations', type: :feature, api: true, slow: true, sunspot: true do
before(:all) do
log_in_as_client!
end
let(:id) { '7c381d47-d251-457a-a2d2-930c8993a5fa' }
let(:lat) { 39.74585 }
let(:long) { -104.998929 }
let(:q) { 'whiteboard cleaner' }
get '/external-products/:id' do
parameter :id, 'The external id to search', required: true
parameter :lat, 'The latitude to search', required: true
parameter :long, 'The longitude to search', required: true
parameter :radius, 'The radius, in miles, to search'
example 'Search a given region for products matching a given external product id' do
do_request user_email: @user.email, user_token: @token, timestamp: @timestamp
expect(response_body).to have_json_type(Array).at_path('external_products')
expect(response_body).to have_json_type(String).at_path('external_products/0/id')
expect(response_body).to have_json_type(String).at_path('external_products/0/name')
expect(response_body).to have_json_type(String).at_path('external_products/0/seo_slug')
expect(response_body).to have_json_type(String).at_path('external_products/0/description')
expect(response_body).to have_json_type(Array).at_path('external_products/0/images')
expect(response_body).to have_json_type(String).at_path('external_products/0/price')
end
end
get '/external-products/:id/external-stores' do
parameter :id, 'The external id to search', required: true
parameter :lat, 'The latitude to search', required: true
parameter :long, 'The longitude to search', required: true
parameter :radius, 'The radius, in miles, to search'
example 'Search a given region for stores which have the product matching a given external product id' do
do_request user_email: @user.email, user_token: @token, timestamp: @timestamp
expect(response_body).to have_json_type(Array).at_path('external_stores')
expect(response_body).to have_json_type(String).at_path('external_stores/0/id')
expect(response_body).to have_json_type(String).at_path('external_stores/0/name')
expect(response_body).to have_json_type(String).at_path('external_stores/0/store_logo')
expect(response_body).to have_json_type(Float).at_path('external_stores/0/longitude')
expect(response_body).to have_json_type(Float).at_path('external_stores/0/latitude')
expect(response_body).to have_json_type(Float).at_path('external_stores/0/distance')
expect(response_body).to have_json_type(String).at_path('external_stores/0/city')
expect(response_body).to have_json_type(String).at_path('external_stores/0/address')
expect(response_body).to have_json_type(String).at_path('external_stores/0/zip')
expect(response_body).to have_json_type(String).at_path('external_stores/0/state')
expect(response_body).to have_json_type(String).at_path('external_stores/0/phone_number')
end
end
get '/external-products/search' do
parameter :q, 'The query string to search', required: true
parameter :lat, 'The latitude to search', required: true
parameter :long, 'The longitude to search', required: true
parameter :radius, 'The radius, in miles, to search'
example 'Search a given region for products by query string' do
do_request q: q, user_email: @user.email, user_token: @token, timestamp: @timestamp
expect(response_body).to have_json_type(Array).at_path('external_products')
expect(response_body).to have_json_type(String).at_path('external_products/0/id')
expect(response_body).to have_json_type(String).at_path('external_products/0/name')
expect(response_body).to have_json_type(String).at_path('external_products/0/seo_slug')
expect(response_body).to have_json_type(String).at_path('external_products/0/description')
expect(response_body).to have_json_type(Array).at_path('external_products/0/images')
expect(response_body).to have_json_type(String).at_path('external_products/0/price')
end
end
get '/external-products/:id/deals' do
before(:each) do
solr_setup
store.location.save
store.location.reload
store.location.index!
end
let(:retailer) { create :retailer }
let!(:deal) { create :deal, retailer_id: retailer.id }
let!(:store) do
create :store, retailer_id: retailer.id,
location: (create :location, latitude: lat, longitude: long)
end
let!(:retailigence_retailer) do
create :retailigence_retailers_retailer, retailer_id: retailer.id,
retailigence_retailer_id: '39bfd9a5-f979-4ef1-816b-f9a38093494a'
end
let!(:content_location) do
create :content_location, store_id: store.id,
locatable_id: deal.id, locatable_type: 'Deal'
end
parameter :id, 'The external id to search', required: true
parameter :lat, 'The latitude to search', required: true
parameter :long, 'The longitude to search', required: true
parameter :radius, 'The radius, in miles, to search'
example 'Search a given region for deals related to a particular product' do
do_request id: id, user_email: @user.email, user_token: @token, timestamp: @timestamp
expect(response_body).to have_json_type(Array).at_path('deals')
expect(response_body).to have_json_type(Integer).at_path('deals/0/id')
expect(response_body).to have_json_type(Integer).at_path('deals/0/retailer_id')
expect(response_body).to have_json_type(String).at_path('deals/0/title')
expect(response_body).to have_json_type(String).at_path('deals/0/seo_slug')
expect(response_body).to have_json_type(String).at_path('deals/0/name')
expect(response_body).to have_json_type(String).at_path('deals/0/sort_name')
expect(response_body).to have_json_type(String).at_path('deals/0/description')
expect(response_body).to have_json_type(:boolean).at_path('deals/0/is_local')
expect(response_body).to have_json_type(:boolean).at_path('deals/0/is_featured')
expect(response_body).to have_json_type(Array).at_path('images')
end
end
get '/external-products/search/deals' do
before(:each) do
solr_setup
store.location.save
store.location.reload
store.location.index!
end
let(:retailer) { create :retailer }
let!(:deal) { create :deal, retailer_id: retailer.id }
let!(:store) do
create :store, retailer_id: retailer.id,
location: (create :location, latitude: lat, longitude: long)
end
let!(:retailigence_retailer) do
create :retailigence_retailers_retailer, retailer_id: retailer.id,
retailigence_retailer_id: 'eea1722b-ac89-4cce-95ec-26c2414646d7'
end
let!(:content_location) do
create :content_location, store_id: store.id,
locatable_id: deal.id, locatable_type: 'Deal'
end
parameter :q, 'The query string to search', required: true
parameter :lat, 'The latitude to search', required: true
parameter :long, 'The longitude to search', required: true
parameter :radius, 'The radius, in miles, to search'
example 'Search a given region for related deals by query string' do
do_request q: q, lat: lat, long: long,
user_email: @user.email, user_token: @token, timestamp: @timestamp
expect(response_body).to have_json_type(Array).at_path('deals')
expect(response_body).to have_json_type(Integer).at_path('deals/0/id')
expect(response_body).to have_json_type(Integer).at_path('deals/0/retailer_id')
expect(response_body).to have_json_type(String).at_path('deals/0/title')
expect(response_body).to have_json_type(String).at_path('deals/0/seo_slug')
expect(response_body).to have_json_type(String).at_path('deals/0/name')
expect(response_body).to have_json_type(String).at_path('deals/0/sort_name')
expect(response_body).to have_json_type(String).at_path('deals/0/description')
expect(response_body).to have_json_type(:boolean).at_path('deals/0/is_local')
expect(response_body).to have_json_type(:boolean).at_path('deals/0/is_featured')
expect(response_body).to have_json_type(Array).at_path('images')
end
end
end
_service.rb:
class RetailigenceService
NEGATIVE_KEYWORDS = 'AND !DVD AND !CD AND !"compact disc"'
MAX_QUERY_TIME = 5000
attr_accessor :products, :locations, :params
def self.products(params = {})
fetch(params).products
end
def self.locations(params = {})
fetch_locations(params).locations
end
def self.fetch(params = {})
service = new
service.fetch(params)
service
end
def self.fetch_locations(params = {})
service = new
service.fetch_locations(params)
service
end
def fetch(params = {})
@params = { offset: 0, limit: 25 }.merge(params)
@params.symbolize_keys!
search_result = Retailigence::Product.search(search_params)
@products = search_result.results
build_products
rescue Retailigence::NoResults
@products = []
rescue Retailigence::APIException
@products = []
end
def fetch_locations(params = {})
@params = { offset: 0, limit: 25 }.merge(params)
@params.symbolize_keys!
Rails.logger.debug "[SENDING] #{location_params}"
search_result = Retailigence::Location.search(location_params)
@locations = search_result.results.map { |retailer| retailer.locations }.flatten
build_locations
rescue Retailigence::NoResults
@locations = []
rescue Retailigence::APIException
@locations = []
end
private
def keywords
keywords = @params[:keywords] || ''
keywords << ' ' << NEGATIVE_KEYWORDS
keywords.gsub(/^\ AND\ /, '')
end
def page_size
@params[:limit]
end
def page
(@params[:offset] / @params[:limit]) + 1
end
def location_params
{
userlocation: @params[:userlocation],
requestorid: RETAILIGENCE_REQUESTOR_ID,
productid: @params[:productid],
pagesize: page_size,
page: page,
maxquerytime: MAX_QUERY_TIME,
excludeadultcontent: true
}
end
def search_params
search_params = {
userlocation: @params[:userlocation],
requestorid: RETAILIGENCE_REQUESTOR_ID,
pagesize: page_size, page: page,
maxquerytime: MAX_QUERY_TIME,
excludeadultcontent: true
}
search_params[:keywords] = keywords unless @params[:product_id]
search_params[:productid] = @params[:product_id] if @params[:product_id]
search_params
end
def format_price_prefix(prefix)
prefix = '$' if prefix == 'USD'
prefix
end
def build_products
@products.map! do |p|
p.images ||= []
p.description_long ||= ''
RetailigenceProduct.new(
id: p.id, name: p.name, seo_slug: p.name.slugify,
description: p.description_long,
images: p.images.map { |img| img.link },
price: "#{format_price_prefix p.msrp_currency}#{p.price}",
retailigence_retailer_id: p.location.retailer.id)
end
end
def build_locations
@locations.map! do |l|
RetailigenceLocation.new(
id: l.id, name: l.retailer.name,
latitude: l.latitude, longitude: l.longitude,
phone_number: l.phone, address: l.address.address1,
city: l.address.city, state: l.address.state,
zip: l.address.postal, store_logo: l.retailer.logo,
distance: l.distance.distance)
end
end
end
Sunspot support/sunspot.rb:
$original_sunspot_session = Sunspot.session
Sunspot.session = Sunspot::Rails::StubSessionProxy.new($original_sunspot_session)
module SolrSpecHelper
def solr_setup
unless $sunspot
$sunspot = Sunspot::Rails::Server.new
pid = fork do
STDERR.reopen('/dev/null')
STDOUT.reopen('/dev/null')
$sunspot.run
end
at_exit { Process.kill('TERM', pid) }
sleep 5
end
Sunspot.session = $original_sunspot_session
end
end