Skip to content

Instantly share code, notes, and snippets.

@bryantee
Created December 10, 2016 19:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bryantee/b03defdb5d616fcaac10fd6f0074374a to your computer and use it in GitHub Desktop.
Save bryantee/b03defdb5d616fcaac10fd6f0074374a to your computer and use it in GitHub Desktop.
unit-testing-authentication
'use static';
const express = require('express');
const mongoose = require('mongoose');
const config = require('./config');
const bodyParser = require('body-parser');
const ghRobot = require('./ghRobot');
const request = require('request');
const bcrypt = require('bcryptjs');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const cookieParser = require('cookie-parser');
const session = require('express-session');
const util = require('util');
// Get models
const User = require('./models/user');
// Declare global so function can be
// used throughout after import
let updateByUser;
const app = express();
app.use(express.static('public'));
passport.use('local', new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, (err, user) => {
if (err) { return done(err); }
if (!user) {
return done(null, false, {message: "Incorrect username"});
}
user.validatePassword(password, error => {
if (err) {
return done(null, false, {message: "Incorrect username"});
} else {
return done(null, user);
}
});
});
}
));
passport.serializeUser(function(user, done) {
console.log('Serialize');
// console.log(`User: ${user}`);
console.log(`User id: ${user.id}`);
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
console.log(`id: ${id}`);
User.findById(id, function(err, user) {
console.log('deserializeUser');
done(err, user);
});
});
app.use(cookieParser());
app.use(bodyParser.json());
app.use(session({
secret: 'toast',
resave: true,
saveUninitialized: true,
}));
app.use(passport.initialize());
app.use(passport.session());
////////////////////////////
// Express Routes for API //
////////////////////////////
// Request update to github commit info
// returns 200 to let client know db update complete
app.post('/user/update/:username', (req, res) => {
let username = req.body.username;
updateByUser(username, () => {
res.sendStatus(200);
});
});
// Updating Goal for user
// Currently takes JSON object with username and new goal
// Returns JSON object with new goal
app.put('/users/:user/goal', (req, res) => {
let user = req.params.user;
let username = req.body.username;
let goal = req.body.currentGoal;
let query = {
username: user
};
let update = {
$set: {
currentGoal: goal
}
};
User.findOneAndUpdate(query, update, {}, (err, result) => {
if (!result) {
return res.status(404).send('No matching user: ' + user);
} else if (err) {
return res.status(500).send('Error: ' + err);
} else {
res.status(201).json({ currentGoal: goal });
}
});
});
// Get all user info for dashboard
app.get('/users/:user', (req, res) => {
let username = req.params.user;
let query = {
username
};
User.findOne(query, (err, result) => {
if (!result) {
return res.status(404).send('Bad username: ' + username);
} else if (err) {
return res.status(500).send('Error: ', err);
}
// TODO: repackage object literal to deliver to client
// ex: object includes password (hashed)
result['password'] = 'PRIVATE';
res.status(200).json(result);
});
});
// signup user
app.post('/users', (req, res) => {
let username = req.body.username.trim();
let password = req.body.password.trim();
bcrypt.genSalt(10, (err, salt) => {
if (err) {
return console.log(`error: ${err}`);
}
bcrypt.hash(password, salt, (err, hash) => {
if (err) {
return console.log(`error: ${err}`);
}
let userObj = {
username: username,
password: hash,
lastCheck: new Date(),
highStreak: 0,
currentCommitStreakDays: 0,
currentGoal: "Double click here to set your goal for the moment."
};
const url = "https://api.github.com/users/" + username;
request({
url: url,
json: true,
headers: {
'User-Agent': 'javascript'
}
}, (err, response, body) => {
if (err) return console.log(`Error making request to Github: ${err}`);
if (response.statusCode !== 200) return console.log(`Status code: ${response.statusCode}`);
if (response.statusCode === 200) {
userObj.avatar = body.avatar_url;
userObj.url = body.url;
userObj.bio = body.bio;
userObj.location = body.location;
User.create(userObj, (err, result) => {
if (err) {
return res.status(500).json({
message: 'Internal server error'
});
}
result['password'] = 'PROTECTED';
res.status(201).json(result);
});
}
})
});
});
});
// authenticate user
app.post('/login', passport.authenticate('local'), (req, res) => {
if (req.user) {
console.log(`Username: ${req.user.username}`);
let redirectURL = '/user/' + req.user.username;
res.status(200).json({
"success": true,
});
}
});
// pointless endpoint for testing
// will keep in for future tests
app.get('/is-login', (req, res) => {
res.status(200).json({
user: req.user
});
});
// logout user
app.get('/logout', (req, res) => {
req.logout();
res.status(401).redirect('/');
});
// Get all users (for testing purposes)
app.get('/users', (req, res) => {
User.find((err, users) => {
if (err) {
return res.status(500).json({
message: 'Internal server error'
});
}
res.status(200).json(users);
});
});
app.get('*', (req, res) => {
res.sendFile(__dirname + '/public/index.html');
});
////////////////////////
// End Routes //////////
////////////////////////
// Startup server
const runServer = function(callback) {
mongoose.connect(config.DATABASE_URL, err => {
if (err && callback) {
return callback(err);
}
console.log(`Connected to db at ${config.DATABASE_URL}`);
app.listen(config.PORT, () => {
if (callback) {
callback();
}
});
});
};
// Check if server.js is being called directly or through ./require
if (require.main === module) {
runServer(err => {
if (err) {
console.error(err);
}
updateByUser = ghRobot();
});
}
module.exports.app = app;
module.exports.runServer = runServer;
'use strict';
global.DATABASE_URL = 'mongodb://localhost/gitchain-test';
const server = require('../server');
const User = require('../models/user');
const chai = require('chai');
const chaiHttp = require('chai-http');
const should = chai.should();
const app = server.app;
chai.use(chaiHttp);
describe('GitChain API', () => {
// seed db
before( done => {
server.runServer( () => {
User.create(
{
username: 'jason-voorhees',
password: '123',
avatar: 'https://avatars.githubusercontent.com/u/10674447?v=3',
currentGoal: 'MURDERRR.',
currentCommitStreakDays: 5,
commitsToday: 1,
highStreak: 15,
lastCommit: new Date(),
lastCheck: new Date()
},
{
username: 'freddy-krueger',
password: '123',
avatar: 'https://avatars.githubusercontent.com/u/10674447?v=3',
currentGoal: 'Slice children faces off',
currentCommitStreakDays: 10,
commitsToday: 7,
highStreak: 27,
lastCommit: new Date(),
lastCheck: new Date()
}, function() {
done();
});
});
});
// tear down db
after( done => {
User.remove( () => {
done();
});
});
// HAPPY PATH TESTS
// it('should return 200 status and html on get "/"', done => {
// chai.request(server)
// .get('/')
// .end((err, res) => {
// res.should.have.status(200);
// done();
// });
// });
it('should return JSON with values for user on "/users/:user"', done => {
chai.request(app)
.get('/users')
.end((err, res) => {
let username = res.body[0].username;
chai.request(app)
.get('/users/' + username)
.end((err, res) => {
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('username', 'jason-voorhees');
res.body.should.have.property('avatar');
res.body.should.have.property('currentGoal', 'MURDERRR.');
res.body.should.have.property('currentCommitStreakDays', 5);
res.body.should.have.property('commitsToday', 1);
res.body.should.have.property('highStreak', 15);
res.body.should.have.property('lastCommit');
done();
})
})
});
it('should update goal in database on POST to "/users/:user/goal"', done => {
chai.request(app)
.put('/users/freddy-krueger/goal')
.send({'username': 'freddy-krueger', 'currentGoal': 'Pick all the flowers'})
.end((err, res) => {
res.should.have.status(201);
res.should.be.json;
res.body.should.have.property('currentGoal', 'Pick all the flowers')
chai.request(app)
.get('/users/freddy-krueger')
.end((err, res) => {
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('username', 'freddy-krueger');
res.body.should.have.property('avatar');
res.body.should.have.property('currentGoal', 'Pick all the flowers');
res.body.should.have.property('currentCommitStreakDays', 10);
res.body.should.have.property('commitsToday', 7);
res.body.should.have.property('highStreak', 27);
res.body.should.have.property('lastCommit');
done();
});
});
});
it('should add new user to db on POST to "/users" w valid JSON', done => {
chai.request(app)
.post('/users')
.send({ 'username': 'norman-bates', 'password': '123' })
.end((err, res) => {
res.should.have.status(201);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('username', 'norman-bates');
chai.request(app)
.get('/users')
.end((err, res) => {
let username = res.body[2].username;
chai.request(app)
.get('/users/' + username)
.end((err, res) => {
res.should.have.status(200);
res.should.be.json;
res.body.should.be.a('object');
res.body.should.have.property('username', 'norman-bates');
done();
});
});
});
});
});
// MODEL
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: { type: String },
currentGoal: { type: String },
currentCommitStreakDays: { type: Number },
commitsToday: { type: Number },
highStreak: { type: Number },
lastCommit: { type: Date },
lastCheck: { type: Date, required: true },
streakDates: [ Date ],
bio: { type: String },
url: { type: String },
location: { type: String }
});
// TODO: Set instance method for user to check if commit hasn't been made, signaling broken streak
UserSchema.methods.validatePassword = function(password, callback) {
bcrypt.compare(password, this.password, function(err, isValid) {
if (err) {
callback(err);
return;
}
callback(null, isValid);
});
};
const User = mongoose.model('User', UserSchema);
module.exports = User;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment