Mock different api response based on input with Axios - javascript

I'm building some e2e test for my Vuejs application.
The framework I'm using is Nightwatch along with the http library Axios (and the relative plugin for mocking: Axios-mock-adapter) and my current process is to have a file that intercepts all the api, and a file that return the reponse object:
So for example, if I want to mock /api/sources:
mock.onGet(/my.url\/api\/sources/).reply(() =>
[200, ResponseObject.getSources],
);
And in the reponse object file I have:
const getSources = {
files: [
{
id: 'bogus',
fileName: 'bogus',
fileUrl: 'http://bogus.com/1',
size: 400,
uploadedTime: '2018-05-24 10:56:27',
sourceContact: 'boguscontact',
isFolder: false,
}, {
id: 'bogus2',
fileName: 'bogus 2',
fileUrl: 'http://bogus.com/2',
size: 500,
uploadedTime: '2018-05-24 10:56:27',
sourceContact: 'boguscontact',
isFolder: false,
}, {
id: 'bogus3',
fileName: 'bogus 3',
fileUrl: 'http://bogus.com/3',
size: 600,
uploadedTime: '2018-05-24 10:56:27',
sourceContact: 'boguscontact',
isFolder: false,
},
],
};
With this set up I have a very annoying problem:
Sometimes I have to return different object through the same api call, for example, if the file has a property ready and I want to test the user-flow to prepare a file to be ready I need to return the file with ready: false the first time, then I add some parameters, and then I have to return the file with ready: true. How can I do that?
Another example would be getting a single source file. I have the same api call api/source/:sourceId but when the source has ready: true it needs to have more parameters compare if the source has ready: false, but I don't know how to simulate that behaviour without a backend.
Right now the only thing I can do is to have a different response based on the query parameters:
mock.onGet(/dmd\.mocked\/api\/files/).reply((config) => {
if (typeof config.params !== 'undefined') {
switch (config.params.status) {
case 'queued':
return [200, ResponseObject.queuedFilesList];
case 'processing':
return [200, ResponseObject.processingFilesList];
default:
return [506];
}
} else {
return [200, ResponseObject.queuedFilesList];
}
});
but this approach works only if I make the call with different parameters. If I make the call without any parameters I don't know how to diversify the response.

Related

How to drop the query parameters after a redirect with NextJS?

