Whilst write tests for my express API I wanted to check that response.body had the html I expected in it.
I found that the response object has a body property but that it is empty. I found the html in the response.text property.
When I used Postman to examine the response, it uses the words header for the header details and body for the html data. This is what I expected. This difference is confusing me as I am just learning http.
I am not asking about my own code as I tried it with a different site (www.bbc.co.uk) and got the same thing.
Why is there a difference in the wording? What is response.body for if not the data being returned?
Here is the code used in my test. This is the ejs template;
<html>
<head></head>
<body>Hello again world!</body>
</html>
Here is the express app;
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
app.set('view engine', 'ejs');
app.get('/', function(req, res) {
res.status(200).render('index');
});
app.get('*', function(req, res){
res.status(404).send('what???');
});
app.listen(3000, function() {
console.log('Example app listening on port ' + port);
});
Here are the tests;
const chai = require('chai');
const chaiHttp = require('chai-http');
const expect = require('chai').expect;
chai.use(chaiHttp);
const api = 'www.bbc.co.uk';
describe('The phonics API', () => {
it('should return status', (done) => {
chai.request('http://localhost:3000')
.get('/')
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.exist;
done();
});
});
it('should return an HTML file in the body', (done) => {
chai.request('http://localhost:3000')
.get('/')
.end((err, res) => {
expect(res).to.be.html;
done();
});
});
it('should return status 404 for wrong url', (done) => {
chai.request('http://localhost:3000')
.get('/resersa')
.end((err, res) => {
expect(res).to.have.status(404);
done();
});
});
it('should respond with a body', (done) => {
chai.request(api)
.get('/')
.end((err, res) => {
expect(res.body).to.exist;
console.log(res.body);
done()
});
});
});
In the last test I added console.log(res.body) to see what was there. It prints {}. When I change it to console.log(res.test) I get the html above.
When I use postman to GET http://localhost:3000 It has the html under a tab called body.
Related
I have an app made with React, Node.js and Socket.io
I deployed Node backend to heroku , frontend to Netlify
I know that CORS errors is related to server but no matter what I add, it just cant go through that error in the picture below.
I also added proxy script to React's package.json as "proxy": "https://googledocs-clone-sbayrak.herokuapp.com/"
And here is my server.js file;
const mongoose = require('mongoose');
const Document = require('./Document');
const dotenv = require('dotenv');
const path = require('path');
const express = require('express');
const http = require('http');
const socketio = require('socket.io');
dotenv.config();
const app = express();
app.use(cors());
const server = http.createServer(app);
const io = socketio(server, {
cors: {
origin: 'https://googledocs-clone-sbayrak.netlify.app/',
methods: ['GET', 'POST'],
},
});
app.get('/', (req, res) => {
res.status(200).send('hello!!');
});
const connectDB = async () => {
try {
const connect = await mongoose.connect(process.env.MONGODB_URI, {
useUnifiedTopology: true,
useNewUrlParser: true,
});
console.log('MongoDB Connected...');
} catch (error) {
console.error(`Error : ${error.message}`);
process.exit(1);
}
};
connectDB();
let defaultValue = '';
const findOrCreateDocument = async (id) => {
if (id === null) return;
const document = await Document.findById({ _id: id });
if (document) return document;
const result = await Document.create({ _id: id, data: defaultValue });
return result;
};
io.on('connection', (socket) => {
socket.on('get-document', async (documentId) => {
const document = await findOrCreateDocument(documentId);
socket.join(documentId);
socket.emit('load-document', document.data);
socket.on('send-changes', (delta) => {
socket.broadcast.to(documentId).emit('receive-changes', delta);
});
socket.on('save-document', async (data) => {
await Document.findByIdAndUpdate(documentId, { data });
});
});
console.log('connected');
});
server.listen(process.env.PORT || 5000, () =>
console.log(`Server has started.`)
);
and this is where I make request from frontend;
import Quill from 'quill';
import 'quill/dist/quill.snow.css';
import { useParams } from 'react-router-dom';
import { io } from 'socket.io-client';
const SAVE_INTERVAL_MS = 2000;
const TextEditor = () => {
const [socket, setSocket] = useState();
const [quill, setQuill] = useState();
const { id: documentId } = useParams();
useEffect(() => {
const s = io('https://googledocs-clone-sbayrak.herokuapp.com/');
setSocket(s);
return () => {
s.disconnect();
};
}, []);
/* below other functions */
/* below other functions */
/* below other functions */
}
TL;DR
https://googledocs-clone-sbayrak.netlify.app/ is not an origin. Drop that trailing slash.
More details about the problem
No trailing slash allowed in the value of the Origin header
According to the CORS protocol (specified in the Fetch standard), browsers never set the Origin request header to a value with a trailing slash. Therefore, if a page at https://googledocs-clone-sbayrak.netlify.app/whatever issues a cross-origin request, that request's Origin header will contain
https://googledocs-clone-sbayrak.netlify.app
without any trailing slash.
Byte-by-byte comparison on the server side
You're using Socket.IO, which relies on the Node.js cors package. That package won't set any Access-Control-Allow-Origin in the response if the request's origin doesn't exactly match your CORS configuration's origin value (https://googledocs-clone-sbayrak.netlify.app/).
Putting it all together
Obviously,
'https://googledocs-clone-sbayrak.netlify.app' ===
'https://googledocs-clone-sbayrak.netlify.app/'
evaluates to false, which causes the cors package not to set any Access-Control-Allow-Origin header in the response, which causes the CORS check to fail in your browser, hence the CORS error you observed.
Example from the Fetch Standard
Section 3.2.5 of the Fetch Standard even provides an enlightening example of this mistake,
Access-Control-Allow-Origin: https://rabbit.invalid/
and explains why it causes the CORS check to fail:
A serialized origin has no trailing slash.
Looks like you haven't imported the cors package. Is it imported anywhere else?
var cors = require('cors') // is missing
I am developing Cypress tests for my API.
The response from my API in Postman is below:
{"infected" : false}
And my Cypress test is below:
describe("Testing the result after scanning file", () => {
it("Scan file", function () {
//Declarations
const fileName = 'example.json';
cy.fixture(fileName, 'binary')
.then((file) => Cypress.Blob.binaryStringToBlob(file))
.then((blob) => {
const formData = new FormData();
formData.append("file", blob, fileName);
cy.request({
method: 'POST',
headers: {
'content-type': 'multipart/form-data'
},
body: formData,
url: '/scan'
}).then(response => {
console.log('the response is: ', response.body)
expect(response.body).to.have.property('infected').and.eq(false);
});
})
});
});
In my browser, the Cypress test fails with the message:
assert expected {} to have property infected
I really have already broken my brain with this issue and still have no clue how to tackle it. Can anybody give me an idea what is going wrong?
Try converting the response to json, you may be seeing a string version of the data.
Postman output will not be helpful, it could be converting automatically in the background.
cy.request({
...
})
.then(response => response.json())
// OR
// .then(response => response.body.json())
.then(data => {
console.log('the data is: ', data) // better debug tool than Postman
expect(data).to.have.property('infected').and.eq(false);
});
I'm using a NextJs server and I need to have two different public assets folders.
Already using the regular /public folder to serve some assets but now I need to add a second public folder that's somewhere else on the server
My code
const express = require('express');
const next = require('next');
const expressApp = express();
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare()
.then(() => {
expressApp.all('*', (req, res) => {
return handle(req, res);
});
expressApp.use(express.static('/usr/local/somewhereelse/assets', {caseSensitive: true}));
spdy.createServer(httpsOptions, expressApp)
.listen(port, (err) => {
if(err) {
throw err;
}
console.log('Listening #:' + port);
});
});
What am I missing here?
Thanks in advance :)
Everyone, rookie mistake I needed to add the static route before the wildcard.
app.prepare()
.then(() => {
expressApp.use(express.static('/usr/local/somewhereelse/assets', {caseSensitive: true}));
expressApp.all('*', (req, res) => {
return handle(req, res);
});
spdy.createServer(httpsOptions, expressApp)
.listen(port, (err) => {
if(err) {
throw err;
}
console.log('Listening #:' + port);
});
});
here is my code:-
exports.uploadImage = (req, res) => {
const BusBoy = require('busboy');
const path = require('path');
const os = require('os');
const fs = require('fs');
const busboy = new BusBoy({ headers: req.headers });
let imageFileName;
let imageToBeUploaded = {};
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
const imageExtension = filename.split('.')[filename.split('.').length - 1];
imageFileName = `${Math.round(Math.random() * 100000000000)}.${imageExtension}`;
const filepath = path.join(os.tmpdir(), imageFileName);
imageToBeUploaded = { filepath, mimetype };
file.pipe(fs.createWriteStream(filepath));
});
busboy.on('finish', () => {
console.log('Busboy on started');
//code breaks here
admin.storage().bucket().upload(imageToBeUploaded.filepath, {
resumable: false,
metadata: {
metadata: {
contentType: imageToBeUploaded.mimetype
}
}
})
.then(() => {
const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`;
console.log('logging image url' + imageUrl);
return db.doc(`/users/${req.user.handle}`).update({ imageUrl })
})
.then(() => {
return res.json({ message: 'Image uploaded successfully' });
})
.catch(err => {
console.error(err);
return res.status(500).json({ error: err.code });
})
});
busboy.end(req.rawBody);
}
I have mentioned where my code is breaking in a comment and the error I am getting is Error: Cannot parse response as JSON: Not Found
message: 'Cannot parse response as JSON: Not Found'
The error message says cannot parse response as JSON. Does that mean the response from firebase is not JSON? I have a token in the header of the request and an image in the body as form-data. I really have not clue what wrong, please help
I unfortunately can't identify the JSON parsing error, so I've instead rewritten the code to be more streamlined as #robsiemb eluded to.
Your uploadImage function appears to be configured as some middleware, so I have done the same below. This code will stream the uploaded data straight to Cloud Storage under a unique file name as generated from Reference.push().key to prevent conflicts.
In the code below,
The uploaded file will be stored at a location similar to: userData/someUserId/images/-JhLeOlGIEjaIOFHR0xd.png
The image's raw URL is not stored in the database because unless the file object or containing bucket is made public it will require a signed URL which can only last up to 7 days (see below).
More than one file can be accepted and uploaded. If this is undesired, configure the limits for the BusBoy instance.
Basic error handling for non-POST requests and missing file entries was added.
// import Firebase libraries & initialize
const admin = require('firebase-admin');
admin.initializeApp(); // initializes from environment variables
// import required modules
const BusBoy = require('busboy');
exports.uploadImage = (req, res) => {
if (req.method !== 'POST') {
res.sendStatus(405); // 405 METHOD_NOT_ALLOWED
return;
}
let busboy = new BusBoy({headers: req.headers}); // add {limits: {files: 1}} to limit to only a single file upload
let bucket = admin.storage().bucket();
let db = admin.firestore();
let storageFilepath;
let storageFile;
// Note: Currently only the last file is saved to `/users/${req.user.handle}`
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
let fileext = filename.match(/\.[0-9a-z]+$/i)[0];
storageFilepath = `userData/${req.user.handle}/images/` + getUniqueName() + fileext;
storageFile = bucket.file(storageFilepath);
file.pipe(storageFile.createWriteStream({ gzip: true }));
})
.on('finish', () => {
if (!storageFile) {
res.status(400).json({error: 'expected file'}); // 400 BAD_REQUEST
return;
}
db.doc(`/users/${req.user.handle}`).update({ imagePath: storageFilepath })
.then(() => {
res.status(201).json({ message: 'Image uploaded successfully' }); // 201 CREATED
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code }); // 500 INTERNAL_SERVER_ERROR
});
})
.on('error', (err) => {
console.error(err);
res.status(500).json({ error: err.code });
});
req.pipe(busboy);
});
function getUniqueName() {
// push() without arguments returns a ThennableReference, which we'll abuse for it's key generation
return admin.database().ref().push().key;
}
If you did want the uploaded image to be publicly accessible, you could use the following .on('finish', ...) handler that adds in the File.makePublic() function:
.on('finish', () => {
if (!storageFile) {
res.status(400).json({error: 'expected file'}); // 400 BAD_REQUEST
return;
}
storageFile.makePublic()
.then(() => {
return db.doc(`/users/${req.user.handle}`).update({
imagePath: storageFilepath,
imageUrl: `https://storage.googleapis.com/${config.storageBucket}/${storageFilepath}`
});
})
.then(() => {
res.status(201).json({ message: 'Image uploaded successfully' }); // 201 CREATED
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code }); // 500 INTERNAL_SERVER_ERROR
});
})
Found a solution to the issue!
Essentially - you need to set up your Google Application Credentials. Go into firebase and look into your settings. You need to set up the environment variable GOOGLE_APPLICATION_CREDENTIALS so that firebase has your credentials when you access these files.
https://firebase.google.com/docs/admin/setup?authuser=1 for more information.
After you've done that, check the security settings in firebase, in every area you're dealing with. This should solve the problem (it's definitely a security issue and not your code).
This was the tutorial in question as well for those looking on . https://www.youtube.com/watch?v=m_u6P5k0vP0&t=7661s .
In my case it was wrong bucket Id configured - after correcting that i was able to upload file
I would like to mock API calls so that
request to
http://localhost:8080/api/test/<yyyy-mm-dd>
gives a response:
{date: <yyyy-mm-dd>, data: 'my cool data'}
where <yyyy-mm-dd> is not fixed (This request is made 7 times for last 7 days)
How can I create a mock for this in TestCafé? Note that response data depends on the request URL.
Place index.html and index.js files in the same folder. Then run testcafe chrome test.js command in your terminal.
index.html
<html>
<body>
<h1>Page</h1>
<button id="sendRequestBtn">Send request</button>
<code id='response'></code>
<script>
var sendRequestBtn = document.getElementById('sendRequestBtn');
var responseData = document.getElementById('response');
sendRequestBtn.addEventListener('click', function (){
fetch('http://localhost:8080/api/test/2019-07-12')
.then(response => {
return response.json();
})
.then(json => {
responseData.textContent = JSON.stringify(json, null, 4);
})
.catch(e => console.error(e));
});
</script>
</body>
</html>
test.js
import { RequestMock } from 'testcafe';
const mock = RequestMock()
.onRequestTo(/http:\/\/localhost:8080\/api\/test\/.*/)
.respond((req, res) => {
res.headers['access-control-allow-origin'] = '*'; // It's necessary because TestCafe load the page via file protocol in this example.
const dateUrlPart = req.path.replace('/api/test/', '');
res.setBody({
date: dateUrlPart,
data: 'my cool data'
});
});
fixture `Fixture`
.page('./index.html')
.requestHooks(mock);
test('test', async t => {
await t.click('#sendRequestBtn').wait(1000);
});