How to test Request objects' properties in node - javascript

I have a function that returns a new Request object;
import * as _url from 'url';
// pathname starts with '/content/'
const isContentUrl = (path) => /^\/content\//.test(path);
export default function(url) {
let urlObj = _url.parse(url);
if (isContentUrl(urlObj.pathname)) {
urlObj.pathname = `/offline${urlObj.pathname}`;
}
return new Request(_url.format(urlObj), {
credentials: 'same-origin',
headers: {
'x-requested-with': 'sw'
}
});
}
Now I'm writing unit tests for this function and although I know there isn't actually much that changes here but say for example the headers could change for whatever reason, how can I assert parts of the request object like the headers, credentials or the URL?
Is there a nice way to be able to parse it for testing?
Ideally I'd like to do something like
it('should return a Request object with the correct headers', () => {
const url = '/content/2c509c50-e4ba-11e6-9645-c9357a75844a';
const request = offlineContent(url);
const result = request.headers;
const expected = {'x-requested-with': 'sw'};
expect(result).to.eql(expected);
});
in my test

Assuming that request here the request HTTP client, the returned object is an instance of the Request constructor defined here.
If you follow through the code, you will see that headers provided are available simply through an object member and thus headers, and other members being attached to self can be easily introspected through javascript.
In addition, the http module through which requests are dispatched is available as self.httpModule and can be mocked by an implementation that is compliant with node http module and requests dispatched through the library can be intercepted through spies.

MDN details the methods on the Request and Headers object. Unfortunately I haven't found a way to convert this to an object that I could assert with a deep equal. But I can use these to assert against.
request.url;
request.credentials;
request.headers.get('x-requested-with');
Unfortunately the Headers.getAll() method has been deprecated so in order to fail tests if someone added a new header I have asserted against;
Array.from(request.headers.keys()).length

Related

Retrieving non-header context from Apollo Server

I keep seeing examples about setting context on Apollo Client like this :
new ApolloClient({
uri: '...',
request(operation) {
const currentUser = readStore<CurrentUser>("currentUser");
currentUser &&
operation.setContext({
headers: { authorization: currentUser.token },
other: "things"
});
}
});
}
With things that go to the request headers, and "other" things. Yet after more than 2h of research, I couldn't find one example of that other context data being retrieved on the other end, on Apollo Server.
It seems like all the examples are about authorization tokens, but how to I retrieve the rest, say using apollo-server-express ?
Here is what I have so far :
const apollo = new ApolloServer({
typeDefs,
resolvers,
context({ req }): Context {
const currentUser =
(req.headers.authorization &&
(jwt.verify(
req.headers.authorization,
process.env.JWTSIGN
) as Context["currentUser"])) ||
null;
// Ok for req.headers.authorization, how to I get "other: things" ?
return {
ip: req.ip,
currentUser
};
}
});
The context function here only gets a req and a res object from Express. After some logs, it doesn't seem to contain the data I want.
Thank you.
The only reason it appears headers are shared in the example you shared is because ApolloClient uses the headers value inside its context to populate the HTTP headers it sends when making a request. Those headers are then parsed by express and made available on the req object. The context used by ApolloClient is strictly client-side. The context used by ApolloServer is strictly server-side. You can't use ApolloClient's context to pass arbitrary values to your server -- you should utilize headers or arguments inside your GraphQL query for that.

How do I stream JSON objects from ExpressJS?

