ember-cli-mirage testing request params - ember.js

I have default params that are added to the search request from a route. I would like to test these in ember-cli-mirage but am stuck on how to capture the request or requestBody so that I can assert against it.
Was looking for something similar to what I found on this SO post, but need access to the actual request and not the DOM. I am able to access the search params entered by the user (the 'text' param in my example) using currentUrl(), but the default params included in the request sent to the server, but not the url.
Is there a way to capture and assert against the request itself using ember-cli-mirage?
Something like
test('it appends default params to request'), function(assert) {
let searchUrl = '/my/route/url';
server.get(searchUrl, (db, request) => {
assert.equal(request.requestBody, "text=abc&all=true");
}
});
EDIT
I was able to get the test to pass using Qunit's async helper, like so:
test('it appends default params to athlete request', function(assert) {
assert.expect(2);
let done = assert.async();
server.get('/athletes', (db, request) => {
let params = request.queryParams;
assert.equal(params["page"], "1");
assert.equal(params["per"], "50");
done();
});
server.create('athlete', {first_name: 'John'});
visit('/athletes');
});
Still getting an error in the console for this test related to the json:api serialization:
normalizeResponse must return a valid JSON API document:
* meta must be an object
Going to open another question related to this failure elsewhere and link it in the comments.

The request param passed to your route handlers is the PretenderJS request object, which has some useful keys:
request.params, the dynamic segments of your route
request.queryParams, deserialized query request params
request.requestBody, the text body, You can use JSON.parse(request.requestBody) to turn this into an object.
So, if you wanted to assert against the query params, use request.queryParms.

Related

I am using htmx javascript API how do I get the response from server

My code block looks like this, basically when a modal is triggered I send a GET request and retrieve the response, it does send the request alright, but I am unable to see (or to put it better) to get the response from the server via .then() from htmx. I am using the example from there documentation.
htmx.ajax('GET',
'/user-related-comment/',
{ swap: 'none', values: { userId: userId } }
).then(data => {
console.log(data)
})
data is undefined when logged to browser console.
For anyone that needs to do this, an option will be to listen to 'htmx:afterOnLoad' event example.
htmx.ajax('GET', '/example', '#myDiv').then(() => {
document.body.addEventListener('htmx:afterOnLoad', event=>{
console.log(event)
// access response at event.detail.xhr.response
// convert to JavaScript object by JSON.parse(event.detail.xhr.response)
})
});
For some reason on first click, does nothing.

Pretender intercepted GET ... but no handler was defined for this type of request for an external request

I use Stripe in an Ember app. Stripe makes request to this address : https://checkout.stripe.com/api/outer/manhattan?key=... In my acceptance test, I have this message : Pretender intercepted GET https://checkout.stripe.com/api/outer/manhattan?key=... but no handler was defined for this type of request.
I tried to stub this request like this :
var server = new Pretender(function() {
this.get("/api/outer/manhattan", function() {
return [200, {}, this.passthrough];
});
});
But it does not work. I also tried with the full url or with a wildcard without success.
Is there a solution ?

Ember-CLI-Mirage enforcing JSON:API?

