sip on user match mailer

This commit is contained in:
Nuwan 2023-01-04 21:41:57 +05:30
parent bbab6cdb9f
commit 46202a2373
20 changed files with 312 additions and 190 deletions

View File

@ -1,2 +0,0 @@
BUNDLE_GEMFILE=Gemfile.alt
BUNDLE_RUBY

View File

@ -87,7 +87,6 @@ gem 'sendgrid_toolkit', '>= 1.1.1'
gem 'stripe' gem 'stripe'
gem 'zip-codes' gem 'zip-codes'
gem 'elasticsearch' gem 'elasticsearch'
gem 'logging', '1.7.2' gem 'logging', '1.7.2'

View File

@ -0,0 +1,8 @@
class AddSubscribeEmailForUserMatch < ActiveRecord::Migration
def self.up
execute("ALTER TABLE users ADD COLUMN subscribe_email_for_user_match BOOLEAN DEFAULT FALSE;")
end
def self.down
execute("ALTER TABLE users DROP COLUMN subscribe_email_for_user_match;")
end
end

View File

@ -0,0 +1,18 @@
class CreateUserMatchEmailSendings < ActiveRecord::Migration
def self.up
execute(<<-SQL
CREATE TABLE public.user_match_email_sendings (
id character varying(64) DEFAULT public.uuid_generate_v4() PRIMARY KEY NOT NULL,
sent_user_ids varchar[],
total_recipients integer,
created_at timestamp without time zone DEFAULT now() NOT NULL,
completed_at timestamp without time zone
);
SQL
)
end
def self.down
execute("DROP TABLE public.user_match_email_sendings")
end
end

View File

@ -0,0 +1,9 @@
class AddUserMatchEmailSentAt < ActiveRecord::Migration
def self.up
execute("ALTER TABLE users ADD COLUMN user_match_email_sent_at timestamp without time zone;")
end
def self.down
execute("ALTER TABLE users DROP COLUMN user_match_email_sent_at;")
end
end

View File

@ -73,6 +73,7 @@ require "jam_ruby/resque/scheduled/hourly_job"
require "jam_ruby/resque/scheduled/minutely_job" require "jam_ruby/resque/scheduled/minutely_job"
require "jam_ruby/resque/scheduled/daily_session_emailer" require "jam_ruby/resque/scheduled/daily_session_emailer"
require "jam_ruby/resque/scheduled/new_musician_emailer" require "jam_ruby/resque/scheduled/new_musician_emailer"
require "jam_ruby/resque/scheduled/new_musician_match_emailer"
require "jam_ruby/resque/scheduled/music_session_reminder" require "jam_ruby/resque/scheduled/music_session_reminder"
require "jam_ruby/resque/scheduled/music_session_scheduler" require "jam_ruby/resque/scheduled/music_session_scheduler"
require "jam_ruby/resque/scheduled/active_music_session_cleaner" require "jam_ruby/resque/scheduled/active_music_session_cleaner"
@ -116,6 +117,8 @@ require "jam_ruby/lib/desk_multipass"
require "jam_ruby/lib/ip" require "jam_ruby/lib/ip"
require "jam_ruby/lib/subscription_message" require "jam_ruby/lib/subscription_message"
require "jam_ruby/lib/stats.rb" require "jam_ruby/lib/stats.rb"
require "jam_ruby/lib/email_new_musician_match"
require "jam_ruby/lib/musician_filter"
require "jam_ruby/amqp/amqp_connection_manager" require "jam_ruby/amqp/amqp_connection_manager"
require "jam_ruby/database" require "jam_ruby/database"
require "jam_ruby/message_factory" require "jam_ruby/message_factory"

View File

@ -399,6 +399,18 @@ module JamRuby
end end
end end
def new_musicians_match(user, musicians_data)
@user, @musicians_data = user, musicians_data
sendgrid_recipients([user.email])
sendgrid_substitute('@USERID', [user.id])
sendgrid_unique_args :type => "new_musicians_match"
mail(:to => user.email, :subject => EmailNewMusicianMatch.subject) do |format|
format.text
format.html
end
end
#################################### NOTIFICATION EMAILS #################################### #################################### NOTIFICATION EMAILS ####################################
def friend_request(user, msg, friend_request_id) def friend_request(user, msg, friend_request_id)
return if !user.subscribe_email return if !user.subscribe_email

View File

@ -0,0 +1 @@
EMAIL BODY HERE: <%= @musicians_data.inspect -%>

View File

