How to write integration tests for a multi-tenant app with subdomain.
I am working on a multi-tenant application. I am trying to write integration tests for a subdomain but can't seem to get it working.
Here is what I have done:
test file
import supertest from 'supertest';
import http from 'http';
import app from '../../../app';
const baseUrl = 'censio.lvh.me:7000';
describe('Censio tests', () => {
let server;
let request;
beforeAll((done) => {
server = http.createServer(app);
server.listen(7000, done);
request = supertest(server);
});
afterAll((done) => {
server.close(done);
});
describe('Signin', () => {
it('should respond with missing fields ...', async () => {
const response = await request
.post(`${baseUrl}/signin`)
.send({})
.set('Accept', 'application/json');
expect(response.status).toBe(400);
});
});
});
app.js
import '#babel/polyfill';
import bodyParser from 'body-parser';
import cookieParser from 'cookie-parser';
import express from 'express';
import subdomain from 'express-subdomain';
import censioRouter from './tenants/censio';
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
// Subdomain definitions
app.use(subdomain('censio', censioRouter));
app.get('*', (req, res) => res.status(200).json({ message: 'Project started' }));
export default app;
When I check response.text I see
<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"utf-8\">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /:7000censio.lvh.me/signin</pre>
</body>
</html>
It puts the post in front. When I get rid of the port, I receive this error: connect ECONNREFUSED 127.0.0.1:80
What do I do?
I finally got it to work. I am posting this for whoever may need this solution in the future.
First, I gained full understanding of how to use a custom domain to test my application locally. I had to modify my /etc/hosts file. This post helped me with that.
I modified my tests (as seen in my question above). Instead of passing app to supertest, I passed the base url I wanted i.e my url containing my subdomain(censio.mydomain.com:myport). This is how it looks like now.
import supertest from 'supertest';
import http from 'http';
import app from '../../../app';
describe('Censio tests', () => {
describe('Signin', () => {
let server;
let request;
beforeAll((done) => {
server = http.createServer(app);
server.listen(7000, done);
request = supertest('http://censio.mydomain.com:7000');
});
afterAll((done) => {
server.close(done);
});
it('should respond with missing fields ...', async () => {
const response = await request
.post('/signin')
.send({})
.set('Accept', 'application/json');
expect(response.status).toBe(400);
});
});
});
Notice I changed where I am calling my before and after hooks.
I hope this helps you as it helped me.
Related
Using the Following Stack Express,Vue,SQL,Axios
GET request is working fine in postman as well from Axios
POST request created error in both attached screenshots
To make sure the Backend is working fine I have tried sending the Data directly from
<form action="url" method="POST">
it is Working fine and data is storing in the database
I have Tried few workaround like disabling SSL setting in postman and Played with proxy setting Also having CORS enabled in the backend and tried some Allow content and header things. Nothing worked
Not able to figure out the Problem in the POST Request. Please Help
--Request Error in the browser from Axios ----
Axios Browser Error
-postman Error when doing POST Request---
Postman Error
---Backend Index.js file---
// const express = require("express");
"use strict";
import express from "express";
const app = express();
import cors from "cors";
//listening on this port
app.listen(3000);
app.use(cors()); // to get Data from Other Domains
app.get("/", function (req, res) {
res.send("Application Started");
console.log("Application Started");
});
//Routes
app.use("/product", require("./routes/product"));
---product.js routes files---
import express from "express";
const router = express.Router();
import bodyParser from "body-parser";
//Middleware
router.use(bodyParser.urlencoded({ extended: true })); // To Parse the body data
//Importing Main Controller
import conProduct from "../controllers/ConProduct";
//Defining functions as per Routes
router.post("/add", conProduct.add); //Showing all the products
router.get("/get", conProduct.get); //Showing all the products
//Exporting Router
module.exports = router;
---Controller for Product file ConProducts.js ---
import sqlConfig from "../database/dbConfig";
let sql = sqlConfig.mysql_pool;
exports.add = (req, res) => {
console.log(req.body);
const { name, shortDescription, price, discount } = req.body;
let addQuery =
"INSERT INTO products(name,short_description,price,discount) VALUES('" +
name +
"','" +
shortDescription +
"','" +
price +
"','" +
discount +
"');";
sql.query(addQuery, (err, result) => {
if (err) throw err;
console.log(result);
res.send("product uploaded");
});
};
--Frontend axios Request --
let formData = {
name: this.name,
shortDescription: this.shortDescription,
price: this.price,
discount: this.discount,
};
console.log(formData);
axios
.post("/product/add", formData)
.then((res) => console.log(res))
.catch((error) => console.log(error));
I got the Answer I was missing a middleware app.use(bodyParser.json) in index.js and because of that there was no value going into database and thus, there was a network error.
I realized that, you are sending a PUT request to backend but your API controller receiving POST requests. So, Don't they have to be same type of request ?
Req.body is not accessible in the routes while making a post request. It would be highly appreciative of someone if he/she help me getting through it. Here is screenshot of my microservice.test.js file. Am I missing something?
import request from "supertest";
import mongoose from "mongoose";
import config from "../config/env";
import routes from "../server/routes";
import { parseResponse, doRequest } from "../server/utils/helperFunctions";
const app = express();
app.use("/", routes);
jest.setTimeout(30000);
The code provided doesn't provide much insight, as I would expect all of the handling of the request to be in your route handler. Is the issue that you are unable to access the body when running tests with supertest? Or that it isn't working at all. More information would be very helpful.
If it is a supertest issue, I would recommend checking out the docs for good examples. Here is one I pulled directly from the NPM site where they POST some data with a request body and then verify the response body:
describe('POST /user', function() {
it('user.name should be an case-insensitive match for "john"', function(done) {
request(app)
.post('/user')
.send('name=john') // x-www-form-urlencoded upload
.set('Accept', 'application/json')
.expect(function(res) {
res.body.id = 'some fixed id';
res.body.name = res.body.name.toLowerCase();
})
.expect(200, {
id: 'some fixed id',
name: 'john'
}, done);
});
});
Also, if you are trying to test your server you should probably import your server code instead of creating a new express instance. For example, in your server code you'll have something like this:
server.js
const express = require('express');
const app = express();
app.use('/', ...) // middleware/route config
...
module.exports = app;
Your server would then use this server like this:
index.js
const app = require('./server')
const port = 4000
app.listen({ port }, () => {
const location = `http://localhost:${port}`
logger.info(`🚀 Server ready at ${location}`)
})
module.exports = app
Now that you have structured your code this way, in your test you can import your server as well (so you are testing your actual server, not a new server that you made up):
server.test.js
const app = require('../../../server');
const request = require('supertest')(app);
describe('Server', () => {
it('does a thing', async () => {
const { body } = await request
.post('http://path/to/test')
.send({ data: 'some data' })
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.expect(200);
expect(body.thing).toBeTrue();
});
});
expressjs version lower than 4 include body parsing middleware
import bodyParser from 'body-parser';
app.use(bodyParser());
example test
it('.post should work with data', function (done) {
var app = express();
app.use(bodyParser());
app.post('/', function(req, res){
res.send(req.body.name);
});
request(app)
.post('/')
.send({ name: 'tobi' })
.expect('tobi', done);
})
I'm just learning Express/React and I'm trying to get set up with routes and basic database connections. I suspect I'm missing something very simple. I've tried to boil it down to the following.
Backend
server.js:
require('dotenv').config({path: '../.env'});
const mysql = require('mysql');
const express = require('express');
const bodyParser = require('body-parser');
const port = process.env.PORT || 5000;
const app = express();
const router = express.Router();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
const users = require('./routes/api/users');
app.use('/api/users', users);
const events = require('./routes/api/events');
app.use('/api/events', events);
const db = mysql.createConnection({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD
});
db.connect(function(err) {
if (err) throw err;
console.log('MySQL Connected!');
})
app.listen(port, () => console.log(`Listening on port ${port}`));
/routes/api/events.js:
const express = require('express');
const router = express.Router();
// GET api/events
router.get('/', (req, res) => {
res.send({id: "1", name: "hi"});
});
module.exports = router;
Frontend
App.js:
import React, { Component } from 'react';
import EventList from './components/EventList';
class App extends Component {
render() {
return (
<div className="App">
<EventList/>
</div>)
}
}
export default App;
/components/EventList.js:
import React, { Component } from 'react';
import axios from 'axios';
class EventList extends Component {
constructor() {
super();
this.state = {
events: []
}
}
componentDidMount() {
axios.get('/api/events')
.then(events => {
this.setState({events: events.data})
})
.catch(err => console.log(err))
}
render() {
var events = this.state.events;
return (
<div>
<p>Events:</p>
<ul>
{ events.map(({ id, name }) => (
<li>{name}</li>
))}
</ul>
</div>
)
}
}
export default EventList;
The error I get is http://localhost:3000/api/events 500 (Internal Server Error). What am I missing? I have truly scoured all of the docs I can find, but I'm not quite getting it.
Edit
I haven't changed anything, but now I'm getting a 404 (Not Found) instead. I had been getting a 500 for awhile, so it wasn't a momentary fluke. I'm not sure what could have changed.
Update
It may help to know that the .env variables I'm pointing to are actually for a remote MySQL database (i.e., DB_HOST != localhost, but a remote URL). Eventually, I'd like to connect the GET call on the events route to that db, but since it doesn't work with what I have here I figured my first issue to solve was upstream. As noted in comments, the PORT var I'm loading in is 3306. When I start the server, it says it's listening on 3306 as expected.
I think you are running your server on port 5000 and front on port 3000. if you request events with http://localhost:5000/api/events instead of /api/events, you would get 200 status code with your json data.
// as is
axios.get('/api/events')
// to be
axios.get('http://localhost:5000/api/events')
You could try typing componentDidMount function like this.
componentDidMount = async () =>{
//like this
}
In addition, I would recommend making the GET ALL its own function so you could just invoke in the componentDidMount function. whenever you run another CRUD action it will automatically update your events for you and instead of setting state every time you can invoke the this.getEvents() again to update it that way.
componentDidMount = async () =>{
this.getEvents()
}
Also you need to add this package (npm install cors) its so you can connect your api to your frontend.
Import it like this in your server.js file
const cors = require('cors')
app.use(cors())
You should add a proxy inside package.json in frontend like this.
"proxy": "http://localhost:5000",
I know my problem may seem not very specific, but I'm having problem describing what's happening because I don't understand it :(
So I've written small express app with ssr (for react) and jwt authentication. The SSR part works nice but the rest is crap.....
import 'babel-polyfill';
import express from 'express';
import bodyParser from 'body-parser';
import logger from 'morgan';
import authRouter from './auth/authRouter';
const app = express();
app.use(express.static('public'));
app.use(logger('dev'));
app.use(bodyParser.json({ type: '*/*' }));
app.use(bodyParser.urlencoded({ extended: false }));
//a lot of irrelevant ssr code
authRouter(app);
app.listen(3000, function(err) {
if (err) {
console.log(err);
}
console.log('main server on port 3000');
});
This is my main server file. My first problem is I don't see ANY console.logs from my files. There's nothing in my terminal. That's the reason I can't see how does my requests look like in my app. I'm testing it using postman like that:
And that's the authRouter I'm using above in main server file:
import express from 'express';
import { signup, signin } from '../controllers/authentication';
import { jwtLogin, localLogin } from '../services/passport';
import passport from 'passport';
passport.use(jwtLogin);
passport.use(localLogin);
//if user is auth'd do not create session for him
const requireAuth = passport.authenticate('jwt', { session: false });
const requireSignin = passport.authenticate('local', { session: false });
const authRouter = function(app) {
app.get('/auth', requireAuth, function(req, res) {
res.send({ hi: 'there' });
});
app.post('/auth/signin', requireSignin, signin); // irrelevant right now
app.post('/auth/signup', signup);
};
export default authRouter;
And that's signup function I'm using in router:
const signup = (req, res, next) => {
console.log('reqqqqqqq', req);
const { email, password, passwordCheck } = req.body; //code crashes here
//some code I deleted for everyones convenience
};
Every request I make my app crashes because req.body is undefined. I can't log req because I can't see any logs. I also tried sending back stringified version of my request body but every time i get "TypeError: Converting circular structure to JSON".
I'll be happy to add any information you may need
EDIT:
I'm gonna check that later at home but now I'm thinking there is something wrong with ssr part of my app because I don't even see that 'main server on port 3000' log..... At the same time server responds with right html, js files and routing works well so.....anyway I'm gonna look it up later
Try using util.inspect from node:
const util = require('util');
// usage in your code
const signup = (req, res, next) => {
console.log(util.inspect(req, { showHidden: true, depth: null }));
const { email, password, passwordCheck } = req.body;
...
};
I solved it.....unexpectedly the problem was lying in my package.json file.
My start script looked like this:
"scripts": {
"start": "nodemon build/server.bundle.js | nodemon build/api.bundle.js",
...
}
And because of that I had these weird errors. Only api.bundle.js file was running correctly......
Anyway thanks for your help ;)
I try to get the :userId "albert" from this url
http://localhost:5000/search/albert?query=al&page=1
at server side but failed, what can I do to get the react-router defined params correctly at node.js with express?
routes.js
[
{
path: '/search/:userId',
component: Search,
}, {
path: '/search',
component: Search,
}
...
]
server.js
server.get('*', async (req, res, next) => {
const pageData = await routes
.filter(route => matchPath(req.path, route))
.map((route) => {
console.log(route)
return route.component
})
}
The React-Router Way
React Router V4 does include a way to extract param data server-side using their matchPath() function, using their standard parameter implementation, "/path-name/:param" route matching.
In this case, it allows me to do a lot of server-side stuff based on the parameter before the express app responds with the page data.
NOTE: this is probably not the most basic implementation, but it's a pared down version of my complete SSR react implementation that makes use of matchPath().
Requirements
Server-side rendered react app
React-router-dom v4
Centralized routes file (because SSR)
Express app server (I'm hosting my express app on Firebase)
In This Example, a server-side express app attempts to run an "initialAction" function in each component during a fresh page load. It passes promise resolve and reject to know when the function is completed running, and the request object which may contain useful params we can extract with matchPath(). It does this for every matching route, again, using matchPath().
Routes.js Example
Where :id is the "id" param in the URL.
const routes = [
{
path: "/news-feed/:id",
component: NewsFeed,
exact: true
},
]
export default routes;
Component Example
Just showing the initialAction() function in the component
import { Link, matchPath } from 'react-router-dom';
class NewsFeed extends Component {
// Server always passes ability to resolve, reject in the initial action
// for async data requirements. req object always passed from express to
// the initial action.
static initialAction(resolve, reject, req) {
function getRouteData() {
let matchingRoute = routes.find(route => {
return matchPath(req.path, route);
});
console.log("Matching Route: ", matchingRoute);
return matchPath(req.path, matchingRoute);
}
let routeData = getRouteData();
console.log("Route Data: ", routeData);
}
/** REST OF COMPONENT **/
Console.log output for the url www.example.com/news-feed/test would be
Route Data: { path: '/news-feed/:id',
url: '/news-feed/test',
isExact: true,
params: { id: 'test' } }
As you can see, we've found our param on the server-side using no regex. matchPath() did the work for us. We can use nice, clean urls.
Server-side index.js
Where the initial action is called, with the promise resolve, reject, and req objects. Keep in mind this is a firebase hosting example and may differ for different hosting providers - your method for the initialAction function call may also differ.
import React from "react";
import ReactDOMServer from 'react-dom/server';
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { StaticRouter, matchPath } from "react-router-dom";
import routes from "../shared/components/App/routes.js";
import express from "express";
import * as functions from "firebase-functions";
// Import Components, Reducers, Styles
import App from "../shared/components/App";
import reducers from "../shared/reducers";
// Prepare our store to be enhanced with middleware
const middleware = [thunk];
const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
// Create store, compatible with REDUX_DEVTOOLS (chrome extension)
const store = createStoreWithMiddleware(reducers);
// Implement cors middleware to allow cross-origin
const cors = require('cors')({ origin: true });
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Finds the component for the given route, runs the "initial action" on the component
// The initialAction is a function on all server-side renderable components that must retrieve data before sending the http response
// Initial action always requires (resolve, reject, req), and returns a promise.
const promises = routes.reduce((acc, route) => {
if (matchPath(req.url, route) && route.component && route.component.initialAction) {
acc.push(new Promise(function (resolve, reject) {
// console.log("Calling initial action...");
store.dispatch(route.component.initialAction(resolve, reject, req));
}));
}
return acc;
}, []);
// Send our response only once all promises (from all components included in the route) have resolved
Promise.all(promises)
.then(() => {
const context = {};
const html = ReactDOMServer.renderToString(
<Provider store={store}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</Provider>
);
const preloadedState = store.getState();
res.status(200).send(renderFullPage(html, preloadedState));
})
.catch(function (error) {
console.log("Promise error at server", error);
});
});
});
module.exports = functions.https.onRequest(app);
Just used a sample node.js app to make a server.js which could be like
const express = require('express')
const app = express()
app.get('/search/:userid', (req, res) => res.json({ key: `Hello World for search with id=${req.params.userid}` }))
app.get('/search', (req, res) => res.send('Hello World!i for search'))
app.get('*', (req, res) => res.send('Hello World!'))
app.listen(3000, () => console.log('Example app listening on port 3000!'))
For the page number and other url params you can do like
req.query['page']
to retrieve the parameters.