Stumped on a couple failures and want to know if I'm understanding Mirage correctly:
1.In ember-cli-mirage, am I correct that the server response I define should reflect what my actual server is returning? For example:
this.get('/athletes', function(db, request) {
let athletes = db.athletes || [];
return {
athletes: athletes,
meta: { count: athletes.length }
}
});
I am using custom serializers and the above matches the format of my server response for a get request on this route, however, on two tests I'm getting two failures with this error: normalizeResponse must return a valid JSON API document: meta must be an object
2.Is mirage enforcing the json:api format, and is it doing so because of the way I'm setting up the tests?
For example, I have several tests that visit the above /athletes route, yet my failures occur when I use an async call like below. I would love to know the appropriate way to correctly overwrite the server response behavior, as well as why the normalizeResponse error appears in the console for 2 tests but only causes the one below to fail.
test('contact params not sent with request after clicking .showglobal', function(assert) {
assert.expect(2);
let done = assert.async();
server.createList('athlete', 10);
//perform a search, which shows all 10 athletes
visit('/athletes');
fillIn('.search-inner input', "c");
andThen(() => {
server.get('/athletes', (db, request) => {
assert.notOk(params.hasOwnProperty("contacts"));
done();
});
//get global athletes, which I thought would now be intercepted by the server.get call defined within the andThen block
click('button.showglobal');
});
});
Result:
✘ Error: Assertion Failed: normalizeResponse must return a valid JSON API document:
* meta must be an object
expected true
I tried changing my server response to a json:api format as suggested in the last example here but this looks nothing like my actual server response and causes my tests to fail since my app doesn't parse a payload with this structure. Any tips or advice must appreciated.
You are correct. Are the failures happening for the mock you've shown above? It looks to me like that would always return meta as an object, so verify the response is what you think it should be by looking in the console after the request is made.
If you'd like to see responses during a test, enter server.logging = true in your test:
test('I can view the photos', function() {
server.logging = true;
server.createList('photo', 10);
visit('/');
andThen(function() {
equal( find('img').length, 10 );
});
});
No, Mirage is agnostic about your particular backend, though it does come with some defaults. Again I would try enabling server.logging here to debug your tests.
Also, when writing asserts against the mock server, define the route handlers at the beginning of the test, as shown in the example from the docs.
I was able to get my second test to pass based on Sam's advice. My confusion was how to assert against the request params for a route that I have to visit and perform actions on. I was having to visit /athletes, click on different buttons, and each of these actions was sending separate requests (and params) to the /athletes route. That's is why I was trying to redefine the route handler within the andThen block (i.e. after I had already visited the route using the route definition in my mirage/config file).
Not in love with my solution, but the way I handled it was to move my assertion out of route handler and instead assign the value of the request to a top-level variable. That way, in my final andThen() block, I was able to assert against the last call to the /athletes route.
assert.expect(1);
//will get assigned the value of 'request' on each server call
let athletesRequest;
//override server response defined in mirage/config in order to
//capture and assert against request/response after user actions
server.get('athletes', (db, request) => {
let athletes = db.athletes || [];
athletesRequest = request;
return {
athletes: athletes,
meta: { count: athletes.length }
};
});
//sends request to /athletes
visit('/athletes');
andThen(() => {
//sends request to /athletes
fillIn('.search-inner input', "ab");
andThen(function() {
//sends (final) request to /athletes
click('button.search');
andThen(function() {
//asserts against /athletes request made on click('button.search') assert.notOk(athletesRequest.queryParams.hasOwnProperty("contact"));
});
});
});
I'm still getting console errors related to meta is not an object, but they are not preventing tests from passing. Using the server.logging = true allowed me to see that meta is indeed an object in all FakeServer responses.
Thanks again to Sam for the advice. server.logging = true and pauseTest() make acceptance tests a lot easier to troubleshoot.

Unexpected end of input on ember .save(), empty responseText

