I've inherited a rails api and I'm trying to test controllers. I have an endpoint '/api/v1/vitcords' where I create new vitcords. The video model only has a validation name. So my doubt is how to test that when I create a new video without specify a name, I get the message error I want, that in this case is "Vitcord name has to be specified". Thanks.
This is the Vcord model
class Vcord
include Mongoid::Document
include Mongoid::Timestamps
include Mongoid::Spacial::Document
include Concerns::Vitcord::M3u8
include Concerns::Vitcord::User
# Validations
validates_presence_of :name
# Atributes
field :name, type: String
field :location, type: Array, spacial: true
field :address, type: String
field :closed, type: Boolean, default: false
field :public, type: Boolean, default: false
field :thumb, type: String
end
This is the controller video_controller.rb
module Api::V1
class VitcordsController < ApiController
def create
user = current_resource_owner
# Validation
param! :name, String, required: true
param! :location, Hash, required: false
param! :address, String, required: false
ap params[:location]
ap params[:location][:latitude]
ap params[:location][:longitude]
# Creating
vcord = Vcord.new()
vcord.name = params[:name] if params[:name]
if params[:location] && params[:location]['latitude'] && params[:location]['longitude']
vcord.location = {
lng: params[:location]['longitude'],
lat: params[:location]['latitude']
}
end
vcord.address = params[:address] if params[:address]
vcord.creator = user
if vcord.save
vcord.members.each do |member|
Notification.generate_notification_for_vitcord_invited(vcord, member)
end
#vitcord = vcord
else
error(:internal_server_error, ["Vitcord name has to be specified"], nil)
end
end
end
And this is the spec
require 'rails_helper'
describe "POST /api/v1/vitcords" do
before(:each) do
db_drop
post "/oauth/token", {
:grant_type => 'assertion',
:assertion => TOKEN
}
#token = response_json['access_token']
end
it "sends an error if a vitcord is created with name nil" do
header 'Authorization', "Bearer #{#token}"
post '/api/v1/vitcords', {
address: "Calle Rue del Percebe",
location: {
latitude: 40.7127837,
longitude: -74.00594130000002
}
}
//This does not work but it would be something like this
expect(error).to eq("Name has to be specified")
end
end
Well, you should refactor your code, but answering your question, you can add an error to you object by doing (look that I used #vcord and not vcord):
#vcord.errors.add(:name, 'Vitcord name has to be specified')
(as you can see here http://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add)
and on your test:
expect(assigns(:vcord).errors.name).to eq('Vitcord name has to be specified').
Related
I am using Graphql mutation to save a User that looks kinda like this:
class CreateUser < Mutations::BaseMutation
argument :email, String, required: true
argument :password, String, required: true
argument :password_confirmation, String, required: true
argument :first_name, String, required: false
argument :last_name, String, required: false
argument :middle_name, String, required: false
argument :source, String, required: false
field :user, Types::UserType, null: true
field :token, String, null: true
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:password_confirmation], email: args[:email])
profile = user.build_profile
profile.first_name = args[:first_name] if args[:first_name].present?
profile.last_name = args[:last_name] if args[:last_name].present?
profile.middle_name = args[:middle_name] if args[:middle_name].present?
user.save!
UserMailer.with(user: user).send_initial_password_instructions.deliver_now if args[:source].present?
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(obj: { user: user, token: user.authentication_token }, success: user.persisted?, errors: user.errors.full_messages)
end
end
All good here. BUT... I have a model named Contact. Which is quite empty:
class Contact < ApplicationRecord
belongs to :user, optional: true
end
So what I am trying to do is to have a method created on Contact that whenever I create a User I can send some args to Contact and let the Contact method execute the save!
This is what I've been trying: Contact.rb
def self.create_with_user(args)
contact = Contact.new(args)
contact.user = User.new(email: args[:email], password: args[:password], password_confirmation: args[:password_confirmation])
contact.user.save!
end
This is what I've been trying: create_user.rb (graphql mutation)
def resolve(args)
user = User.new(password: args[:password], password_confirmation: args[:password_confirmation], email: args[:email])
profile = user.build_profile
profile.first_name = args[:first_name] if args[:first_name].present?
profile.last_name = args[:last_name] if args[:last_name].present?
profile.middle_name = args[:middle_name] if args[:middle_name].present?
contact = Contact.create_with_user(args)
user.contact = contact
user.save!
UserMailer.with(user: user).send_initial_password_instructions.deliver_now if args[:source].present?
# current_user needs to be set so authenticationToken can be returned
context[:current_user] = user
MutationResult.call(obj: { user: user, token: user.authentication_token }, success: user.persisted?, errors: user.errors.full_messages)
end
But this results into a NoMethodError Exception: undefined method 'to' for Contact:Class.
Newbie rails here so I am really interested in learning this. Thankyou
The error was generated from contact.rb you have a type its belongs_to but you have belongs to.
contact.rb
class Contact < ApplicationRecord
belongs to :user, optional: true
end
Preferred Solutions
If the creation of the Contact should always happen and you don't have any custom params from the endpoint, use a callback on the User model.
user.rb
class User < ApplicationRecord
after_create :create_contact
private
def create_contact
Contact.create(user: self, .....) # the other params you need to pass from model
end
end
If you have custom params from the endpoint you can use accepts_nested_attributes_for and make the contact params nested on the user.
params = {
user: {
user_params,
contact: {
contact_params
}
}
}
user.rb
class User < ApplicationRecord
accepts_nested_attributes_for :contact
end
I have spent a lot of time trying to emplement ability of validation incoming params is rswag specs, my code:
# incoming-parameter
params = {
login: 'www',
id: 15
}
# test rswag-spec
path '/controller/hello' do
post('Say Hello!') do
tags 'users'
consumes 'application/json'
produces 'application/json'
parameter name: :my_params, in: :body, schema: {
type: :object,
required: %i[id name],
properties: {
id: { type: :string },
name: { type: :string }
}
}
response(200, 'successful') do
# schema '$ref' => '#/components/schemas/UserRegistrationResponse'
describe 'new user with valid reg_params' do
let(:my_params) { params }
run_test! do |response|
data = JSON.parse(response.body)
puts "data = #{data}"
end
end
end
end
end
You expecting that incoming params won't pass validation, because id - is an integer, and name field is absent. But that's doesn't matter and test is compliting with success.
Can you say what's wrong with my code an why don't work validation of incoming parameters those declarated in rswag docs?
I installed graphql_devise gem
when I tried to user context[:current_resource], I can't use context[:current_resource]
why?
when I want to use current_user, should i user devise_token_authentication?
I'm in trouble.
please help
thanks
configration
install into my schema
class MySchema < GraphQL::Schema
use GraphqlDevise::SchemaPlugin.new(
query: Types::QueryType,
mutation: Types::MutationType,
resource_loaders: [
GraphqlDevise::ResourceLoader.new('User'),
],
authenticate_default: false
)
mutation(Types::MutationType)
query(Types::QueryType)
# Setup GraphQL to use the built-in lazy_resolve method
use BatchLoader::GraphQL
end
route.rb:
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
# For graphql
if Rails.env.development?
mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql"
end
post "/graphql", to: "graphql#execute"
get "/graphql", to: "graphql#execute"
graqhql_controller.rb:
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
result = BusinessCloudSchema.execute(query, variables: variables, context: graphql_context(:user), operation_name: operation_name)
render json: result unless performed?
rescue => e
raise e unless Rails.env.development?
handle_error_in_development e
end
user_update_input.rb
module Types
class UserUpdateInput < Types::BaseInputObject
argument :fist_name, String, required: true
argument :last_name, String, required: true
argument :position, String, required: false
argument :image, ApolloUploadServer::Upload, required: false
def update!
current_user.assign_attributes(first_name: first_name, last_name: last_name, image: valid_image)
current_user.update
{
errors: user_errors(current_user.errors),
user: current_user.reload
}
end
private
def valid_image
image if image&.is_a?(ApolloUploadServer::Wrappers::UploadedFile)
end
end
end
update_user.rb
module Mutations
class UpdateUser < BaseMutation
field :user, Types::UserType, null: true
argument :user, Types::UserUpdateInput, required: true
def resolve(user:)
{
user: user.update!
}
end
end
end
iI execute the following mutation in graphiql
mutation{
updateUser(input: { clientMutationId: "", user: {fistName: "aa", lastName: "bb", position: "manager"}}) {
clientMutationId
user{
email
firstName
image
lastName
position
}
}
}
So I'm trying to query on a single user within the database but end up getting:
"Field 'userSingle' doesn't accept argument 'first_name'"
I'm getting that in GraphiQL when I run the following query:
query GetSingleUser {
userSingle(first_name: "Test"){
first_name
last_name
}
}
In my query_type.rb I have the following:
field :userSingle, !types[Types::UserType] do
resolve -> (obj, args, ctx) {
argument :first_name, !types.String
argument :id, types.ID
User.find(id: args[:id])}
end
Originally I had:
field :userSingle, !types[Types::UserType] do
resolve -> (obj, args, ctx) {User.find(id: args[:id])}
end
Same issue. If I take out the id: same issue. Also the same issue with:
field :userSingle, !types[Types::UserType] do
resolve -> (obj, args, ctx) {
argument :first_name, !types.String
argument :id, types.ID
user = User.find_by(
id: args[:id],
first_name: args[:first_name])
}
end
You could create a user_type.rb file with the following:
class Types::UserType < Types::BaseObject
description "A user object"
field :id, Integer, null: false
field :first_name, String, null: false
end
Then have the following in query_type.rb file:
module Types
class QueryType < Types::BaseObject
...
# User query
field :user, UserType, null: true do
description "Find a user by first_name"
argument :first_name, String, required: true
end
def user(first_name:)
User.find_by(first_name: first_name)
end
end
end
With all this in place, the query should then look like this:
{
user(first_name: "name") {
id
firstName: first_name
}
}
Instead of, !types[Types::UserType] in query_type file to
field :userSingle do
type Types::UserType
argument
resolve
..
end
This is the place where is create a test table:
factory :reward_scheme, class: RewardsModels::RewardScheme do
uid { ExpectedData::COSTA_UID }
scheme_type { "bricks" }
reward_type { "menu"}
company_address { FactoryGirl.build(:company_address) }
reward_config { FactoryGirl.build(:reward_config) }
brand { FactoryGirl.build(:brand) }
master_offers { [ FactoryGirl.build(:master_offer) ] }
master_specials { [ FactoryGirl.build(:master_special) ] }
url "http://costa.com"
after(:create) do |reward_scheme|
reward_scheme.stores << FactoryGirl.create(:store)
reward_scheme.user_cards << FactoryGirl.create(:user_card)
end
end
The logs are as follows:
CoreModels::Transaction
Failure/Error: reward_scheme.stores << FactoryGirl.create(:store)
Mongoid::Errors::Validations:
message:
Validation of RewardsModels::Store failed.
summary:
The following errors were found: Reward scheme can't be blank
resolution:
Try persisting the document with valid data or remove the validations.
# ./spec/factories/reward_scheme.rb:15:in `block (3 levels) in <top (required)>'
# ./spec/core/transaction_spec.rb:6:in `block (2 levels) in <top (required)>'
This is how the model file looks like:
module UserModels
class Store
include Mongoid::Document
include Mongoid::Timestamps
field :reward_scheme_id, type: String
field :store_id, type: String
field :store_name, type: String, default: "HQ"
field :reward_scheme_name, type:String
field :about, type: String, default: "MyGravity - loyalty begins with trust"
field :logo, type: String, default: 'https://static.mygravity.co/partners/logo.svg'
field :splash_screen_url, type: String, default: "https://static.mygravity.co/assets/SplitShire_Blur_Background_XVI.jpg"
field :awaiting_update, type: Boolean, default:false
embeds_one :location, class_name:'UserModels::Location'
embeds_one :open_hours, class_name:'UserModels::OpenHours'
embeds_one :optional, class_name:'UserModels::Optional'
embeds_many :specials, class_name:'UserModels::Special'
embeds_many :offers, class_name:'UserModels::Offer'
before_create :set_defaults
def set_defaults
self.location = UserModels::Location.new unless self.location
self.optional = UserModels::Optional.new unless self.optional
end
end
class Location
include Mongoid::Document
field :longitude, type: Float, default: -0.131425
field :latitude, type: Float, default: 51.507697
field :address_line_1, type: String, default: 'Impact Hub - Westmister'
field :post_code, type: String, default: 'SW1Y 4TE'
field :city_town, type: String, default: 'London'
embedded_in :store
end
class OpenHours
include Mongoid::Document
field :monday, type: String
field :tuesday, type: String
field :wednesday, type: String
field :thursday, type: String
field :friday, type: String
field :saturday, type: String
field :sunday, type: String
field :sunday_1, type: String
embedded_in :store
end
class Special
include Mongoid::Document
# Need this to search
field :special_id, type: Integer
field :special_uid, type: Integer
field :title, type: String
field :text, type: String
embedded_in :store
before_save :set_special_uid
def set_special_uid
self.special_uid = self.special_id
end
def attributes
# p super
# p Hash[super.map{|k,v| [(alais[k] || k), v]}]
hash = super
alais = {'special_id' => 'id'}
hash.keys.each do |k,v|
hash[ alais[k] ] = hash['special_id'].to_s if alais[k]
# Need this as special_id is mapped in the iOS to a string...
hash['special_id'] = hash['special_id'].to_s if k == 'special_id'
end
hash
end
end
class Offer
include Mongoid::Document
field :name, type: String
field :offer_id, type: Integer
field :value, type: Float, default:0.0 # monetary value
field :points, type: Integer
field :icon_url, type: String
field :icon_name, type: String
embedded_in :store
def attributes
# p super
# p Hash[super.map{|k,v| [(alais[k] || k), v]}]
hash = super
alais = {'offer_id' => 'id'}
hash.keys.each { |k,v| hash[ alais[k] ] = hash['offer_id'] if alais[k] }
hash
end
end
class Optional
include Mongoid::Document
field :email, type: String, default:""
field :twitter, type: String, default:""
field :telephone, type: String, default:""
field :wifi_password, type: String, default:""
embedded_in :store
end
end
Any leads regarding the code changes required for upgrading to mongoid 6 is highly appreciated.
Thanks