In my rails controller, I am trying to write a test for the method search_backups. The issue I am having is that devices_ids_from_elastic = ConfigTextSearch.search search_term returns nothing and so the test runs .map on devices_ids_from_elastic which is incorrectly a #<String:0x007f8a90b67ca0>.
How can I stub out devices_ids_from_elastic.map() to fix this issue?
Failures:
1) ReportsController access control allows architects to search backups
Failure/Error: post 'search_backups'
NoMethodError:
undefined method `each_pair' for #<String:0x007f8a90b67ca0>
# ./app/controllers/reports_controller.rb:19:in `map'
# ./app/controllers/reports_controller.rb:19:in `elastic_mongo_lookup'
# ./app/controllers/reports_controller.rb:32:in `search_backups'
# ./spec/controllers/reports_controller_spec.rb:123:in `block (3 levels) in <top (required)>'
test:
describe "controller method test" do
before do
allow(CSV).to receive(:generate).and_return("1234, blah")
stub_request(:get, "http://localhost:9200/mongo_index/config_files/_search?q=").
with(:headers => {'Expect'=>'', 'User-Agent'=>'Faraday v0.9.1'}).
to_return(:status => 200, :body => '{lots of json stuff in here }', :headers => {})
it "allows users to search backup log files" do
reports = double(ReportsController)
reports.stub(:map).and_return("help")
post 'search_backups'
end
controller:
search_backups
def elastic_mongo_lookup(search_term)
devices_ids_from_elastic = ConfigTextSearch.search search_term
device_ids = devices_ids_from_elastic.map { |device| device._source.device_id }
csv_string = CSV.generate do |csv|
Device.where(:_id.in => device_ids).each do |device|
csv << [device.logical_name, device.primary_ip]
end
end
return csv_string
end
def search_backups
authorize! :read, :custom_report
csv_string = elastic_mongo_lookup params[:search_term]
if csv_string.blank?
flash[:notice] = "No results were found"
redirect_to reports_path
else
render text: "DeviceID, primary_ip\n" + csv_string
end
end#search_backups
Try this: allow(ConfigTextSearch).to receive(:search).with({}).and_return([])
Related
Hi I was testing by rspec for rails app now. I have no idea to fix this problem.
here down below. anyone can fix this?
1) Items::AddToBasketsController Post #create succuss
Failure/Error: #item = Item.find(params[:item_id])
ActiveRecord::RecordNotFound:
Couldn't find Item with 'id'=category=nigiri&discription=test+test&img=%23%3CFile%3A0x00007fc54e9bfc38%3E&name=test1&price=301
# ./app/controllers/items/add_to_baskets_controller.rb:4:in `create'
# ./spec/controller/add_to_baskets_controller_spec.rb:43:in `block (4 levels) in <top (required)>'
# ./spec/controller/add_to_baskets_controller_spec.rb:42:in `block (3 levels) in <top (required)>'
add_to_baskets_controller_spec.rb
require 'rails_helper'
RSpec.describe Items::AddToBasketsController, type: :controller do
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let(:first_item) { create(:first_item, admin_id: admin.id) }
let(:item_params) { attributes_for(:first_item) }
before do
login_user
end
describe "Post #create" do
it "succuss" do
basket = user.prepare_basket
expect do
post :create, params: { item_id: FactoryBot.attributes_for(:first_item) }
end.to change(basket.basket_items, :count).by(1)
end
end
end
add_to_baskets_controller.rb
class Items::AddToBasketsController < Items::ApplicationController
def create
basket = current_user.prepare_basket
#item = Item.find(params[:item_id])
basket.basket_items.create!(item_id: #item.id)
flash[:success] = "your item in basket"
redirect_to baskets_path
end
end
On the line:
post :create, params: { item_id: FactoryBot.attributes_for(:first_item) }
Your saying that the parameter item_id should be FactoryBot.attributes_for(:first_item), which is a hash with all the attributes of first_item.
Moreover, to get the ID, you'll need to first create the object. In other word:
item_id: FactoryBot.create(:first_item).id
Or simply, using your let:
item_id: first_item.id
I'm working with redmine and I've installed a plugin for managing mails.
When I try to send a mail I obtain the following error
[ActiveJob] [ActionMailer::DeliveryJob] [uuid] Error performing ActionMailer::DeliveryJob (Job ID: uuid) from Async(mailers) in 41.81ms: NoMethodError (undefined method `each' for #<User:id>):
This is the file that gives me the error
module EncryptMails
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method :mail_without_relocation, :mail
alias_method :mail, :mail_with_relocation
end
end
module InstanceMethods
# action names to be processed by this plugin
def actions
[
'attachments_added',
'document_added',
'issue_add',
'issue_edit',
'message_posted',
'news_added',
'news_comment_added',
'wiki_content_added',
'wiki_content_updated'
]
end
# dispatched mail method
def mail_with_relocation(headers={}, &block)
# pass unchanged, if action does not match or plugin is inactive
act = Setting.plugin_openpgp['activation']
return mail_without_relocation(headers, &block) if
act == 'none' or not actions.include? #_action_name or
(act == 'project' and not project.try('module_enabled?', 'openpgp'))
# relocate recipients
recipients = relocate_recipients(headers)
header = #_message.header.to_s
# render and deliver encrypted mail
reset(header)
m = mail_without_relocation prepare_headers(
headers, recipients[:encrypted], encrypt = true, sign = true
) do |format|
format.text
end
m.deliver
# render and deliver filtered mail
reset(header)
tpl = #_action_name + '.filtered'
m = mail_without_relocation prepare_headers(
headers, recipients[:filtered], encrypt = false, sign = true
) do |format|
format.text { render tpl }
format.html { render tpl } unless Setting.plain_text_mail?
end
m.deliver
# render unchanged mail (deliverd by calling method)
reset(header)
m = mail_without_relocation prepare_headers(
headers, recipients[:unchanged], encrypt = false, sign = false
) do |format|
format.text
format.html unless Setting.plain_text_mail?
end
m
end
# get project dependent on action and object
def project
case #_action_name
when 'attachments_added'
#attachments.first.project
when 'document_added'
#document.project
when 'issue_add', 'issue_edit'
#issue.project
when 'message_posted'
#message.project
when 'news_added', 'news_comment_added'
#news.project
when 'wiki_content_added', 'wiki_content_updated'
#wiki_content.project
else
nil
end
end
# relocates reciepients (to, cc) of message
def relocate_recipients(headers)
# hash to be returned
recipients = {
:encrypted => {:to => [], :cc => []},
:blocked => {:to => [], :cc => []},
:filtered => {:to => [], :cc => []},
:unchanged => {:to => [], :cc => []},
:lost => {:to => [], :cc => []}
}
# relocation of reciepients
[:to, :cc].each do |field|
headers[field].each do |user|
# encrypted
unless Pgpkey.find_by(user_id: user.id).nil?
recipients[:encrypted][field].push user and next
end
# unencrypted
case Setting.plugin_openpgp['unencrypted_mails']
when 'blocked'
recipients[:blocked][field].push user
when 'filtered'
recipients[:filtered][field].push user
when 'unchanged'
recipients[:unchanged][field].push user
else
recipients[:lost][field].push user
end
end unless headers[field].blank?
end
recipients
end
# resets the mail for sending mails multiple times
def reset(header)
#_mail_was_called = false
#_message = Mail.new
#_message.header header
end
# prepares the headers for different configurations
def prepare_headers(headers, recipients, encrypt, sign)
h = headers.deep_dup
# headers for recipients
h[:to] = recipients[:to]
h[:cc] = recipients[:cc]
# headers for gpg
h[:gpg] = {
encrypt: false,
sign: false
}
# headers for encryption
if encrypt
h[:gpg][:encrypt] = true
# add pgp keys for emails
h[:gpg][:keys] = {}
[:to, :cc].each do |field|
h[field].each do |user|
user_key = Pgpkey.find_by user_id: user.id
unless user_key.nil?
h[:gpg][:keys][user.mail] = user_key.fpr
end
end unless h[field].blank?
end
end
# headers for signature
if sign
server_key = Pgpkey.find_by(:user_id => 0)
unless server_key.nil?
h[:gpg][:sign] = true
h[:gpg][:sign_as] = Setting['mail_from']
h[:gpg][:password] = server_key.secret
end
end
h
end
end
end
The stack of the log tells me that the error is in row 109
# relocation of reciepients
[:to, :cc].each do |field|
I'm not an expert of ruby and rails but I've seen that each is a method of a Ruby array, not a custom one, so I don't understand why I obtain the error.
What I am doing wrong and how can I fix this error?
Are you sure the problem isn't on this line?
h[field].each do |user|
The field there would be :to or :cc so h[field] could be a User instance.
If you want to allow h[:to] or h[:cc] to be a single User or an array of Users, then wrap it in Array():
# relocation of reciepients
[:to, :cc].each do |field|
Array(headers[field]).each do |user|
#^^^^
I'd also move that trailing unless so it doesn't get missed, maybe something like this:
%i[to cc].select { |field| headers[field].present? }.each do |field|
Array(headers[filed]).each do |user|
#...
end
end
Background: I've got an after_action callback in my controller, which takes the string address, processes it and stores longitude and latitude in corresponding fields. I want to test this.
This SO question, as well as this article only consider update methods, but at least, they are quite clear, because I've already got an object to work with.
So my question is - how to find this newly created record? This SO question led me to this code:
require 'rails_helper'
RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do
context "POST methods" do
describe "#edit and #create" do
it "encodes and stores lang/lot correctly" do
post :create, general_setting: FactoryGirl.attributes_for(:general_setting)
expect(assigns(:general_setting).long).to eq(37.568021)
# expect(general_setting.long).to eq(37.568021)
# expect(general_setting.lat).to eq(55.805553)
end
end
end
end
But using the code in the answer, I get this error:
Failure/Error: expect(assigns(:general_setting).long).to eq(37.568021)
NoMethodError:
undefined method `long' for nil:NilClass
Update #1:
This is my new controller spec code:
RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do
context 'POST methods' do
before(:each) do
allow(subject).to receive(:set_long_lat)
end
describe 'post create' do
before(:each) do
post :create, params: { general_setting: FactoryGirl.attributes_for(:general_setting) }
end
it "saves the record with valid attributes" do
expect{subject}.to change{GeneralSetting.count}.by(1)
end
it 'calls :set_long_lat' do
expect(subject).to have_received(:set_long_lat)
end
end
end
describe '#set_long_lat' do
# spec for method
end
end
Update #2:
Here is my controller code:
class Admin::Settings::GeneralSettingsController < AdminController
include CrudConcern
before_action :find_general_setting, only: [:edit, :destroy, :update, :set_long_lat]
after_action :set_long_lat
def index
#general_settings = GeneralSetting.all
end
def new
#general_setting = GeneralSetting.new
# Билдим для того, что бы было видно сразу одно поле и пользователь не должен
# кликать на "добавить телефон"
#general_setting.phones.build
#general_setting.opening_hours.build
end
def edit
# Тоже самое, что и с нью - если телефонов нет вообще, то показываем одно пустое поле
if #general_setting.phones.blank?
#general_setting.phones.build
end
if #general_setting.opening_hours.blank?
#general_setting.opening_hours.build
end
end
def create
#general_setting = GeneralSetting.new(general_setting_params)
create_helper(#general_setting, "edit_admin_settings_general_setting_path")
end
def destroy
destroy_helper(#general_setting, "admin_settings_general_settings_path")
end
def update
# debug
# #general_setting.update(language: create_hash(params[:general_setting][:language]))
#general_setting.language = create_hash(params[:general_setting][:language])
update_helper(#general_setting, "edit_admin_settings_general_setting_path", general_setting_params)
end
private
def set_long_lat
geocoder = Geocoder.new
data = geocoder.encode!(#general_setting.address)
#general_setting.update!(long: data[0], lat: data[1])
end
def find_general_setting
#general_setting = GeneralSetting.find(params[:id])
end
def general_setting_params
params.require(:general_setting).permit(GeneralSetting.attribute_names.map(&:to_sym).push(
phones_attributes: [:id, :value, :_destroy, :general_setting_id ]).push(
opening_hours_attributes: [:id, :title, :value, :_destroy, :deneral_setting_id]) )
end
def create_hash(params)
language_hash = Hash.new
params.each do |param|
language_hash[param.to_sym] = param.to_sym
end
return language_hash
end
end
(If it helps - I've got a lot of similar crud-actions, that is why I've put them all in a concern controller)
module CrudConcern
extend ActiveSupport::Concern
include Language
included do
helper_method :create_helper, :update_helper, :destroy_helper, :get_locales
end
def get_locales
#remaining_locales = Language.get_remaining_locales
end
def create_helper(object, path)
if object.save!
respond_to do |format|
format.html {
redirect_to send(path, object)
flash[:primary] = "Well done!"
}
end
else
render :new
flash[:danger] = "Something not quite right"
end
#remaining_locales = Language.get_remaining_locales
end
def update_helper(object, path, params)
if object.update!(params)
respond_to do |format|
format.html {
redirect_to send(path, object)
flash[:primary] = "Well done!"
}
end
else
render :edit
flash[:danger] = "Something's not quite right"
end
end
def destroy_helper(object, path)
if object.destroy
respond_to do |format|
format.html {
redirect_to send(path)
flash[:primary] = "Well done"
}
end
else
render :index
flash[:danger] = "Something's not quite right"
end
end
end
Update #3
It's not the ideal solution, but, somehow, controller tests just won't work. I've moved my callback into the model and updated my general_setting_spec test.
class GeneralSetting < ApplicationRecord
after_save :set_long_lat
validates :url, presence: true
private
def set_long_lat
geocoder = Geocoder.new
data = geocoder.encode(self.address)
self.update_column(:long, data[0])
self.update_column(:lat, data[1])
end
end
My tests now:
RSpec.describe GeneralSetting, type: :model do
let (:regular) { FactoryGirl.build(:general_setting) }
describe "checking other validations" do
it "is invalid with no url" do
expect{
invalid.save
}.not_to change(GeneralSetting, :count)
end
it 'autofills the longitude' do
expect{ regular.save }.to change{ regular.long }.from(nil).to(37.568021)
end
it 'autofills the latitude' do
expect{ regular.save }.to change{ regular.lat }.from(nil).to(55.805078)
end
end
end
I would test expectation that controller calls method specified in after_action and make a separate test for that method.
Something like:
context 'POST methods' do
before(:each) do
allow(subject).to receive(:method_from_callback)
end
describe 'post create' do
before(:each) do
post :create, params: { general_setting: attributes_for(:general_setting) }
end
it 'calls :method_from_callback' do
expect(subject).to have_received(:method_from_callback)
end
end
end
describe '#method_from_callback' do
# spec for method
end
Be sure to use your method name instead of :method_from_callback pay attention that I've used rspec 3.5 syntax (wrapped request request parameters into params).
In rails I am writing a test for a controller method search_backups with Rspec:
def elastic_mongo_lookup(search_term)
devices_ids_from_elastic = ConfigTextSearch.search search_term
puts devices_ids_from_elastic
device_ids = devices_ids_from_elastic.map { |device| device._source.device_id }
csv_string = CSV.generate do |csv|
Device.where(:_id.in => device_ids).each do |device|
csv << [device.logical_name, device.primary_ip]
end
end
return csv_string
end
def search_backups
authorize! :read, :custom_report
csv_string = elastic_mongo_lookup params[:search_term]
if csv_string.blank?
flash[:notice] = "No results were found"
redirect_to reports_path
else
render text: "DeviceID, primary_ip\n" + csv_string
end
end#search_backups
describe "try controller method" do
let(:reports_controller) { ReportsController.new }
before do
allow(CSV).to receive(:generate).and_return("1234", "blah")
allow(ConfigTextSearch).to receive(:search).and_return(['"hits": [ {"_source":{"device_id":"54afe167b3000006"}]'])
allow(:devices_ids_from_elastic).to receive(:map).and_return('54afe167b3000006')
stub_request(:get, "http://localhost:9200/mongo_index/config_files/_search?q=").
with(:headers => {'Expect'=>'', 'User-Agent'=>'Faraday v0.9.1'}).
to_return(:status => 200, :body => '', :headers => {})
end
it "allows people to search backups" do
reports = double(ReportsController)
post 'search_backups'
end
end
The issue is that ConfigTextSearch.search search_term returns a elasticsearch ORM object.. which means I can't stub it because the .map() method on devices_ids_from_elastic.map is unique with it's nested _source method.
How could I bypass elastic_mongo_lookup entirely and just return a mocked csv_string to search_backups?
In an RSpec controller test, controller is defined as the controller under test. You can therefore achieve what you're asking about with the following:
allow(controller).to receive(:elastic_mongo_lookup).and_return('whatever string you choose')
Basically I'm stumped as to why this particular test is failing. The page works fine when I go in and test it out by hand but the test keeps failing. First off, here's the error.
2) Setlist Pages Edit page adding a song
Failure/Error: click_button submit
ActionController::RoutingError:
No route matches {:action=>"edit", :controller=>"setlists", :id=>nil}
# ./app/controllers/allocations_controller.rb:15:in `create'
# (eval):2:in `click_button'
# ./spec/requests/setlist_pages_spec.rb:79:in `block (4 levels) in <top (required)>'
I realise that the id is set to nil but I know that when the test initially visits the page it renders because tests for content pass. I'm not sure why it points to the create controller when I'm testing the edit action as well? The test is shown below:
before do
#setlist = Setlist.create(date: Date.today, morning: true)
end
...
describe "Edit page" do
let(:admin) { FactoryGirl.create(:admin) }
let(:submit){ "Add Song" }
before do
#secondSong = FactoryGirl.create(:song)
sign_in admin
visit edit_setlist_path(#setlist)
end
it{ should have_content("Edit a Setlist")}
describe "adding a song" do
before do
select("#{#secondSong.title} by #{#secondSong.artist}", from: 'Songs')
click_button submit
end
it{ should have_selector('div.alert.alert-success')}
it "should create a new allocation" do
expect{click_button submit}.to change(Allocation, :count).by(1)
end
end # end adding a song
end # end edit test
Controller code as requested:
def create
#setlist = Setlist.new(params[:setlist])
if #setlist.save
#success!
flash[:success] = "Setlist saved"
##setlist.allocations.build produce invalid allocation with nil id
redirect_to setlist_path(#setlist)
else
#FAIL!
render 'new'
end
end
def edit
#songs= Song.search(params[:search])
##songs = Song.all(order: 'title')
#setlist = Setlist.find(params[:id])
#allocations = #setlist.allocations
#allocation = Allocation.new
#selections = Song.all.collect {|s| [ [s.title, s.artist].join(" by "), s.id ]}
end
def update
#setlist = Setlist.find(params[:id])
#selections = Song.all.collect {|s| [ [s.title, s.artist].join(" by "), s.id] }
#allocations = #setlist.allocations
#allocation = Allocation.new
#Allocation parameters
#allocation.song_id = params[:allocation][:song_id]
#allocation.setlist_id = #setlist.id
#allocation.songPosition = #setlist.songs.count + 1
if #setlist.update_attributes(params[:setlist])
if #allocation.save
flash[:success] = "SETLIST SAVED!"
redirect_to edit_setlist_path(#setlist)
else
flash[:fail] = "Sorry there was an error adding songs to the setlist"
render 'edit'
end
else
flash[:fail] = "Invalid Set"
render 'edit'
end
end
Any pointers would be much appreciated!