In a grails integration test, I have code that resembles this:
def ctrlA = new MyController()
... make some request that returns 'ok' ...
assert ctrlA.response.json.status == 'ok'
def ctrlB = new MyController()
... make some request that returns 'error' ...
assert ctrlB.response.json.status == 'error' // fails; status still equals 'ok'
Problem: Even when ctrlB actually does return a json response that looks like { status: 'error' }, I'm actually seeing { status: 'ok' }, the value that was in ctrlA.response.json!! My logs in the controller indicate that 'error' is most definitely being returned.
Why is this?
Ah. Don't need the separate ctrlA and ctrlB at all. Just call ctrl.response.reset() in between.
Related
Note: This feature works properly on my local site and in review apps AND passes in rspec locally, but seems to only fail when testing in CircleCI.
Below I have a filter class that I use to pass in a default scope (Object.where(state: :in_progress)), and uses a filter parameter to further filter the default scope. This class is called when hitting an endpoint ex: /api/example/endpoint?filter=current_user and while testing this endpoint I've found this bug. In my other specs I've validated the data of the objects being queried, the current_user, and the filter param and have been able to narrow down the issue to the call to apply: ->(scope, user). I have verified that scope remains valid even after passing it into apply, but when the where clause is appended scope becomes an empty ActiveRecord::Relation as if the data never existed at all. I suspect there is either a bug with rspec or something about rspec that I do not understand.
class WorkOrderPreviews::IndexFilter
FILTERS = {
current_user_filter: {
apply?: ->(params) {
params[:filter] == "current_user"
},
apply: ->(scope, user) {
scope.where(technician: user)
}
}.freeze,
}.freeze
def call(scope, params, current_user)
FILTERS.keys.inject(scope.dup) do |current_scope, filter_key|
filter = fetch_filter(filter_key)
filter[:apply].call(current_scope, current_user) if filter[:apply?].call(params)
end
end
def fetch_filter(filter_key)
FILTERS.fetch(filter_key) { raise ArgumentError, 'Unknown filter.' }
end
end
My spec:
let_once(:tenant) { create_new_tenant }
let!(:staff) { tenant.owner }
let!(:tech) { FactoryBot.create(:staff, :technician) }
let!(:assigned_work_order) { FactoryBot.create(:work_order, :in_progress, technician: tech) }
let!(:unassigned_work_order) { FactoryBot.create(:work_order, :in_progress) }
let!(:estimate) { FactoryBot.create(:work_order, :estimate) }
before do
host! tenant.hostname
#access_token, refresh_token = jwt_and_refresh_token(tech.user, "user")
end
context 'without user filter' do
it 'should respond with all work orders with stat=in_progress' do
get api_internal_work_order_previews_path,
headers: { "Authorization" => #access_token }
expect(assigns(:work_orders)).to eq([assigned_work_order, unassigned_work_order])
end
end
context 'with user filter' do
it 'should respond with the assigned work order when ?filter=current_user' do
scope = WorkOrder.where(state: 'in_progress')
filtered_results = WorkOrderPreviews::IndexFilter.new.call(scope, { filter: 'current_user' }, tech)
get api_internal_work_order_previews_path,
headers: { "Authorization" => #access_token },
params: { filter: "current_user" }
expect(assigns(:work_orders)).to eq(filtered_results)
end
end
# This is the reponse body for spec #1. This data (which excludes the filter) is correct.
(byebug) JSON.parse(response.body)
[{"id"=>4, "advisor"=>nil, "technician"=>"Firstname5 Lastname5", "due_in_time"=>nil, "due_out_time"=>nil, "total_ro_billed_hours"=>0, "label"=>nil, "vehicle_plate_number"=>"JC2931", "vehicle_fleet_number"=>"12345", "vehicle_year"=>"1996", "vehicle_make"=>"Toyota", "vehicle_model"=>"T100", "vehicle_color"=>"BLACK", "vehicle_engine"=>"V6, 3.4L; DOHC 24V", "service_titles"=>[]}, {"id"=>5, "advisor"=>nil, "technician"=>nil, "due_in_time"=>nil, "due_out_time"=>nil, "total_ro_billed_hours"=>0, "label"=>nil, "vehicle_plate_number"=>"JC2931", "vehicle_fleet_number"=>"12345", "vehicle_year"=>"1996", "vehicle_make"=>"Toyota", "vehicle_model"=>"T100", "vehicle_color"=>"BLACK", "vehicle_engine"=>"V6, 3.4L; DOHC 24V", "service_titles"=>[]}]
(byebug)
I found this bug while testing if response.body returned the correct collection, and to further prove the point of this post I'm including the spec above. Weirdly enough, filtered_results returns the correct data, but the same call in my controller returns the empty ActiveRecord::Relation. The only thing I can think of is that maybe the user session in CircleCI is instantiated in a different way than when running locally, but this seems unlikely. What am I missing?
I have a class MyVoucherClass that calls a separate service within a Rails application.
In the class that I am testing, VoucherIssuer, I am calling two of MyVoucherClass’s class methods, issue_voucher and activate_voucher which POST to the separate service.
I want to stub the entire MyVoucherClass and what values its class methods return. From RSpec documentation and further searching I have found that the following should work:
subject(:issue_credits) { described_class.new.issue_credits }
let(:my_voucher_class_double) do
class_double(MyVoucherClass,
issue_voucher: { voucher_id: "ABC123" }.to_json,
activate_voucher: instance_double(VoucherClass, voucher_id: "ABC123")
).as_stubbed_const
end
context “when using MyVoucherClass” do
it “calls on MyVoucherService” do
issue_credits
end
end
However, it throws the error:
WebMock::NetConnectNotAllowedError: Real HTTP connections are disabled. Unregistered request: POST [separate service url]
which means that the method return value stubbing is not working.
I am working around this with multiple allow(MyVoucherClass) ... and_return() statements instead, but I am wondering why the class double and as_stubbed_const are not working, because it would be ideal to do it in one class_double instead of allow twice.
let & let!
Note that let is lazy-evaluated: it is not evaluated until the first time
the method it defines is invoked. You can use let! to force the method's
invocation before each example.
Either you can call my_voucher_class_double inside it block to invoked or use let! instead of let
Using let
subject(:issue_credits) { described_class.new.issue_credits }
let(:my_voucher_class_double) do
class_double(MyVoucherClass,
issue_voucher: { voucher_id: "ABC123" }.to_json,
activate_voucher: instance_double(VoucherClass, voucher_id: "ABC123")
).as_stubbed_const
end
context “when using MyVoucherClass” do
it “calls on MyVoucherService” do
my_voucher_class_double
issue_credits
end
end
Using let!
subject(:issue_credits) { described_class.new.issue_credits }
let!(:my_voucher_class_double) do
class_double(MyVoucherClass,
issue_voucher: { voucher_id: "ABC123" }.to_json,
activate_voucher: instance_double(VoucherClass, voucher_id: "ABC123")
).as_stubbed_const
end
context “when using MyVoucherClass” do
it “calls on MyVoucherService” do
issue_credits
end
end
I have implemented PayPal checkout API in my rails application by using the SmartButtons and by creating the order in the server-side.
I have used the payouts-ruby-sdk gem and my code is as follows:-
index.html.erb
<!-- Set up a container element for the button -->
<div id="paypal-button-container"></div>
<!-- Include the PayPal JavaScript SDK -->
<script src="https://www.paypal.com/sdk/js?client-id=xyz¤cy=USD"></script>
<script>
// Render the PayPal button into #paypal-button-container
paypal.Buttons({
// Call your server to set up the transaction
createOrder: function(data, actions) {
return fetch('/orders', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
return orderData.orderID;
});
},
// Call your server to finalize the transaction
onApprove: function(data, actions) {
return fetch('/orders/' + data.orderID + '/capture', {
method: 'post'
}).then(function(res) {
return res.json();
}).then(function(orderData) {
// Three cases to handle:
// (1) Recoverable INSTRUMENT_DECLINED -> call actions.restart()
// (2) Other non-recoverable errors -> Show a failure message
// (3) Successful transaction -> Show a success / thank you message
// Your server defines the structure of 'orderData', which may differ
var errorDetail = Array.isArray(orderData.details) && orderData.details[0];
if (errorDetail && errorDetail.issue === 'INSTRUMENT_DECLINED') {
// Recoverable state, see: "Handle Funding Failures"
// https://developer.paypal.com/docs/checkout/integration-features/funding-failure/
return actions.restart();
}
if (errorDetail) {
var msg = 'Sorry, your transaction could not be processed.';
if (errorDetail.description) msg += '\n\n' + errorDetail.description;
if (orderData.debug_id) msg += ' (' + orderData.debug_id + ')';
// Show a failure message
return alert(msg);
}
// Show a success message to the buyer
alert('Transaction completed');
});
}
}).render('#paypal-button-container');
</script>
orders_controller.rb
class OrdersController < ApplicationController
skip_before_action :verify_authenticity_token
def index
end
def create
# Creating Access Token for Sandbox
client_id = 'xyz'
client_secret = 'abc'
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
request = PayPalCheckoutSdk::Orders::OrdersCreateRequest::new
request.request_body({
intent: "CAPTURE",
purchase_units: [
{
amount: {
currency_code: "USD",
value: "10.00"
}
}
]
})
begin
# Call API with your client and get a response for your call
# debugger
response = client.execute(request)
puts response.result.id
render json: {success: true, orderID: response.result.id}
rescue PayPalHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
end
def execute_payment
client_id = 'xyz'
client_secret = 'abc'
# Creating an environment
environment = PayPal::SandboxEnvironment.new(client_id, client_secret)
client = PayPal::PayPalHttpClient.new(environment)
request = PayPalCheckoutSdk::Orders::OrdersCaptureRequest::new(session[:orderID])
begin
# Call API with your client and get a response for your call
response = client.execute(request)
# If call returns body in response, you can get the deserialized version from the result attribute of the response
order = response.result
puts order
rescue PayPalHttp::HttpError => ioe
# Something went wrong server-side
puts ioe.status_code
puts ioe.headers["debug_id"]
end
end
end
Now I want to implement the Paypal's Payouts API and I know that paypal-ruby-sdk is available for it but I am confused where to fit this code and how to integrate it with the front end. Any ideas? Thanks in advance :)
Your code above is Checkout code, for both front-end (JavaScript), and back-end (Ruby).
Payouts has nothing to do with Checkout, neither front-end Checkout nor back-end Checkout.
Payouts is strictly a backend API operation, where you send money from your account to another account.
Payouts does not connect to any front-end UI. You can build your own UI to trigger a payout, if you need one. Presumably you know who you want to send money from your account to, and what process should trigger this action.
Following is a code fragment obtained from Grails website.
<script>
function messageKeyPress(field,event) {
var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
var message = $('#messageBox').val();
if (theCode == 13){
<g:remoteFunction action="submitMessage" params="\'message=\'+message" update="temp"/>
$('#messageBox').val('');
return false;
} else {
return true;
}
}
function retrieveLatestMessages() {
<g:remoteFunction action="retrieveLatestMessages" update="chatMessages"/>
}
function pollMessages() {
retrieveLatestMessages();
setTimeout('pollMessages()', 5000);
}
pollMessages();
</script>
The above code worked but when i added the Controller it stopped working. I meant that the records gets saved in the DB, but i am not able to retrieve the data and display on screen.
This is what i did
<g:remoteFunction controller="message" action="retrieveLatestMessages" update="chatMessages"/>
The MessageController function is as follows:
#Secured([ 'ROLE_USER'])
def retrieveLatestMessages() {
println "test"
def messages = Message.listOrderByDate(order: 'desc', max:1000)
[messages:messages.reverse()]
println messages
}
The above controller function gets executed (I see the println statements on console), but the data isn't getting populating on the screen.
Can someone help me out here
UPDATE
[{"class":"myPro.Message","id":3,"date":"2014-07-23T17:31:58Z","message":"dfdf","name":"hi"},{"class":"myPro.Message","id":2,"date":"2014-07-23T17:31:56Z","message":"dfdfdf","name":"dd"},{"class":"myPro.Message","id":1,"date":"2014-07-23T17:31:18Z","message":"xxxx","name":"fie"}]
Your method - retrieveLatestMessages() action in your case - must return a model, but it returns the output of println instead.
To make your code work, you must place the model in the last line, or explicitly return it by using return statement:
def retrieveLatestMessages() {
println "test"
def messages = Message.listOrderByDate(order: 'desc', max:1000)
println messages
[messages:messages.reverse()]
}
Try this
import grails.converters.JSON
#Secured([ 'ROLE_USER'])
def retrieveLatestMessages() {
println "test"
def messages = Message.listOrderByDate(order: 'asc', max:1000)
render messages as JSON
}
Enjoy.
I had this sample app working on mine with no issues but here is the thing, this process requires you to poll the page consistently and it is resource intensive:
I ended up writing a domainClass that was bound to a Datasource that was using the HQL db and was outside of my own app, the process requires a DB table to stream chat....
Alternative is to move away from polling and use websockets:
check out this video
https://www.youtube.com/watch?v=8QBdUcFqRkU
Then check out this video
https://www.youtube.com/watch?v=BikL52HYaZg
Finally look at this :
https://github.com/vahidhedayati/grails-websocket-example
This has been updated and includes the 2nd method of using winsocket to make simple chat ....
Is there a shorthand way to do this without explicit "text/json" designation?
def remoteError = {
render( status: 500, contentType: "text/json"){
error( exception: "a remote exception occurred")
}
}
I tried using as JSON...no content is returned but the status code is correct...
render( status: 500, exception: params.exception) as JSON
If you use a converter parameter to render then you cannot specify any other parameter such as status like you normally would when using gsp views. You can however set the response status prior to calling render:
response.status = 500
render([error: 'an error occurred'] as JSON)
render(status:500,text:(errors as JSON).toString(),contentType: 'application/json')