@ -0,0 +1 @@
EMAIL BODY HERE: <%= @musicians_data.inspect -%>

View File

@ -0,0 +1,63 @@
module JamRuby
class EmailNewMusicianMatch
PER_PAGE = 20
JOINED_WITHIN_DAYS = ""
ACTIVE_WITHIN_DAYS = ""
def self.subject
"New musicians with good Internet connections to you have joined JamKazam!"
end
def self.send_new_musicians
params = {
latency_good: true,
latency_fair: true,
latency_high: false,
proficiency_beginner: true,
proficiency_intermediate: true,
proficiency_expert: true,
from_location: false,
joined_within_days: JOINED_WITHIN_DAYS,
active_within_days: ACTIVE_WITHIN_DAYS,
limit: PER_PAGE
}
begin
nextOffset = 0
email_sending = UserMatchEmailSending.most_recent
if email_sending.completed?
email_sending = UserMatchEmailSending.create
end
recipients = User.where(subscribe_email: true, subscribe_email_for_user_match: true).where("users.id NOT IN ?", email_sending.sent_user_ids)
#User.where(email: "nuwan@jamkazam.com").each do |user|
recipients.order("updated_at DESC, last_join_session_at DESC").each do |user|
ip = '127.0.0.1' #TODO: get this from user data?
matched_musician_data = []
while !nextOffset.nil? && nextOffset >= 0 do
params.merge!({ offset: nextOffset })
search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, ip, params)
matched_musician_data << [search, latency_data] if search.results.size > 0
end
if matched_musician_data.size > 0
UserMailer.new_musicians_match(user, matched_musician_data).deliver_now
user.update_column(:user_match_email_sent_at, Time.now)
email_sending.sent_user_ids.push(user.id)
email_sending.save!
end
end
email_sending.total_recipients = email_sending.sent_user_ids.size
email_sending.completed_at = Time.now
email_sending.save!
rescue => exception
raise exception
end
end
end
end

View File