I'm doing a PUT request with ember .save() method. Returned status is 200, but I keep getting the "unexpected end of input error". I think it might be because request is returning and empty json responseText as shown here :
http://gyazo.com/6cbb68c1de8fd79a6ec90e6f122dc132
Do you have any ideas how I can solve this problem or the exact reason I get this error?
The RESTAdapter sets the jQuery $.ajax option dataType to json. This causes all responses to be treated as JSON and parsed.
I believe your getting that error because the response is not valid JSON.
There are two ways to fix this:
1. Change the Server Response
Change the server to it returns a valid JSON string and it will stop you getting that error.
2. Implement a Custom Ember Data Adapter
You can implement a custom adapter that sets the dataType option to text when you call .save()`
App.MyAdapter = DS.RESTAdapter.extend({
// By default, the RESTAdapter sets 'dataType'
// to JSON - causing the response text to be
// treated as a JSON resulting in an error
// for responses that are not valid JSON.
// We want to override this if we are not
// expecting JSON from the server
ajaxOptions: function(url, type, options) {
// get the default RESTAdapter 'ajaxOptions'
var hash = this._super(url, type, options);
// override if it's a PUT request
if (type === 'PUT') {
hash.dataType = 'text';
}
return hash;
},
ajaxSuccess: function(jqXHR, data) {
if (typeof data === 'string') {
// return an empty object so the Serializer
// handles it correctly
return {};
} else {
return data;
}
}
});
I've created a JSBin that demonstrates this. When you click the save button the mocked ajax response returns the same string (' ' - looks like one space character?) that your server is returning.
http://emberjs.jsbin.com/durugo/5/edit?js,output

Basic HTTP Authentication in Ember Data REST Adapter

Is there a way in Ember.js (and Ember-data) to send credentials to an api that requires Basic HTTP Authentication? I can see how it's done in JQuery here, but don't see any straightforward way to do it in Ember. I thought maybe adding something to the header would work (see below in coffeescript), but no success:
App.AuthAdapter = DS.RESTAdapter.extend(
host: 'https://my-api.example.com'
namespace: 'v1'
headers:
"Authorization Basic fooUsername:barPassword"
...
You can extend the default Rest adapter and add a headers hash which will be included in the ajax that's sent.
App.ApplicationAdapter = DS.RESTAdapter.extend(
headers:
withCredentials: true
Authorization: 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
)
Or you could take it a step farther and override the ajax method
App.ApplicationAdapter = DS.RESTAdapter.extend(
ajax: (url, type, hash) ->
adapter = this
new Ember.RSVP.Promise((resolve, reject) ->
hash = hash or {}
hash.url = url
hash.type = type
hash.dataType = "json"
hash.context = adapter
if hash.data and type isnt "GET"
hash.contentType = "application/json; charset=utf-8"
hash.data = JSON.stringify(hash.data)
if adapter.headers isnt `undefined`
headers = adapter.headers
hash.beforeSend = (xhr) ->
forEach.call Ember.keys(headers), (key) ->
xhr.setRequestHeader key, headers[key]
hash.success = (json) ->
Ember.run null, resolve, json
hash.error = (jqXHR, textStatus, errorThrown) ->
Ember.run null, reject, adapter.ajaxError(jqXHR)
Ember.$.ajax hash
)
)
Can you use $.ajaxPrefilter? e.g.
Ember.$.ajaxPrefilter (options) ->
options.xhrFields = { withCredentials: true }
options.username = 'fooUsername'
options.password = 'barPassword'
true # need to return non-falsy here
As #gerry3 stated $.ajaxPrefilter is a valid solution.
But if you want to solve a problem of dynamically changing your Headers AFTER an event, for instance, a successful LOGIN attempt, then you need to put more wires. In my case I need to send back a 'Token' Header that is provided by the server after a successful AJAX-login. But, of course, when the user initiates the App he's not logged-in already.
The problem is that once you reopen or extend the RESTAdapter, or define an ajaxPrefilter, even if you're binding it to a value (or localStorage as in my case) the class won't be following the current variable value. It's like a snapshot taken at some moment. So it's useless in my scenario.
I'm following Embercast Client Authentication which is a good start (code available), but instead of jQuery data-fetching I'm using Ember-Data.
So the trick is to observe the token and re-define the ajaxPrefilter as many times as you need it.
tokenChanged: function() {
this.get('token')=='' ?
localStorage.removeItem('token') :
localStorage.token = this.get('token');
$.ajaxPrefilter(function(options, originalOptions, xhr) {
return xhr.setRequestHeader('Token', localStorage.token);
});
}.observes('token')
Therefore, when the user logs-in he'll have a valid token and send it in every request to the server via the RESTAdapter.
Hope this helps someone.