In NextJS,
how can I use redirect to turn URL like /page?foo=bar into /page/bar ?
I read https://nextjs.org/docs/api-reference/next.config.js/redirects but couldn't find a solution.
What I have today is:
{
source: '/page',
has: [
{
type: 'query',
key: 'foo'
}
],
destination: '/page/:foo',
permanent: true
}
but that make /page?foo=bar into /page/bar?foo=bar.
How can I drop the query ?
Edit:
So I realized that this doesn't event work at all with Netlify.
I tried to follow https://docs.netlify.com/routing/redirects/ but I have the same problem with the query parameters staying.
You can use middleware.
Just parse the query parameter yourself and add redirection.
Store a file _middleware.ts below the pages directory:
export async function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
if (// Your-thing )
return NextResponse.redirect(//Your-url);
return NextResponse.next();
}
Maybe there is a different way, I don't know, but it doesn't matter.
At least when using Vercel, you can archive this by repeating the parameters from the has in the destination but leaving the value empty.
E.g.:
{
source: '/page',
has: [
{
type: 'query',
key: 'foo'
}
],
destination: '/page/:foo?foo=',
permanent: true
}
Parameters that already exit in the destination won't be copied over and parameters with an empty value in the destination will be removed completely.

Unable to get data from created() to data() in VueJS 2

I am fetching data from API inside the created method and i want to use these data in the page.
Here is my code.
created(){
let id = this.$route.params.id
let videos;
this.$axios.get(this.$axios.defaults.apiURL + 'v1.0.0/tips/' +id,).then((response) => {
this.videos = response.data.data;
}, (error) => {
toast.$toast.error('Something went wrong! Please try again', {
position: 'top'
})
});
},
data(){
let videos = this.videos;
return {
video: {
sources: [{
src: videos.video_url,
type: 'video/mp4'
}],
options: {
autoplay: true,
volume: 0.6,
poster: videos.thumbnail
}
}
}
}
I am getting error that thumbnail and video_url is not defined. This 2 values are coming from API response. How can i solve this? Thanks
I can see two obvious issues with your code (without seeing it in action):
created is a synchronous hook, but your axios request is returning a promise. Instead of waiting for the promise, you are immediately trying to show the result, hence the issue you are encountering - the data just hasn't arrived yet.
Your use of this seems a bit chaotic (i.e. let videos = this.videos - where would this.videos come from? The only other 'videos' is declared inside of a different function with let)
There are multiple ways to solve this, depending on what you want to show while you are fetching the data and what type of component this is - if you want to show a spinner while you are waiting for the request to be answered, or if you just want to show some progress bar on the previous page and only enter this one once it's loaded.
In-component loading
In the first case, I would suggest setting a variable or using a loader management solution like vue-wait. Your code could look like this then:
data() {
return {
loading: true,
videos: null,
}
},
computed: {
video() {
return this.videos ? {
sources: [{
src: this.videos.video_url,
type: 'video/mp4'
}],
options: {
autoplay: true,
volume: 0.6,
poster: this.videos.thumbnail
}
} : null
}
},
methods: {
fetch() {
let id = this.$route.params.id
this.$axios.get(this.$axios.defaults.apiURL + 'v1.0.0/tips/' + id, ).then((response) => {
this.videos = response.data.data;
}, (error) => {
toast.$toast.error('Something went wrong! Please try again', {
position: 'top'
})
}).finally(() => (this.loading = false));
},
},
created() {
this.fetch()
},
In your template, you would add somewhere v-if=!loading to make sure that the request has finished before you try to show something
Data-fetching before entering page
If this is a page though, you could request the data in beforeRouteEnter - there's a whole article that explains the principle on the vue site

How can I get dynamic data from within gatsby-config.js?

Consider the following code within gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: `${dynamicURL}`, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
}
As you can see above, the URL for the source plugin is something that I need to be dynamic. The reason for this is that the file URL will change every time it's updated in the CMS. I need to query the CMS for that field and get its CDN URL before passing to the plugin.
I tried adding the following to the top of gatsby-config.js but I'm getting errors.
const axios = require("axios")
let dynamicURL = ""
const getBrands = async () => {
return await axios({
method: "get",
url: "https://some-proxy-url-that-returns-json-with-the-csv-file-url",
})
}
;(async () => {
const brands = await getBrands()
dynamicURL = brands.data.summary.url
})()
I'm assuming this doesn't work because the config is not waiting for the request above to resolve and therefore, all we get is a blank URL.
Is there any better way to do this? I can't simply supply the source plugin with a fixed/known URL ahead of time.
Any help greatly appreciated. I'm normally a Vue.js guy but having to work with React/Gatsby and so I'm not entirely familiar with it.
I had similar requirement where I need to set siteId of gatsby-plugin-matomo dynamically by fetching data from async api. After searching a lot of documentation of gatsby build lifecycle, I found a solution.
Here is my approach -
gatsby-config.js
module.exports = {
siteMetadata: {
...
},
plugins: {
{
resolve: 'gatsby-plugin-matomo',
options: {
siteId: '',
matomoUrl: 'MATOMO_URL',
siteUrl: 'GATSBY_SITE_URL',
dev: true
}
}
}
};
Here siteId is blank because I need to put it dynamically.
gatsby-node.js
exports.onPreInit = async ({ actions, store }) => {
const { setPluginStatus } = actions;
const state = store.getState();
const plugin = state.flattenedPlugins.find(plugin => plugin.name === "gatsby-plugin-matomo");
if (plugin) {
const matomo_site_id = await fetchMatomoSiteId('API_ENDPOINT_URL');
plugin.pluginOptions = {...plugin.pluginOptions, ...{ siteId: matomo_site_id }};
setPluginStatus({ pluginOptions: plugin.pluginOptions }, plugin);
}
};
exports.createPages = async function createPages({ actions, graphql }) {
/* Create page code */
};
onPreInit is a gatsby lifecycle method which is executing just after plugin loaded from config. onPreInit lifecycle hook has some built in methods.
store is the redux store where gatsby is storing all required information for build process.
setPluginStatus is a redux action by which plugin data can be modified in redux store of gatsby.
Here the important thing is onPreInit lifecycle hook has to be called in async way.
Hope this helps someone in future.
Another approach that may work for you is using environment variables as you said, the URL is known so, you can add them in a .env file rather than a CSV.
By default, Gatsby uses .env.development for gatsby develop and a .env.production for gatsby build command. So you will need to create two files in the root of your project.
In your .env (both and .env.development and .env.production) just add:
DYNAMIC_URL: https://yourUrl.com
Since your gatsby-config.js is rendered in your Node server, you don't need to prefix them by GATSBY_ as the ones rendered in the client-side needs. So, in your gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-source-fetch`,
options: {
name: `brands`,
type: `brands`,
url: process.env.DYNAMIC_URL, // This is the part I need to be dynamic at run/build time.
method: `get`,
axiosConfig: {
headers: { Accept: "text/csv" },
},
saveTo: `${__dirname}/src/data/brands-summary.csv`,
createNodes: false,
},
},
],
It's important to avoid tracking those files in your Git repository since you don't want to expose this type of data.