@ -1,5 +1,5 @@
module JamRuby module JamRuby
class MusiciansFilter class MusicianFilter
LATENCY_SCORES = { LATENCY_SCORES = {
good: { label: 'GOOD', min: 0, max: 40 }, good: { label: 'GOOD', min: 0, max: 40 },
@ -9,20 +9,87 @@ module JamRuby
unknown: { label: 'UNKNOWN', min: -2, max: -2 } unknown: { label: 'UNKNOWN', min: -2, max: -2 }
}; };
#ATTN: Rails.application.config is out side to the JamRuby module. Is it a good decision to use def self.filter(user, remote_ip, params)
#application confis here? latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
def self.users_latency_data(user_obj, remote_ip, latency_good, latency_fair, latency_high, filter_opts, offset, limit) latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair])
latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
latency_data = [] offset = [params[:offset].to_i, 0].max
limit = [params[:limit].to_i, 20].max
filter_params = {}
filter_latency_url = "#{Rails.application.config.latency_data_host}/search_users" filter_params.merge!(from_location: params[:from_location] ? '1' : '0')
genres = params[:genres]
filter_params.merge!(genres: genres) if genres
beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner])
intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate])
expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert])
proficiency_levels = []
proficiency_levels.push(1) if beginner
proficiency_levels.push(2) if intermediate
proficiency_levels.push(3) if expert
instruments = params[:instruments]
if instruments && instruments.any? && proficiency_levels.any?
inst = []
instruments.each do |ii|
proficiency_levels.each do |pl|
inst << { id: ii[:value], proficiency: pl}
end
end
filter_params.merge!(instruments: inst)
end
filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
begin
#bm = Benchmark.measure do
result = JamRuby::MusicianFilter.users_latency_data(user, remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit)
latency_data = result[:data]
nextOffset = result[:next]
user_ids = latency_data.map{ |l_data| l_data[:user_id] }
#end
# Bugsnag.notify("search_users_benchmark") do |report|
# report.severity = "info"
# report.add_tab(:benchmark, benchmark: bm.to_s)
# end if Rails.env.production?
sobj = JamRuby::MusicianSearch.user_search_filter(user)
search = sobj.user_search_results(user_ids)
[search, latency_data, nextOffset]
rescue => exception
logger.debug("Latency exception: #{exception.message}")
Bugsnag.notify(exception) do |report|
report.severity = "error"
report.add_tab(:latency, {
params: params,
user_id: user.id,
name: user.name,
url: filter_latency_url,
})
end
raise exception
end
end
def self.users_latency_data(user_obj, remote_ip, latency_good, latency_fair, latency_high, filter_opts, offset, limit)
filter_latency_url = "#{APP_CONFIG.latency_data_host}/search_users"
uri = URI(filter_latency_url) uri = URI(filter_latency_url)
begin begin
http = Net::HTTP.new(uri.host, uri.port) http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if Rails.application.config.latency_data_host.start_with?("https://") http.use_ssl = true if APP_CONFIG.latency_data_host.start_with?("https://")
req = Net::HTTP::Post.new(uri) req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Basic #{Rails.application.config.latency_data_host_auth_code}" req["Authorization"] = "Basic #{APP_CONFIG.latency_data_host_auth_code}"
req["Content-Type"] = "application/json" req["Content-Type"] = "application/json"
req_params = { req_params = {
@ -43,8 +110,6 @@ module JamRuby
req.body = req_params.to_json req.body = req_params.to_json
response = http.request(req) response = http.request(req)
#debugger
if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess) if response.is_a?(Net::HTTPOK) || response.is_a?(Net::HTTPSuccess)
json_body = JSON.parse(response.body) json_body = JSON.parse(response.body)
@ -70,7 +135,7 @@ module JamRuby
} }
}.uniq }.uniq
return {data: latency_data, next: nextOffset} return { data: latency_data, next: nextOffset }
else else
logger.debug("Latency response failed: #{response}") logger.debug("Latency response failed: #{response}")
Bugsnag.notify("LatencyResponseFailed") do |report| Bugsnag.notify("LatencyResponseFailed") do |report|
@ -88,91 +153,6 @@ module JamRuby
rescue => exception rescue => exception
raise exception raise exception
end end
latency_data
end
def self.filter(params)
user_id = params[:user_id]
remote_ip = params[:remote_ip]
raise Exception("This query request should contain user_id and remote_ip") if user_id.blank? || remote_ip.blank?
user = User.find(user_id)
latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair])
latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
offset = [params[:offset].to_i, 0].max
limit = [params[:limit].to_i, 20].max
filter_params = {}
filter_params.merge!(from_location: params[:from_location] ? '1' : '0')
genres = params[:genres]
filter_params.merge!(genres: genres) if genres
beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner])
intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate])
expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert])
proficiency_levels = []
proficiency_levels.push(1) if beginner
proficiency_levels.push(2) if intermediate
proficiency_levels.push(3) if expert
instruments = params[:instruments]
#debugger
if instruments && instruments.any? && proficiency_levels.any?
inst = []
instruments.each do |ii|
proficiency_levels.each do |pl|
inst << { id: ii[:value], proficiency: pl}
end
end
filter_params.merge!(instruments: inst)
end
filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
@latency_data = []
begin
#bm = Benchmark.measure do
result = users_latency_data(user, remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit)
@latency_data = result[:data]
@nextOffset = result[:next]
user_ids = @latency_data.map{ |l_data| l_data[:user_id] }
#end
# Bugsnag.notify("search_users_benchmark") do |report|
# report.severity = "info"
# report.add_tab(:benchmark, benchmark: bm.to_s)
# end if Rails.env.production?
sobj = MusicianSearch.user_search_filter(user)
#@search = sobj.search_results_page(filter_params, page, user_ids)
#debugger
@search = sobj.user_search_results(user_ids)
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter'
rescue => exception
logger.debug("Latency exception: #{exception.message}")
Bugsnag.notify(exception) do |report|
report.severity = "error"
report.add_tab(:latency, {
params: params,
user_id: user.id,
name: user.name,
url: filter_latency_url,
})
end
render json: {}, status: 500
end
end end
end end
end end

View File

@ -1,9 +0,0 @@
module JamRuby
class EmailNewMusicianMatch
def self.send_new_musician
end
end
end

View File

@ -0,0 +1,11 @@
module JamRuby
class UserMatchEmailSending < ActiveRecord::Base
def completed?
!completed_at.nil?
end
def self.most_recent
UserMatchEmailSending.order(created_at: :desc).first
end
end
end

View File

@ -7,7 +7,7 @@ module JamRuby
def self.perform def self.perform
@@log.debug("waking up") @@log.debug("waking up")
EmailNewMusicianMatch.send_new_musician EmailNewMusicianMatch.send_new_musicians
@@log.debug("done") @@log.debug("done")
end end

View File