I'm trying to stream JSON objects from an ExpressJS / Node backend API to a frontend site.
I do not want to use Sockets.IO for various reasons. As I understand it, the native streaming libraries should support streaming objects, it appears that just Express is complicating this.
My frontend code seams straight forward. I use Fetch to get my target URL, get a read stream from the response object, and set that read stream to objectMode: true.
Frontend Example:
async function () {
let url = "myurl";
let response = await fetch( url, {
method: 'GET',
mode: 'cors',
wtihCredentials: 'include'
}
const reader = response.body.getReader({objectMode: true });
// Where things are a bit ambiguous
let x = true;
while (x) {
const {done, value} = reader.read()
if (done) { break; }
// do something with value ( I push it to an array )
}
}
Backend Bode Example ( fails because of I cannot change the stream to objectMode )
router.get('/', (request, response) => {
response.writeHead(200, { 'Content-Type' : 'application/json' });
MongoDB.connection.db.collection('myCollection').find({}).forEach( (i) => {
response.write(i);
}).then( () => {
response.end()
})
})
Now my problem is that there does not appear to be anyway to change the ExpressJS write stream to objectMode: true. To my dismay, the ExpressJS documentation doesn't even acknoledge the existence of the write() function on the response object: https://expressjs.com/en/api.html#res
How do I change this over to objectMode: true ?
conversely, I tried to work with the writeStream as a string. The problem that I run into is that when the send buffer fills up, it does it by characters, not by the object. These means that at some point invalid JSON is passed to requester.
A suggested solution that I run into often is that I could read all of the chunks on the client and assemble valid JSON. This defeats the purpose of streaming, so Im trying to find a better way.
For what I believe is the same problem, I cannot figure out how to talk directly to the write stream object from the express code so I am unable to use the native writeStream operation writable.length in order to manually check to see if there is space for the entire JSON object as a string. This is preventing me from using stringified JSON with new line terminators.
https://nodejs.org/api/stream.html#stream_writable_writablelength
https://nodejs.org/api/stream.html#stream_writable_writableobjectmode
Could someone set me straight? I am working with 100k + records in my Mongo database, I really need partical page loading to work so that the users can start picking through the data.

Parse raw body on cloudflare-worker service(Non NODE)

I've created an api server'ish environment on cloudflare using cloudflare-worker and there is no node server running(cloudflare-worker is pretty much serverless event handler service). It does provide the configurations to handle any subdomain calls much like how api works. I've used a package called cf-worker-router to do so.
My cloud service looks like this:
import { ApiError, ApiRedirect, DomainRouter, FetchRouter } from 'cf-worker-router';
const router = new FetchRouter();
// add the cloudflare event listener
addEventListener('fetch', (event) => {
router.onFetch(event);
});
router.route('/users', 'POST', async (event) => {
// automatically converts anything not of Response type to ApiResponse
return await event.request.text();
});
And what I did was create a POST request to the url and supplied some body to the request. I was able to get the request text successfully but now I can't figure out how to parse the text I received.
When using the request as multipart/form-data request and the received body text is as follows:
"----------------------------093590450792211419875705\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\nJon Doe\r\n----------------------------093590450792211419875705--\r\n"
I tried sending application/x-www-form-urlencoded and I the response text as such:
"name=Jon%20Doe"
And Similar for application/json request:
"{\n\t\"name\": \"Jon Doe\"\n}"
Since cloudflare is not using nodejs server, body-parser can't be applied here. This service is pretty much an open api so it needs to take care of all sorts of request content types. Is there any way to identify and decode the strignified contents from any of these content types to a valid object in javascript?
To handle form data uploads, you can use the request.formData() method which will return a promise of a FormData object.
For example:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const formData = await request.formData();
const name = formData.get('name');
return new Response(`Hello ${name}`);
}

Dynamically Set header for all requests ( React, super-agent )

