This is the topic resolver. I'm trying to update a field in learning_path table each time this query is executed for a student.
class Resolvers::Topic < GraphQL::Schema::Resolver
type Types::TopicType, null: true
description 'Returns the topic information'
argument :title, String, required: true
def resolve(title:)
user = context[:current_user]
ability = Ability.for(user)
topic = Topic.find_by(title: title)
if %w[student teacher admin].include?(user&.role) && ability.can?(:read, Topic)
if user&.role == 'student'
user.userable.learning_paths.find_by(subject_id: topic.subject_id).update(visited_at: DateTime.now)
end
if %w[student teacher].include?(user&.role) && !user&.userable&.subjects&.include?(topic.subject)
raise GraphQL::ExecutionError, 'This topic is not avaiable for you.'
end
topic
else
raise GraphQL::ExecutionError, 'You can't access this information.'
end
end
end
It works but the rspec is failling with this error:
1) LmsApiSchema topic when there's a current user returns an error if the student is not subscribe
Failure/Error: user.userable.learning_paths.find_by(subject_id: topic.subject_id).update(visited_at: DateTime.now)
NoMethodError:
undefined method `update' for nil:NilClass
topic rspec test that fails in testing:
context 'when there\'s a current user' do
let!(:student) { create(:student) }
let!(:user) { create(:user, userable: student) }
let!(:subject_a) { create(:subject) }
let!(:topic) { create(:topic, subject_id: subject_a.id) }
let!(:question) { create(:question, topic_id: topic.id) }
before do
create(:option, question_id: question.id)
prepare_context({ current_user: user })
end
it 'returns an error if the student is not subscribe' do
expect(graphql!['errors'][0]['message']).to eq('This topic is not avaiable for you.')
end
Just worked out. Added the & before setting the method for avoiding the nil error.
Figured it out thanks to this
user.userable.learning_paths.find_by(subject_id: topic.subject_id)&.update(visited_at: DateTime.now)
Related
I'm trying to test a Mailer but I can't figure why I'm not reaching a nested model attribute in mailer
let's see, I have this models
class CandidateChallenge < ApplicationRecord
has_one :candidate_evaluation
def coach
self.candidate_evaluation.user
end
end
class CandidateEvaluation < ApplicationRecord
belongs_to :candidate_challenge
end
In mailer I have
def notify_coach(candidate_challenge_id)
#candidate_challenge = CandidateChallenge.find(candidate_challenge_id)
#user = #candidate_challenge.user
#coach = #candidate_challenge.coach # <= throws nil
mail(
to: #coach.email, # <= this is nil too
cc: #user.email,
)
end
And in my test I have
let!(:user) { create(:coach_user) }
let!(:candidate_challenge) {
create(:candidate_challenge, user: user)
}
let(:job) { NotificationMailer.new }
# Test passing
it "should have a coach" do
expect(candidate_challenge.coach.email).to eq(candidate_challenge.candidate_evaluation.user.email)
end
# Test failing
it "job is created" do
ActiveJob::Base.queue_adapter = :test
expect {
job.notify_coach(candidate_challenge.id).deliver_now
}.to change {
ActiveJob::Base.queue_adapter.enqueued_jobs.size
}.by_at_least(1)
end
Error
Failure/Error: to: #coach.email,
NoMethodError:
undefined method `email' for nil:NilClass
With pry
asking from model throws nil
CandidateChallenge.last.candidate_evaluation
=> #<CandidateEvaluation:0x0000000111b25ab8
id: 25,
candidate_challenge_id: 13,
user_id: nil>
CandidateChallenge.find(candidate_challenge.id).candidate_evaluation
=> #<CandidateEvaluation:0x000000012223a668
id: 25,
candidate_challenge_id: 13,
user_id: nil>
from factorybot gives expected result
candidate_challenge.coach.email
=> "test26#example.com"
candidate_challenge.candidate_evaluation.user
=> #<User id: 26, email: "test26#example.com" ...>
candidate_challenge.candidate_evaluation
=> #<CandidateEvaluation:0x00000001218357f8
id: 26,
candidate_challenge_id: 13,
user_id: 26>
So, I'm wondering why if I ask in mailer from class model CandidateChallenge.find(candidate_challenge_id) user_id is nil?, how can I test notify_coach method with rspec without losing this relationship?
EDIT:
I forgot to mention that let!(:user) { create(:user) } is a coach user
I am new to testing in rails, I am trying to pass the test as below where it kept throwing back errors as
/scooties_coupon POST Coupon /scooties_coupons/:coupon_id with coupon coupon_id redeemed API_KEY returns redeemed id
Failure/Error: expect(json['coupon']).to eq(scooties_coupon.coupon)
expected: "6B2F5"
got: nil
(compared using ==)
Here is the code for the test I am running:
context 'with coupon coupon_id redeemed API_KEY' do
subject { post "/api/scooties_coupons/#{scooties_coupon.id}", headers: headers }
it "returns redeemed id" do
subject
expect(json['coupon']).to eq(scooties_coupon.coupon)
expect(json['redeemed']).to eq(scooties_coupon.redeemed)
end
end
my factory code:
FactoryBot.define do
factory :scooties_coupon do
coupon { "43MDA" }
redeemed { false }
email { "email#host.com" }
trait :redeemed do
redeemed { true }
end
end
end
here's the model:
class ScootiesCoupon < ApplicationRecord
has_paper_trail
before_create :set_coupon_code
after_update :send_activecamp_email_scootiescoupon
validates :email, presence: true, format: { with: /\A[^#\s]+#([^#.\s]+\.)+[^#.\s]+\z/ }
def update_redeemed(redeemed = true, email = nil)
email == self.email if email.present? || self.email.present?
self.created_at + self.expires_in.seconds < DateTime.now if self.expires_in.present?
self.update_attribute(:redeemed, redeemed)
end
private
def set_coupon_code
loop do
self.coupon = gen_coupon
break unless coupon_exists?
end
end
def coupon_exists?
self.class.exists?(coupon: coupon)
end
def gen_coupon
return SecureRandom.hex[0..4].upcase
end
end
I've tried to get rid of the validation on email but it doesn't seem to b e a major problem here, Could you give me a little idea of any other way to solve this problem?
I am using Pundit for authorization in my application with rspec for testing.
require 'rails_helper'
describe SubjectPolicy do
subject { described_class.new(user, subject) }
let(:subject) { Subject.create }
context 'is an administrator' do
let(:role) { Role.create(role_name: 'admin') }
let(:user) { User.create(role_id: role.id) }
it { is_expected.to permit_actions([:index]) }
end
context 'is a teacher' do
let(:role) { Role.create(role_name: 'teacher') }
let(:user) { User.create(role_id: role.id) }
it { is_expected.to forbid_actions([:index]) }
end
end
When running the test for this spec test I receive the following error.
Failure/Error: it { is_expected.to permit_actions([:index]) }
NoMethodError: undefined method 'index?' for #<Subject:0x007fdcc1f70fd0>
There is a route for this index action and it is in my subjects_controller.
The code in the subject policy is very simple.
class SubjectPolicy < ApplicationPolicy
def index?
#user.is_admin?
end
end
Here is the index action in my subjects_controller
def index
#subjects = Subject.all
authorize #subjects
end
I am able to create subjects as an admin, and it does in fact block non-admins from accessing the index. But I am confused as to why this test would fail. I have this policy spec set up just like others and they are passing just fine. Any idea?
Tested in browser and works fine. Test error says "expected result to have changed from 0 to 1, but did not change". Is this a factory issue or rspec issue? Why is it not changing?
Error:
Failures:
1) ShortLinksController Short links controller Clicking a short link increments the click counter by 1
Failure/Error: expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
expected result to have changed from 0 to 1, but did not change
# ./spec/controllers/short_links_controller_spec.rb:34:in `block (4 levels) in <top (required)>'
Rspec:
it "increments the click counter by 1" do
short_link = create(:short_link)
expect{ get :url_dispatch, { id: short_link.short_link } }.to change{short_link.click_counter}.from(0).to(1)
end
Controller:
def url_dispatch
id = params[:id]
record = ShortLink.where(["short_link = ?", id]).first
if record.update(click_counter: record.click_counter + 1)
redirect_to record.redirect_to
else
render '/not_found'
end
end
Factory:
FactoryGirl.define do
factory :short_link do
redirect_to "http://google.com"
title "This is the google page"
short_link "xGh7u"
click_counter 0
owner Owner.create!(first_name: "Bob", last_name: "Diller", email: "bdiller#example.com")
end
end
per Fab's request, here is how I'm currently working around the issue.
context 'save invocations' do
before(:each) do
#org = create(:organization)
user = create(:user, organization: #org, is_admin: true)
sign_in user
end
it 'valid scenario' do
user2 = create(:user, organization: #org, is_admin: false)
put :update, id: user2, user: { is_admin: true }
user2.reload
expect(response).to have_http_status(204)
expect(user2.is_admin).to eq true
end
end
Here I'm calling user2.reload in order to get the updated attributes from the user2 factory.
I don't know why the expect{} syntax doesn't work for factories but you could refactor your code like this:
it "increments the click counter by 1" do
short_link = create(:short_link)
count = short_link.click_counter
get :url_dispatch, { id: short_link.short_link }
short_link.reload
expect(short_link.click_counter).to eq count + 1
end
Again I'm not saying this is best practice, I just couldn't find anything in the FactoryGirl documentation regarding RSpec 3 expect syntax in controllers that update attributes.
I have the following Rspec test for a vote model, which includes a custom validation ensuring you can't vote on your own content, which is shown below. I am puzzled as to why only 2 of these tests fail with a nilclass error when the other tests within the spec pass.
#vote must be nil but why aren't the other tests failing with the same error?
vote.rb
validates :ensure_not_author
def ensure_not_author
votable = self.votable_type.downcase
errors.add(:user_id, "You can't vote on your own content.") if self.votable.user_id == self.user_id
end
factories
factory :answer do
user_id :user
question_id :question
body "you need to change your grip"
votes_count 0
correct false
end
factory :vote do
user_id :user
votable_id :answer
votable_type "Answer"
value 1
points 5
end
factory :user do |u|
u.sequence(:email) {|n| "test#{n}#hotmail.com"}
u.sequence(:username) {|n| "tester#{n}" }
u.password "password"
u.password_confirmation "password"
u.remember_me true
u.reputation 200
end
vote_spec.rb
require "spec_helper"
describe Vote do
before(:each) do
#user2 = FactoryGirl.create(:user)
#user = FactoryGirl.create(:user)
#answer = FactoryGirl.create(:answer, user_id: #user)
#vote = Vote.create(user_id: #user2.id, value: 1, points: 5, votable_id: #answer.id, votable_type: "Answer")
end
subject { #vote }
it { should respond_to(:user_id) }
it { should respond_to(:votable_id) }
it { should respond_to(:votable_type) }
it { should respond_to(:value) }
it { should respond_to(:points) }
describe 'value' do
before { #vote.value = nil }
it { should_not be_valid }
end
describe "user_id" do
before { #vote.user_id = nil }
it { should_not be_valid }
end
describe "votable_id" do
before { #vote.votable_id = nil }
it { should_not be_valid }
end
describe "votable type" do
before { #vote.votable_type = nil }
it { should_not be_valid }
end
describe "vote value" do
before { #vote.value = 5 }
it { should_not be_valid }
end
end
Failures:
1) Vote votable_id
Failure/Error: it { should_not be_valid }
NoMethodError:
undefined method `user_id' for nil:NilClass
# ./app/models/vote.rb:17:in `ensure_not_author'
# ./spec/models/vote_spec.rb:25:in `block (3 levels) in <top (required)>'
2) Vote votable type
Failure/Error: it { should_not be_valid }
NoMethodError:
undefined method `downcase' for nil:NilClass
# ./app/models/vote.rb:16:in `ensure_not_author'
# ./spec/models/vote_spec.rb:35:in `block (3 levels) in <top (required)>'
You validator ensure_not_author depends Vote#votable_type and Vote#votable to function well. And when you test validity of #vote, this validator will be tested.
However, in your "votable_id" testcase, you set votable_id to be nil. Later when you test #vote's validity with should_not be_valid, the ensure_not_author is called and failed at self.votable.user_id because ActiveRecord will query for Votable with votable_id.
Similarly, your "votable type" test case failed at self.votable_type.downcase since you set votable_type to be nil.
You should check the availability of the attributes in your validator before you send messages to them. Or write other validators to check them before ensure_not_author.