Flutter - How to get a Javascript callback in WebView?

I am using a WebView to load a local HTML file that contains the callback -
function onReward(data){
console.log("onReward: " + data.earnedThisSession);
Survey.postMessage(data.earnedThisSession);
}
This callback gets triggered when a user completes an action, I am currently facing two problems
The data.earnedThisSession returned by the function differs from every user and I want to get this as a variable in my dart code to reward my user
My WebView dependency is not printing console messages in my debug console
This is my JavascriptChannel -
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Survey',
onMessageReceived: (JavascriptMessage message) {
print(message.message);
}),
].toSet();
This is my WebviewScaffold -
FutureBuilder<String>(
future: _loadLocalHTML(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(),
body: WebView(
initialUrl: Uri.dataFromString(
snapshot.data,
mimeType: 'text/html',
encoding: Encoding.getByName('utf-8'),
).toString(),
javascriptChannels: jsChannels,
javascriptMode: JavascriptMode.unrestricted,
),
);
}
How do I use evalJavascript in this to fetch my reward data?
Changing Survey.postMessage() to window.Survey.postMessage() may work. Im not sure how to use evalJavascipt with WebviewScaffold, i'm using like this:
final _webView = new FlutterWebviewPlugin();
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Survey',
onMessageReceived: (JavascriptMessage message) {
print(message.message);
}),
].toSet();
_webView.launch(url, jsChannels);
_webView.onStateChanged.listen((event) {
if (event.type == WebViewState.finishLoad) {
_webView.evalJavascript('Your Js Code' +
'window.Survey.postMessage("Your Return Mes.")');
}
});

Download pdf using remote method

I intend to download a dynamically generated pdf file using a remote method, the file exists at a particular path and I am using return type "file". My implementation is:
customer.downloadFile = function downloadFile(userId, res, cb){
var reader = fs.createReadStream(__dirname + '/../document.pdf');
cb(null, reader, 'application/pdf');
};
customer.remoteMethod(
'downloadFile',
{
isStatic: true,
accepts: [
{ arg: 'id', type: 'string', required: true },
{ arg: 'res', type: 'object', 'http': { source: 'res' } }
],
returns: [
{ arg: 'body', type: 'file', root: true },
{ arg: 'Content-Type', type: 'string', http: { target: 'header' } }
],
http: {path: '/:id/downloadFile', verb: 'get'}
}
);
The issue with the above code is that the browser although displays the beautiful pdf file container, but instead of the file following error is shown:
Please point out as to what is wrong with the code and how to correct.
Got lead from this URL: https://github.com/strongloop/loopback-swagger/issues/34
Got that working with following:
fs.readFile(fileName, function (err, fileData) {
res.contentType("application/pdf");
res.status(200).send(fileData);
if (!err) {
fs.unlink(fileName);
}
else {
cb(err);
}
});
You should use loopback-component-storage to manage downloadable files.
Files are grouped in so-called containers (Basically, a folder with a single level of hierarchy, not more).
How it is done:
Create a model called container for instance.
Create a storage datasource that uses as connector the loopback-component-storage
Give to container model the datasource storage
That's it. You can upload and download files to/from your container.
With a container, you can store files to a local filesystem, or move later to Cloud solutions.

Categories