rspec, validate the actual Expectation parameter - ruby-on-rails

Is it possible to validate the actual parameter received by a mock objects method parameter? I cannot do a direct comparison because i'm using Fabricate and converting the object into a serialised format.
for example:
expect(user).to have_received(:add).with(valid_user())
so in that case valid_user() would accept the parameter, validate and return a boolean, to verify a valid value was passed into user.add()
can something like that be done?
So far I've been reading the documentation in https://github.com/rspec/rspec-mocks regarding Argument Matchers
Edit: in my specific case, the argument is a quite large string. I would like to validate that the string is valid by potentially running a regex against the string. So i would like to run the argument through a validation method which simply returns true/false.

You can use a custom matches in with() as shown in the spec docs https://relishapp.com/rspec/rspec-mocks/docs/setting-constraints/matching-arguments#using-a-custom-matcher

Related

Can't modify frozen String on Hash

I am a bit confused with the frozen string and utilizing them with test cases.
I just added the following line at the top of my test cases :
# frozen_string_literal: true
And i have the following two test cases:
test "Create upload invoice invalid invoice id" do
post :upload, params = {invoices_data: [{invoice_id: 987654, unit_id: 1321}]}
assert_response :not_found
end
test "Create upload invoice request to fortnox with non array request parameter" do
request = {invoices_data: {invoice_id: "invoice.id", unit_id: 321}}
post :upload_invoices, params = request
assert_response :bad_request
end
All of a sudden my second test failed with
RuntimeError: can't modify frozen String
at this line
post :upload_invoices, params = request
however, if I change the string to some symbol for instance :invoice_id then it works just fine.
Can someone guide why about the following two things:
Why does sending a string value fails in this case reporting that I
am trying to modify a String and which string value I am trying to
modify?
Why does it fail on post request, if it has to fail then it
should fail when creating the request i.e request = {invoices_data: {invoice_id: "invoice.id", unit_id: 321}}
What i can do to send string value instead of Symbol in the hash?
1a) Sending a string value fails in this case because your upload_invoices controller action attempts to modify the invoice_id parameter itself. (Or you're running an old version of Rails where the #post method itself attempts to modify the invoice_id parameter by converting it to UTF-8 encoding.)
1b) The string value you're trying to modify is "invoice.id".
2 ) It fails on the post request and not the assignment to the request variable because the assignment to the request variable is not where the attempted modification happens. The frozen string literal is attempted to be modified by the call to #post. See answer 1a above.
3 ) You can send a non-frozen string value in the hash a few different ways. You could remove the # frozen_string_literal: true magic comment, but I feel you don't want to do that. Otherwise, the simplest thing to do is to send along a duplicate of the string with either +'invoice.id' or the less esoteric 'invoice.id'.dup. Or you can create a non-literal string with something silly like ['invoice', 'id'].join('.') or :invoice.to_s. No doubt there are other ways.
However, it seems EXTREMELY unlikely you want to pass a string here at all. The invoice_id parameter is almost assuredly an integer, and passing a string to it makes little sense unless I guess you're trying to test that the controller action can handle that kind of erroneous input. If so, one of the string duplication techniques +'string_literal'/'string_literal'.dup would be your best option.
I would wager by the name of the test that you're actually trying to send along a real invoice_id which means you don't want to pass along a string, but instead an integer. Maybe the ID of an Invoice fixture you have setup?
And on another slightly unrelated note, you're not passing params to the #post method properly. It should be params: ... not params = ....

conditional match on rails rspec

I want to write a test on helper method which fetches data from external service based on an id. So there is an uncertainty whether the value will be returned or nil. But if value is returned, id of returned value must be equal to given id. Is there a way to achieve this?
expect(record).to be_nil.or expect(record.id).to eq(deal.user_id)
however it seems the or condition does not work the way I think. I am new to RoR. Might be missing any obvious way to do it.
You can write ruby code in specs too:
if record != nil
expect(record.id).to eq(deal.user_id)
end
or combine matchers:
expect(record).to be_nil.or(eq(deal.user_id))

