I have just started to use Pactum to unit test my API endpoints.
In one of my API endpoints, I am calling an external API and I have tried to mock this external API. My code clearly calls the external API.
However, my test fails and I get an error (the actual endpoint 32 is just a sample, it is not hardcoded in the actual codes):
Interaction not exercised: GET - https://62b32eab4f851f87f4563d9b.mockapi.io/user/32
Here is a sample of my test:
it('should update record', async () => {
await spec()
.post('http://localhost:3000/user')
.withJson({
"name": "helen",
"city": "London"
})
.useInteraction({
request: {
method: 'GET',
path: 'https://62b32eab4f851f87f4563d9b.mockapi.io/user/32'
},
response: {
status: 200,
body: [
{"name":"Jerald Labadie","country":"Buckinghamshire","city":"Lake Junebury","email":"Alf_Schneider81#gmail.com","id":"32"}
]
}
})
.expectStatus(200)
.wait(3000)
})
I know I can just connect to mockapi for the mock, but is there any way to resolve this and do this locally?
While using mocks, we need to divert the traffic from your service to the local mock server. Can be achieved simply by using environment variables.
const mock_url = process.env.MOCK_URL || 'https://<key>.mockapi.io';
Now, start the sever by initiating the MOCK_URL environment variable to the pactum's mock server url. Assuming pactum mock server is running on localhost 9393.
export MOCK_URL=http://localhost:9393 && node server.js
Now update your interaction to handle the traffic.
it('should update record', async () => {
await spec()
.useInteraction({
request: {
method: 'GET',
path: '/user/32'
},
response: {
status: 200,
body: [ {"name":"Jerald Labadie","country":"Buckinghamshire","city":"Lake Junebury","email":"Alf_Schneider81#gmail.com","id":"32"} ]
}
})
.post('http://localhost:3000/user')
.withJson({
"name": "helen",
"city": "London"
})
.expectStatus(200)
})
Related
all,
I am writing a front-end of my app, using Vue. I need to request some information from the back-end, launched on the same server.
I added proxy like this:
module.exports = {
devServer: {
port: 2611,
proxy: {
'/api/markets/light': {
target: 'http://localhost:2610/markets/light',
ws: true,
logLevel: 'debug',
pathRewrite: {
'^/api/markets/light': '/markets/light',
},
},
},
},
}
(I've tried different combinations of the parameters, like excluding pathRewrite, '^/api/...' instead of '/api/...', etc.)
This is how data is requested from the code:
created() {
axios.get(this.$store.getters.marketsLightLink)
.then(response => this.markets = response.data)
.catch(error => console.log(error));
}
marketsLightLink simply concatenates strings:
const store = new Vuex.Store({
state: {
marketDataHost: '/api',
markets: {
markets: '/markets',
marketLight: '/markets/light',
},
},
getters: {
marketsLightLink(state) {
return state.marketDataHost + state.markets.marketLight;
},
},
});
So when i open the page, where i should see the results of the request, i just see 404 error in the browser's console and no data downloaded. At the same time I see, that the link is proxied correctly:
[HPM] Rewriting path from "/api/markets/light" to "/markets/light"
[HPM] GET /api/markets/light ~> http://localhost:2610/markets/light
And when i press the resulting link, the requested information is shown in the browser.
Can anyone help me, what am I doing wrong please?
I want to send a POST request to an external API with axios in a nuxt projekt where I use the nuxt auth module.
When a user is authenticated axios seems to automatically add an authorization header (which is fine and often required for calls to my backend API). However, when doing calls to an external API the header might not be accepted and cause the call to fail.
Is there any way to specify for which URLs the auth header should be added or excluded?
Here are the configurations of the auth and axios module in my nuxt.config
// Axios module configuration
axios: {
baseURL: '//localhost:5000',
},
// Auth module configuration
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'token' },
logout: { url: '/auth/logout', method: 'delete' },
user: { url: '/auth/user', method: 'get', propertyName: 'user' },
},
},
},
}
Some more background:
In my particular usecase I want to upload a file to an Amazon S3 bucket, so I create a presigned upload request and then want to upload the file directly into the bucket. This works perfectly fine as long as the user is not authenticated.
const { data } = await this.$axios.get('/store/upload-request', {
params: { type: imageFile.type },
})
const { url, fields } = data
const formData = new FormData()
for (const [field, value] of Object.entries(fields)) {
formData.append(field, value)
}
formData.append('file', imageFile)
await this.$axios.post(url, formData)
I tried to unset the Auth header via the request config:
const config = {
transformRequest: (data, headers) => {
delete headers.common.Authorization
}
}
await this.$axios.post(url, formData, config)
This seems to prevent all formData related headers to be added. Also setting any header in the config via the headers property or in the transformRequest function does not work, which again causes the call to the external API to fail obviously (The request will be sent without any of these specific headers).
As I'm working with the nuxt axios module I'm not sure how to add an interceptor to the axios instance as described here or here.
Any help or hints on where to find help is very much appreciated :)
Try the following
Solution 1, create a new axios instance in your plugins folder:
export default function ({ $axios }, inject) {
// Create a custom axios instance
const api = $axios.create({
headers: {
// headers you need
}
})
// Inject to context as $api
inject('api', api)
}
Declare this plugin in nuxt.config.js, then you can send your request :
this.$api.$put(...)
Solution 2, declare axios as a plugin in plugins/axios.js and set the hearders according to the request url:
export default function({ $axios, redirect, app }) {
const apiS3BaseUrl = // Your s3 base url here
$axios.onRequest(config => {
if (config.url.includes(apiS3BaseUrl) {
setToken(false)
// Or delete $axios.defaults.headers.common['Authorization']
} else {
// Your current axios config here
}
});
}
Declare this plugin in nuxt.config.js
Personally I use the first solution, it doesn't matter if someday the s3 url changes.
Here is the doc
You can pass the below configuration to nuxt-auth. Beware, those plugins are not related to the root configuration, but related to the nuxt-auth package.
nuxt.config.js
auth: {
redirect: {
login: '/login',
home: '/',
logout: '/login',
callback: false,
},
strategies: {
...
},
plugins: ['~/plugins/config-file-for-nuxt-auth.js'],
},
Then, create a plugin file that will serve as configuration for #nuxt/auth (you need to have #nuxt/axios installed of course.
PS: in this file, exampleBaseUrlForAxios is used as an example to set the variable for the axios calls while using #nuxt/auth.
config-file-for-nuxt-auth.js
export default ({ $axios, $config: { exampleBaseUrlForAxios } }) => {
$axios.defaults.baseURL = exampleBaseUrlForAxios
// I guess that any usual axios configuration can be done here
}
This is the recommended way of doing things as explained in this article. Basically, you can pass runtime variables to your project when you're using this. Hence, here we are passing a EXAMPLE_BASE_URL_FOR_AXIOS variable (located in .env) and renaming it to a name that we wish to use in our project.
nuxt.config.js
export default {
publicRuntimeConfig: {
exampleBaseUrlForAxios: process.env.EXAMPLE_BASE_URL_FOR_AXIOS,
}
}
I'm creating a REST API using AWS CDK version 1.22 and I would like to document my API using CDK as well, but I do not see any documentation generated for my API after deployment.
I've dived into aws docs, cdk example, cdk reference but I could find concrete examples that help me understand how to do it.
Here is my code:
const app = new App();
const api = new APIStack(app, 'APIStack', { env }); // basic api gateway
// API Resources
const resourceProps: APIResourceProps = {
gateway: api.gateway,
}
// dummy endpoint with some HTTP methods
const siteResource = new APISiteStack(app, 'APISiteStack', {
env,
...resourceProps
});
const siteResourceDocs = new APISiteDocs(app, 'APISiteDocs', {
env,
...resourceProps,
});
// APISiteDocs is defined as follow:
class APISiteDocs extends Stack {
constructor(scope: Construct, id: string, props: APIResourceProps) {
super(scope, id, props);
new CfnDocumentationVersion(this, 'apiDocsVersion', {
restApiId: props.gateway.restApiId,
documentationVersion: config.app.name(`API-${config.gateway.api.version}`),
description: 'Spare-It API Documentation',
});
new CfnDocumentationPart(this, 'siteDocs', {
restApiId: props.gateway.restApiId,
location: {
type: 'RESOURCE',
method: '*',
path: APISiteStack.apiBasePath,
statusCode: '405',
},
properties: `
{
"status": "error",
"code": 405,
"message": "Method Not Allowed"
}
`,
});
}
}
Any help/hint is appreciated, Thanks.
I have tested with CDK 1.31 and it is possible to use the CDK's default deployment option and also add a document version to the stage. I have used the deployOptions.documentVersion in rest api definition to set the version identifier of the API documentation:
import * as cdk from '#aws-cdk/core';
import * as apigateway from "#aws-cdk/aws-apigateway";
import {CfnDocumentationPart, CfnDocumentationVersion} from "#aws-cdk/aws-apigateway";
export class CdkSftpStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const documentVersion = "v1";
// create the API
const api = new apigateway.RestApi(this, 'books-api', {
deploy: true,
deployOptions: {
documentationVersion: documentVersion
}
});
// create GET method on /books resource
const books = api.root.addResource('books');
books.addMethod('GET');
// // create documentation for GET method
new CfnDocumentationPart(this, 'doc-part1', {
location: {
type: 'METHOD',
method: 'GET',
path: books.path
},
properties: JSON.stringify({
"status": "successful",
"code": 200,
"message": "Get method was succcessful"
}),
restApiId: api.restApiId
});
new CfnDocumentationVersion(this, 'docVersion1', {
documentationVersion: documentVersion,
restApiId: api.restApiId,
description: 'this is a test of documentation'
});
}
}
From what I can gather, if you use the CDK's default deployment options which create stage and deployment on your behalf, it won't be possible to append the stage with a documentation version set.
Instead, the solution would be to set the RESTAPI's option object to deploy:false and define the stage and deployment manually.
stack.ts code
import * as cdk from '#aws-cdk/core';
import * as apigateway from '#aws-cdk/aws-apigateway';
import { Stage, Deployment, CfnDocumentationPart, CfnDocumentationVersion, CfnDeployment } from '#aws-cdk/aws-apigateway';
export class StackoverflowHowToDocumentRestApiUsingAwsCdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// create the API, need to not rely on CFN's automatic deployment because we need to
// make our own deployment to set the documentation we create
const api = new apigateway.RestApi(this, 'books-api',{
deploy: false
});
// create GET method on /books resource
const books = api.root.addResource('books');
books.addMethod('GET');
// // create documentation for GET method
const docpart = new CfnDocumentationPart(this, 'doc-part1', {
location: {
type: 'METHOD',
method: 'GET',
path: books.path
},
properties: JSON.stringify({
"status": "successful",
"code": 200,
"message": "Get method was succcessful"
}),
restApiId: api.restApiId
});
const doc = new CfnDocumentationVersion(this, 'docVersion1', {
documentationVersion: 'version1',
restApiId: api.restApiId,
description: 'this is a test of documentation'
});
// not sure if this is necessary but it made sense to me
doc.addDependsOn(docpart);
const deployment = api.latestDeployment ? api.latestDeployment: new Deployment(this,'newDeployment',{
api: api,
description: 'new deployment, API Gateway did not make one'
});
// create stage of api with documentation version
const stage = new Stage(this, 'books-api-stage1', {
deployment: deployment,
documentationVersion: doc.documentationVersion,
stageName: 'somethingOtherThanProd'
});
}
}
OUTPUT:
Created a feature request for this option here.
I had the same exact problem. The CfnDocumentationVersion call has to occur after you create all of your CfnDocumentationPart. Using your code as an example, it should look something like this:
class APISiteDocs extends Stack {
constructor(scope: Construct, id: string, props: APIResourceProps) {
super(scope, id, props);
new CfnDocumentationPart(this, 'siteDocs', {
restApiId: props.gateway.restApiId,
location: {
type: 'RESOURCE',
method: '*',
path: APISiteStack.apiBasePath,
statusCode: '405',
},
properties: JSON.stringify({
"status": "error",
"code": 405,
"message": "Method Not Allowed"
}),
});
new CfnDocumentationVersion(this, 'apiDocsVersion', {
restApiId: props.gateway.restApiId,
documentationVersion: config.app.name(`API-${config.gateway.api.version}`),
description: 'Spare-It API Documentation',
});
}
}
I have a very basic nuxt.js application using JSON in a local db.json file, for some reason the generated static site links leading to network error, but I can access them from the url or page refresh.
nuxt config
generate: {
routes () {
return axios.get('http://localhost:3000/projects')
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
},
main root index page
data() {
return {
projects: []
}
},
async asyncData({$axios}){
let projects = await $axios.$get('http://localhost:3000/projects')
return {projects}
}
single project page
data() {
return {
id: this.$route.params.id
}
},
async asyncData({params, $axios}){
let project = await $axios.$get(`http://localhost:3000/projects/${params.id}`)
return {project}
}
P.S. I have edited the post with the code for the main and single project page
Issues with server-side requests of your application are caused by conflicts of ports on which app and json-server are running.
By default, both nuxt.js and json-server run on localhost:3000 and requests inside asyncData of the app sometimes do not reach correct endpoint to fetch projects.
Please, check fixed branch of your project's fork.
To ensure issue is easily debuggable, it is important to separate ports of API mock server and app itself for dev, generate and start commands.
Note updated lines in nuxt.config.js:
const baseURL = process.env.API_BASE_URL || 'http://localhost:3000'
export default {
server: {
port: 3001,
host: '0.0.0.0'
},
modules: [
['#nuxtjs/axios', {
baseURL
}]
],
generate: {
async routes () {
return axios.get(`${baseURL}/projects`)
.then((res) => {
return res.data.map((project) => {
return '/project/' + project.id
})
})
}
}
}
This ensures that API configuration is set from a single source and, ideally, comes from environmental variable API_BASE_URL.
Also, app's default port has been changed to 3001, to avoid conflict with json-server.
asyncData hooks have been updated accordingly to pass only necessary path for a request. Also, try..catch blocks are pretty much required for asyncData and fetch hooks, to handle error correctly and access error specifics.
I'm new to the MEAN stack and I'm having some trouble with routing...
I have a module called "applications".
the APIs i want on the server side are:
get: http://localhost:3000/api/applications/(_appid)
getByMakeathonId: http://localhost:3000/api/applications/makeathons/(_mkid)
Applications Service
function ApplicationsService($resource) {
return $resource('api/applications/:path/:applicationId', {
path: '#path',
applicationId: '#id'
}, {
get: {
method: 'GET',
params: {
path: '',
applicationId: '#_id'
}
},
getByMakeathonId: {
method: 'GET',
params: {
path: 'makeathon',
applicationId: '#_id'
}
},
update: {
method: 'PUT'
}
});
Server Routing
app.route('/api/applications').post(applications.create);
app.route('/api/applications').all(applicationsPolicy.isAllowed)
.get(applications.list);
app.route('/api/applications/makeathon/:makeathonId').all(applicationsPolicy.isA llowed)
.get(applications.applicationByMakeathonID);
1) what I'm getting when I call $save and the object and the save is successful, there is a call for .get, and the request URL is: http://localhost:3000/api/applications//56f15736073083e00e86e170 (404 not found)
the problem here of course is the extra '/' - How do I get rid of it.
2) when I call getByMakeathonId, the request url is: http://localhost:3000/api/applications/makeathon?id=56e979f1c6687c082ef52656 400 (Bad Request)
I do I configure so that I'll get the two requests that I want?
10x!
You are getting the repeated // in your request url because you have declared that there will be a :path in your applications resource, and you are providing an empty string to interpolate there.
As $resource is meant to provide RESTful interaction with your API, I think the most appropriate approach would be to have separate $resources to deal with applications and makeathons. Something like this:
For applications:
function ApplicationsService($resource) {
return $resource('api/applications/:applicationId', {
applicationId: '#id'
}, {
update: {
method: 'PUT'
}
});
}
For makeathons:
function MakeathonsService($resource) {
return $resource('api/applications/makeathons/:makeathonId', {
makeathonId: '#id'
}
});
}
/** your server route would then be updated to
* app.route('/api/applications/makeathons/:makeathonId')...
*/