In my mailers I generate pdf file for attachments, using header_cookies (pass it from controller). How do I generate header_cookies in the articles_spec.rb test ?
Article Mailer:
class ArticlesMailer < ActionMailer::Base
def send_article(article, header_cookies)
#article = article
#user = #article.user
attachments['article.pdf'] = prepare_pdf_file
mail(
subject: "Article №#{#article.id}",
to: #user.email
)
end
def prepare_pdf_file(header_cookies)
Grover.new(pdf_view_article_url(#article), cookies: header_cookies).to_pdf
end
end
Article Controller
def create
#articele = Article.new(article_params)
if #article.save
ArticlesMailer.send_article(#article, header_cookies).deliver_now
else
render :new
end
end
artilces_spec.rb
RSpec.describe ArticlesMailer, type: :mailer do
describe "send_article" do
let!(:admin) { create(:user, :superadmin) }
let!(:article) { create(:article, user: admin) }
let!(:mail) { ArticlesMailer.send_article(article, header_cookies) }
let(:article_preview_page) { preview_article_path(user_id: admin.id, id: article.id) }
it "renders the headers" do
expect(mail.subject).to eq("Article №#{article.id}")
expect(mail.to).to eq admin.email
end
it "renders the body" do
expect(mail.body.encoded).to match("Hi #{admin.name}")
end
end
end
Did you try creating the header_cookies object and pass it to the send_article call?
let!(:header_cookies) do
[
{ name: 'sign_username', value: 'any#any.com', domain: 'mydomain' },
{ name: '_session_id', value: '9c014df0b699d8dc08d1c472f8cc594c', domain: 'mydomain' }
]
end
Related
I have a rails backend api application integrated with auth0 service that only verifies validity of auth_token received from frontend application. After securing all backend api endpoints all my tests fail with a result "Not Authenticated", which is how it should be. However I cannot figure out how to get through the authentication and to not require it for rspec tests. Here are my classes:
projects_controller_spec.rb
require "rails_helper"
RSpec.describe Api::V1::ProjectsController, :type => :controller do
describe 'GET /api/v1/organizations/1/projects' do
let!(:organization) { create(:organization_with_projects) }
before { get :index, params: { organization_id: organization } }
context 'when authorized' do
it 'should return JSON objects' do
expect(json['projects'].count).to equal(3)
end
it { expect(response).to have_http_status(:ok) }
it { expect(response.content_type).to include('application/json') }
end
describe 'POST /api/v1/organizations/1/projects' do
let!(:organization) { create(:organization) }
let(:project) { organization.projects.first }
before { post :create, params: { organization_id: organization, project: attributes_for(:project) } }
context 'when authorized' do
it { expect(response).to have_http_status(:created) }
it { expect(response.content_type).to include("application/json") }
it { expect(json).to eq(serialized(project)) }
end
end
end
application_controller.rb
class ApplicationController < ActionController::API
include Pundit
include Secured
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found(error)
render json: { error: error.message }, status: :not_found
end
end
concerns/secured.rb
module Secured
extend ActiveSupport::Concern
included do
before_action :authenticate_request!
end
private
def authenticate_request!
# Create user if not existing
pundit_user
auth_token
rescue JWT::VerificationError, JWT::DecodeError
render json: { errors: ['Not Authenticated'] }, status: :unauthorized
end
def http_token
if request.headers['Authorization'].present?
request.headers['Authorization'].split(' ').last
end
end
def auth_token
JsonWebToken.verify(http_token)
end
def pundit_user
User.create_from_token_payload({token: auth_token[0], organization_id:
request.parameters['organization_id']})
end
end
lib/json_web_token.rb
require 'net/http'
require 'uri'
class JsonWebToken
def self.verify(token)
JWT.decode(token, nil,
true, # Verify the signature of this token
algorithm: 'RS256',
iss: 'https://xxx.auth0.com/',
verify_iss: true,
aud: Rails.application.secrets.auth0_api_audience,
verify_aud: true) do |header|
jwks_hash[header['kid']]
end
end
def self.jwks_hash
jwks_raw = Net::HTTP.get URI("https://xxx.auth0.com/.well-known/jwks.json")
jwks_keys = Array(JSON.parse(jwks_raw)['keys'])
Hash[
jwks_keys
.map do |k|
[
k['kid'],
OpenSSL::X509::Certificate.new(
Base64.decode64(k['x5c'].first)
).public_key
]
end
]
end
end
It looks like I found a solution by adding the following line into every controller spec file:
before { allow(controller).to receive(:authenticate_request!).and_return(true) }
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).
I'm writing a Rails API and am stuck trying to write controllers that will test the authentication. For instance, I have in my PostController before_action :authenticate which is
def authenticate
authenticate_or_request_with_http_token do |token, options|
User.find_by(:auth_token => token)
end
end
And this is my PostsController test:
require 'rails_helper'
RSpec.describe Api::PostsController, type: :controller do
let(:valid_attributes) {
{
"title" => "Post title",
"content" => "Post content",
"status" => "published"
}
}
let(:invalid_attributes) {
{
"title" => "",
"content" => "",
"status" => ""
}
}
let(:valid_session) { {} }
before do
params = { session: { email: 'wagner.matos#mac.com', password: 'foobar' } }
SessionsController.create params
#post = Post.new({
title: "Some title",
content: 'Some content',
status: "published"
})
end
it "creates a new post" do
post :create, post: #post
expect(Post.count).to eq(1)
end
end
Yet the above is failing with the following error:
1) Api::PostsController creates a new post
Failure/Error: SessionsController.create params
NoMethodError:
undefined method `create' for SessionsController:Class
Any suggestions?
You can not invoke create action of SessionsController with class. Better you create user object and assign to request.env the same token. like below sample code -
require 'rails_helper'
RSpec.describe Api::PostsController, type: :controller do
before do
user = User.create( :auth_token => 'token' )
request.env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Token.encode_credentials("token")
end
end
I would like to do a specific search using ransack but my test always returns all instances.
My test:
RSpec.describe UsersController, type: :controller do
describe "GET #index" do
context 'ransack search by email' do
let!(:user1) { create(:user, email: 'user1#example.com') }
let!(:user2) { create(:user, email: 'user2#example.com') }
context 'finds specific user' do
before { get :index, q: '2' }
it "should find just one user" do
expect(assigns(:users).first).to eq [user2]
end
it { should respond_with(:success) }
it { should render_template(:index) }
end
My controller:
class UsersController < ApplicationController
def index
#q ||= User.ransack(params[:q])
#users = #q.result(distinct: true)
end
end
What am I doing wrong?
The param q: should be like
q: {email_cont: '2'}
I receive following error:
Output:
1) LabelsController#create label is new creates a new label
Failure/Error: post :create, attributes[:label], format: :json
NoMethodError:
undefined method `save!' for nil:NilClass
# ./app/models/labeling.rb:17:in `update_target'
In Labeling model:
after_create :update_target
def update_target
self.target.save!
end
Test:
require 'spec_helper'
describe LabelsController do
before(:each) do
controller.stub(:current_user).and_return(mock_model(User))
stub_request(:any, "www.example.com").to_return(status: 200)
end
describe "#create" do
context "label is new" do
it "creates a new label" do
attributes = {
label: {
name: "test",
labeling: {
target_type: "Link", target_id: 1
}
}
}
response.status.should == 200
post :create, attributes[:label], format: :json
end
end
end
end
Labeling controller:
def create
label = Label.find_by_name(params[:name])
labeling = label.labelings.build do |lb|
lb.user_id = current_user.id
lb.target_type = params[:labeling][:target_type]
lb.target_id = params[:labeling][:target_id]
end
if labeling.save
render json: {
name: label.name,
id: label.id,
labeling: {
id: labeling.id
}
}
end
end
By the looks of it you don't have a Target with ID 1 on the database, so where you refer to self.target the returned value is nil.
What I'd do in your case is first create a target and then pass its id to the attributes hash:
target = Traget.create!
attributes = {
label: {
name: "test",
labeling: {
target_type: "Link", target_id: target.id
}
}
}
This way you don't need to stub anything.
If you really must stub the method you can use RSpecs any_instance method:
Labeling.any_instance.stub(:update_target).and_return(true)