@ -4,7 +4,7 @@
require "spec_helper" require "spec_helper"
describe "RenderMailers", :slow => true do describe "RenderMailers" do
let(:user) { FactoryGirl.create(:user) } let(:user) { FactoryGirl.create(:user) }
let(:school) {FactoryGirl.create(:school, education:true)} let(:school) {FactoryGirl.create(:school, education:true)}
@ -16,7 +16,7 @@ describe "RenderMailers", :slow => true do
describe "UserMailer emails" do describe "UserMailer emails" do
before(:each) do before(:each) do
user.update_email = "my_new_email@jamkazam.com" #user.update_email = "my_new_email@jamkazam.com"
UserMailer.deliveries.clear UserMailer.deliveries.clear
end end
@ -53,6 +53,32 @@ describe "RenderMailers", :slow => true do
it { @filename="friend_request"; UserMailer.friend_request(user, 'So and so has sent you a friend request.', friend_request.id).deliver_now } it { @filename="friend_request"; UserMailer.friend_request(user, 'So and so has sent you a friend request.', friend_request.id).deliver_now }
end end
# describe "sending about new musicians with good latency to the user", focus: true do
# let(:user) { User.find_by(email: "nuwan@jamkazam.com") }
# let(:params) {
# {latency_good: true,
# latency_fair: true,
# latency_high: false,
# proficiency_beginner: true,
# proficiency_intermediate: true,
# proficiency_expert: true,
# from_location: false,
# joined_within_days: "",
# active_within_days: "",
# limit: 20,
# offset: 0}
# }
# let(:ip){ "127.0.0.1" }
# it{
# @filename="new_musicians_match"
# search, latency_data, nextOffset = JamRuby::MusicianFilter.filter(user, ip, params)
# matched_musician_data = []
# matched_musician_data << [search, latency_data]
# UserMailer.new_musicians_match(user, matched_musician_data).deliver_now
# }
# end
=begin =begin
describe "student/teacher" do describe "student/teacher" do
let(:teacher) { u = FactoryGirl.create(:teacher); u.user } let(:teacher) { u = FactoryGirl.create(:teacher); u.user }

View File

@ -182,7 +182,7 @@ end
# gem 'rack-timeout' # gem 'rack-timeout'
#end #end
gem 'ffi', '1.12.0' gem 'ffi', '1.14.0'
group :development, :test do group :development, :test do
gem 'rspec-rails' #, require: "rspec/rails" #, '2.14.2' gem 'rspec-rails' #, require: "rspec/rails" #, '2.14.2'
@ -250,4 +250,4 @@ end
group :package do group :package do
#gem 'fpm' #gem 'fpm'
end end

View File

@ -459,7 +459,9 @@ GEM
mime-types (3.3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2021.0212) mime-types-data (3.2021.0212)
mimemagic (0.3.5) mimemagic (0.4.3)
nokogiri (~> 1)
rake
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.14.3) minitest (5.14.3)

View File