GraphQL - Allow only certain values for Input Types

I am using the [graphql][1] gem to build out a GraphQL API.
When defining inputs for example --
Inputs::BranchInput = GraphQL::InputObjectType.define do
name 'BranchInput'
argument :scope, types.String
end
The argument scope is actually an enum field that will accept either small or big. How can I define this in the input that this argument will only accept these two values? Is it possible to show this in the docs as well when they are generated so that the UI developers are also clear on this?
When using Grape to generate REST APIs, I would generally use to values parameter to define something like this.

Validating API input

Various parameters are appended to the API URL.
There is data that needs to be validated by ruby (as rails does not process validations before creating/modifying data) before processing.
The following returns true with a given string, but the parametrized version always returns true
!1e48.is_a? Integer
(!params[:id].is_a? Integer)
I realise rails considers all paramters as strings, but converting the params to_i or some other type leads onto a garden path. What would be appropriate syntax?
Use more restrictive Kernel#Integer function:
!(Integer(params[:id]) rescue nil)

Scope and Field as Parameter in Rails

I've added a scope to a Rails model that allows for searching based on a specified parameter field using a range. Here is what it looks like:
scope :upcoming, lambda { |field|
where("to_char(#{field}, 'DDD') BETWEEN :alpha AND :omega",
alpha: Time.now.advance(days: 4).strftime('%j'),
omega: Time.now.advance(days: 8).strftime('%j'),
)
}
Event.upcoming(:registration) # Query all events with registration shortly.
Event.upcoming(:completion) # Query all events with completion shortly.
The above works fine, however in creating I read the Ruby on Rails Guides and found the following:
Putting the variable directly into the conditions string will pass the variable to the database as-is. This means that it will be an unescaped variable directly from a user who may have malicious intent. If you do this, you put your entire database at risk because once a user finds out he or she can exploit your database they can do just about anything to it. Never ever put your arguments directly inside the conditions string.
Although the scope is currently never called with a user parameter, I am curious if a way exists of setting the field without using the interpolation in order to better conform with the above recommendation. I've tried using another named parameter, however this will escape the field with quotes (and thus cause it to fail). Any ideas?
I would recommend validating the field parameter against the model's attributes, essentially using the model as a whitelist for values that are allowed to be passed. Something like this:
scope :upcoming, lambda { |field|
if column_names.include? field.to_s
where("to_char(#{field}, 'DDD') BETWEEN :alpha AND :omega",
alpha: Time.now.advance(days: 4).strftime('%j'),
omega: Time.now.advance(days: 8).strftime('%j'),
)
else
# throw some error or return nil
end
}
Okay, reading all the way to the end might help(thanks rubyprince). It looks like you are doing a between query on a field that is storing a date in Oracle. The problem is that to_char is looking for a variable, not a string. And the act of escaping a variable in rails turns it into a string. So, in this particular case you might convert :alpha and :omega into the format of the value stored in field. That way you can escape field in a straightforward manner. Of course there is the issue with Oracle treating dates as Time. I'm guessing that is why you converted to day-of-year for the compare. If you are using the Oracle Enhanced Adaptor you can set
self.emulate_dates_by_column_name = true
to make sure that the field is treated like a date. Then use the to_date function(which takes a string) with :alpha and :omega
scope :upcoming, lambda { |field|
where(":field BETWEEN to_date(:alpha,'yyyy/mm/dd') AND to_date(:omega,'yyyy/mm/dd')",
field: field,
alpha: Time.now.advance(days: 4).strftime('%Y/%m/%d'),
omega: Time.now.advance(days: 8).strftime('%Y/%m/%d'),
)
}
I have no way of testing this so I might be off in the weeds here.
Validating user input as per Jordan is always a good idea.

Resources