I would like an elegant way to dynamically set a Authentication header on all requests. My current solution is using a superagent-defaults package but it dooesn't handle dynamic headers
Take the sample code below
superagentWrapper.js
import defaults from 'superagent-defaults';
import localStorage from 'localStorage';
const superagent = defaults();
superagent
.set('Authorization', `Bearer ${localStorage.getItem('access_token')}`);
export default superagent;
api.js
import request from '../../../utils/superagentWrapper';
ExampleAuthenticatedCall: (x) => {
return new Promise((resolve, reject) => {
request
.post(sources.exampleCall())
.accept('json')
.set('Content-Type', 'application/json')
.timeout(40000)
.send({ exampleCall: "xxx"})
.end((error, res) => {
res.body.error ? reject(res.body.error) : resolve(res.body);
});
});
},
So the issue is that my superAgentWrapper is required by all my api files at the time of page load. This means that the example works fine as long as the access_token never changes ( Which it does do potentially multiple times before a page refresh ).
I've found a solution to this issue here https://www.crowdsync.io/blog/2017/10/16/setting-defaults-for-all-your-superagent-requests/.
Whilst this could potentially work an ideal solution would be to dynamically mirror all the methods the request library has rather than manually having to define them ( new methods may be added / removed in the future so this approach is a little brittle ).
Perhaps anyone could point me in the right direction of how this might be possible perhaps by using proxies / reflection?
Use the standard superagent, and instead of returning the same request, return a function that generates a new request, and gets the current token from the localStorage:
import request from 'superagent';
import localStorage from 'localStorage';
const superagent = () => request
.set('Authorization', `Bearer ${localStorage.getItem('access_token')}`);
export default superagent;
Usage (see request and promises):
import request from '../../../utils/superagentWrapper';
AuthenticatedCall: (x) => request() // generate a new request
.get(sources.AuthenticatedCall(x)) // a request returns a promise by default
If you don't won't to convert the wrapper to a function, you can use a proxy with a get handler. The proxy will produce a new request, with the current token, whenever it's called:
import request from 'superagent';
import localStorage from 'localStorage';
const handler = {
get: function(target, prop, receiver) {
return request
.set('Authorization', `Bearer ${localStorage.getItem('access_token')}`)[prop];
}
};
const requestProxy = new Proxy(request, handler);
export default requestProxy;
Usage (see request and promises):
import request from '../../../utils/superagentWrapper';
AuthenticatedCall: (x) => request // generate a new request
.get(sources.AuthenticatedCall(x)) // a request returns a promise by default

Get page URL parameters from a service worker

How do I get page URL with parameters from a service worker?
I have tried self.registration.scope but that doesn't include the parameters.
I'm not clear as to whether you're asking about getting the service worker script's URL, or the URLs of all of the client pages that are open under the service worker's scope. So... here's how to do both:
// Get a URL object for the service worker script's location.
const swScriptUrl = new URL(self.location);
// Get URL objects for each client's location.
self.clients.matchAll({includeUncontrolled: true}).then(clients => {
for (const client of clients) {
const clientUrl = new URL(client.url);
}
});
In either of those cases, once you have a URL object, you can use its searchParams property if you're interested in the query parameters:
if (url.searchParams.get('key') === 'value') {
// Do something if the URL contains key=value as a query parameter.
}
You can get waiting.scriptURL or active.scriptURL, pass result to URL() constructor, get .search property of object
navigator.serviceWorker.register("sw.js?abc=123")
.then(function(reg) {
const scriptURL = reg.waiting && reg.waiting.scriptURL || reg.active.scriptURL;
const url = new URL(scriptURL);
const queryString = url.search;
console.log(queryString);
}).catch(function(err) {
console.log("err", err);
});
ServiceWorker.scriptURL can give you the URL and its parameters as well.
Then, the following step is to get a ServiceWorker object, and it depends on where you would like to use the URL parameters.
In the worker script, use self.serviceWorker.scriptURL. e.g.
const searchParams = new URLSearchParams(self.serviceWorker.scriptURL);
In the page script, use scriptURL with navigator.serviceWorker.ready. e.g.
const serviceWorkerRegistration = await navigator.serviceWorker.ready;
const activeServiceWorker = serviceWorkerRegistration.active;
const searchParams = new URLSearchParams(activeServiceWorker.scriptURL);
However, you might want to get a registration object from register API, but the above code snippet should work as well.

Categories

Resources