@ -5,7 +5,7 @@ class ApiSearchController < ApiController
respond_to :json respond_to :json
include LatencyHelper #include LatencyHelper
def index def index
if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i if 1 == params[Search::PARAM_MUSICIAN].to_i || 1 == params[Search::PARAM_BAND].to_i
@ -95,94 +95,96 @@ class ApiSearchController < ApiController
end end
end end
def filter # def filter
latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good]) # latency_good = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_good])
latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair]) # latency_fair = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_fair])
latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high]) # latency_high = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:latency_high])
offset = [params[:offset].to_i, 0].max # offset = [params[:offset].to_i, 0].max
limit = [params[:limit].to_i, 20].max # limit = [params[:limit].to_i, 20].max
filter_params = {} # filter_params = {}
filter_params.merge!(from_location: params[:from_location] ? '1' : '0') # filter_params.merge!(from_location: params[:from_location] ? '1' : '0')
genres = params[:genres] # genres = params[:genres]
filter_params.merge!(genres: genres) if genres # filter_params.merge!(genres: genres) if genres
# if genres && genres.any? # # if genres && genres.any?
# genres.map!{|genre| {id: genre} } # # genres.map!{|genre| {id: genre} }
# filter_params.merge!(genres: genres) # # filter_params.merge!(genres: genres)
# end # # end
beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner]) # beginner = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_beginner])
intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate]) # intermediate = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_intermediate])
expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert]) # expert = ActiveRecord::Type::Boolean.new.type_cast_from_user(params[:proficiency_expert])
proficiency_levels = [] # proficiency_levels = []
proficiency_levels.push(1) if beginner # proficiency_levels.push(1) if beginner
proficiency_levels.push(2) if intermediate # proficiency_levels.push(2) if intermediate
proficiency_levels.push(3) if expert # proficiency_levels.push(3) if expert
instruments = params[:instruments] # instruments = params[:instruments]
#debugger # #debugger
if instruments && instruments.any? && proficiency_levels.any? # if instruments && instruments.any? && proficiency_levels.any?
inst = [] # inst = []
instruments.each do |ii| # instruments.each do |ii|
proficiency_levels.each do |pl| # proficiency_levels.each do |pl|
inst << { id: ii[:value], proficiency: pl} # inst << { id: ii[:value], proficiency: pl}
end # end
end # end
filter_params.merge!(instruments: inst) # filter_params.merge!(instruments: inst)
end # end
filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank? # filter_params.merge!(joined_within_days: params[:joined_within_days]) unless params[:joined_within_days].blank?
filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank? # filter_params.merge!(active_within_days: params[:active_within_days]) unless params[:active_within_days].blank?
@latency_data = [] # @latency_data = []
begin # begin
#bm = Benchmark.measure do # #bm = Benchmark.measure do
result = users_latency_data(latency_good, latency_fair, latency_high, filter_params, offset, limit) # result = JamRuby::MusicianFilter.users_latency_data(current_user, request.remote_ip, latency_good, latency_fair, latency_high, filter_params, offset, limit)
@latency_data = result[:data] # @latency_data = result[:data]
@nextOffset = result[:next] # @nextOffset = result[:next]
user_ids = @latency_data.map{ |l_data| l_data[:user_id] } # user_ids = @latency_data.map{ |l_data| l_data[:user_id] }
#end # #end
# Bugsnag.notify("search_users_benchmark") do |report| # # Bugsnag.notify("search_users_benchmark") do |report|
# report.severity = "info" # # report.severity = "info"
# report.add_tab(:benchmark, benchmark: bm.to_s) # # report.add_tab(:benchmark, benchmark: bm.to_s)
# end if Rails.env.production? # # end if Rails.env.production?
sobj = MusicianSearch.user_search_filter(current_user) # sobj = MusicianSearch.user_search_filter(current_user)
#@search = sobj.search_results_page(filter_params, page, user_ids) # #@search = sobj.search_results_page(filter_params, page, user_ids)
#debugger # debugger
@search = sobj.user_search_results(user_ids) # @search = sobj.user_search_results(user_ids)
# respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter'
# rescue => exception
# logger.debug("Latency exception: #{exception.message}")
# Bugsnag.notify(exception) do |report|
# report.severity = "error"
# report.add_tab(:latency, {
# params: params,
# user_id: current_user.id,
# name: current_user.name,
# url: filter_latency_url,
# })
# end
# render json: {}, status: 500
# end
# end
def filter
begin
@search, @latency_data, @nextOffset = JamRuby::MusicianFilter.filter(current_user, request.remote_ip, params)
respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter' respond_with @search, responder: ApiResponder, status: 201, template: 'api_search/filter'
rescue
rescue => exception
logger.debug("Latency exception: #{exception.message}")
Bugsnag.notify(exception) do |report|
report.severity = "error"
report.add_tab(:latency, {
params: params,
user_id: current_user.id,
name: current_user.name,
url: filter_latency_url,
})
end
render json: {}, status: 500 render json: {}, status: 500
end end
end end
private
def filter_latency_url
"#{Rails.application.config.latency_data_host}/search_users"
end
end end

View File

@ -86,7 +86,6 @@ module LatencyHelper
raise exception raise exception
end end
latency_data
end end
end end

View File

@ -133,9 +133,8 @@ describe "Musician Filter API", type: :request do
expect(JSON.parse(response.body)["musicians"].size).to eq(8) expect(JSON.parse(response.body)["musicians"].size).to eq(8)
end end
it "filter musicians when no latency option is selected" do it "filter musicians when no latency option is selected", focus: true do
post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false } post '/api/filter.json', { latency_good: false, latency_fair: false, latency_high: false }
expect(JSON.parse(response.body)["musicians"].size).to eq(8) expect(JSON.parse(response.body)["musicians"].size).to eq(8)
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]).not_to eq(nil) expect(JSON.parse(response.body)["musicians"][0]["latency_data"]).not_to eq(nil)
expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["audio_latency"]).not_to eq(nil) expect(JSON.parse(response.body)["musicians"][0]["latency_data"]["audio_latency"]).not_to eq(nil)