#68 Fixed tests up to login/-out
parent
3b60b2fc86
commit
887b87231b
|
@ -47,33 +47,17 @@ const userSchema = new mongoose.Schema({
|
|||
}).plugin(unique)
|
||||
|
||||
/* User methods */
|
||||
// Do not replace with arrow functions!
|
||||
// https://stackoverflow.com/a/37875212
|
||||
|
||||
// Create email confirmation token
|
||||
userSchema.methods.createEmailToken = function (next) {
|
||||
debug('user.createEmailToken() called')
|
||||
var user = this
|
||||
userSchema.methods.createEmailToken = function () {
|
||||
debug(`user.createEmailToken() called for ${user.id}`)
|
||||
let user = this
|
||||
|
||||
// Callback next(err, token)
|
||||
if (typeof next === 'function') {
|
||||
return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(16, (err, buf) => {
|
||||
if (err) return next(err)
|
||||
if (buf) {
|
||||
debug(`Buffer ${buf.toString('hex')} created`)
|
||||
user.emailToken = buf.toString('hex')
|
||||
user.save()
|
||||
.then(() => {
|
||||
return next(null, user.emailToken)
|
||||
})
|
||||
.catch((err) => {
|
||||
return next(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Promise
|
||||
} else return new Promise((resolve, reject) => {
|
||||
crypto.randomBytes(16, (err, buf) => {
|
||||
if (err) reject(err)
|
||||
if (err) return reject(err)
|
||||
if (buf) {
|
||||
debug(`Buffer ${buf.toString('hex')} created`)
|
||||
user.emailToken = buf.toString('hex')
|
||||
|
@ -89,49 +73,11 @@ userSchema.methods.createEmailToken = function (next) {
|
|||
}
|
||||
|
||||
// Create password reset token
|
||||
userSchema.methods.createPassToken = function (next) {
|
||||
debug('user.createPassToken() called')
|
||||
var user = this
|
||||
userSchema.methods.createPassToken = function () {
|
||||
let user = this
|
||||
debug(`user.createPassToken() called for ${user.id}`)
|
||||
|
||||
// Callback next(err, token, expires)
|
||||
if (typeof next === 'function') {
|
||||
|
||||
// Reuse old token, resetting clock
|
||||
if (user.auth.passTokenExpires >= Date.now()) {
|
||||
debug(`Reusing old password token...`)
|
||||
user.auth.passTokenExpires = Date.now() + 3600000 // 1 hour
|
||||
user.save()
|
||||
.then(() => {
|
||||
return next(null, user.auth.passToken, user.auth.passTokenExpires)
|
||||
})
|
||||
.catch((err) => {
|
||||
return next(err)
|
||||
})
|
||||
|
||||
// Create new token
|
||||
} else {
|
||||
debug(`Creating new password token...`)
|
||||
crypto.randomBytes(16, (err, buf) => {
|
||||
if (err) return next(err)
|
||||
if (buf) {
|
||||
user.auth.passToken = buf.toString('hex')
|
||||
user.auth.passTokenExpires = Date.now() + 3600000 // 1 hour
|
||||
user.save()
|
||||
.then(() => {
|
||||
debug('successfully saved user in createPassToken')
|
||||
return next(null, user.auth.passToken, user.auth.passTokenExpires)
|
||||
})
|
||||
.catch((err) => {
|
||||
debug('error saving user in createPassToken')
|
||||
return next(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Promise
|
||||
|
||||
} else return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Reuse old token, resetting clock
|
||||
if (user.auth.passTokenExpires >= Date.now()) {
|
||||
|
@ -147,7 +93,7 @@ userSchema.methods.createPassToken = function (next) {
|
|||
} else {
|
||||
debug(`Creating new password token...`)
|
||||
crypto.randomBytes(16, (err, buf) => {
|
||||
if (err) return next(err)
|
||||
if (err) return reject(err)
|
||||
if (buf) {
|
||||
user.auth.passToken = buf.toString('hex')
|
||||
user.auth.passTokenExpires = Date.now() + 3600000 // 1 hour
|
||||
|
@ -163,32 +109,20 @@ userSchema.methods.createPassToken = function (next) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// Generate hash for new password and save it to the database
|
||||
userSchema.methods.generateHashedPassword = function (password, next) {
|
||||
userSchema.methods.generateHashedPassword = function (password) {
|
||||
debug(`user.generateHashedPassword() called for ${this.id}`)
|
||||
|
||||
// Delete token
|
||||
this.auth.passToken = undefined
|
||||
this.auth.passTokenExpires = undefined
|
||||
|
||||
// Callback next(err, token, expires)
|
||||
if (typeof next === 'function') {
|
||||
|
||||
// Generate hash
|
||||
bcrypt.genSalt(8, (err, salt) => {
|
||||
if (err) return next(err)
|
||||
bcrypt.hash(password, salt, (err, hash) => {
|
||||
if (err) return next(err)
|
||||
this.auth.password = hash
|
||||
this.save()
|
||||
.then(() => { next(); })
|
||||
.catch((err) => next(err) )
|
||||
})
|
||||
})
|
||||
|
||||
} else return new Promise((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Generate hash
|
||||
bcrypt.genSalt(8, (err, salt) => {
|
||||
|
@ -206,17 +140,12 @@ userSchema.methods.generateHashedPassword = function (password, next) {
|
|||
}
|
||||
|
||||
// Check for valid password
|
||||
userSchema.methods.validPassword = function (password, next) {
|
||||
// Callback next(err, res)
|
||||
if (typeof next === 'function') bcrypt.compare(password, this.auth.password, next)
|
||||
else return new Promise( (resolve, reject) => {
|
||||
bcrypt.compare(password, this.auth.password)
|
||||
.then( (result) => {
|
||||
if (result===true) resolve()
|
||||
else reject(new Error('Passwords don\'t match'))
|
||||
})
|
||||
.catch( (err) => reject(err) )
|
||||
})
|
||||
userSchema.methods.validPassword = function (password) {
|
||||
let user = this
|
||||
debug(`user.validPassword() called for ${user.id}`)
|
||||
|
||||
return bcrypt.compare(password, user.auth.password)
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -13,6 +13,7 @@ const mw = require('./middleware.js')
|
|||
const User = require('./models.js').user
|
||||
|
||||
module.exports = (passport) => {
|
||||
|
||||
// Serialize/deserialize users
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
|
@ -33,13 +34,16 @@ module.exports = (passport) => {
|
|||
debug(`Perfoming local login for ${email}`)
|
||||
User.findOne({'email': email})
|
||||
.then((user) => {
|
||||
|
||||
// No user with that email
|
||||
if (!user) {
|
||||
debug(`No user with that email`)
|
||||
req.session.next = undefined
|
||||
return done(null, false, req.flash('warning', 'Incorrect email or password.'))
|
||||
|
||||
// User exists
|
||||
} else {
|
||||
debug(`User exists. Checking password...`)
|
||||
|
||||
// Check password
|
||||
user.validPassword(password)
|
||||
|
@ -47,6 +51,7 @@ module.exports = (passport) => {
|
|||
|
||||
// Password incorrect
|
||||
if (!res) {
|
||||
debug(`Incorrect password`)
|
||||
req.session.next = undefined
|
||||
return done(null, false, req.flash('warning', 'Incorrect email or password.'))
|
||||
|
||||
|
@ -71,7 +76,7 @@ module.exports = (passport) => {
|
|||
))
|
||||
|
||||
// Social login
|
||||
function socialLogin (req, service, profileId, done) {
|
||||
function socialLogin(req, service, profileId, done) {
|
||||
debug(`socialLogin() called for ${service} account ${profileId}`)
|
||||
let query = {}
|
||||
query['auth.' + service] = profileId
|
||||
|
|
|
@ -10,6 +10,7 @@ const debug = require('debug')('tracman-routes-auth')
|
|||
const env = require('../env/env.js')
|
||||
|
||||
module.exports = (app, passport) => {
|
||||
|
||||
// Methods for success and failure
|
||||
const loginOutcome = {
|
||||
failureRedirect: '/login',
|
||||
|
@ -55,7 +56,7 @@ module.exports = (app, passport) => {
|
|||
.post((req, res, next) => {
|
||||
|
||||
// Send token and alert user
|
||||
function sendToken (user) {
|
||||
function sendToken(user) {
|
||||
debug(`sendToken() called for user ${user.id}`)
|
||||
|
||||
// Create a new password token
|
||||
|
@ -119,7 +120,7 @@ module.exports = (app, passport) => {
|
|||
mw.throwErr(err, req)
|
||||
res.redirect('/login#signup')
|
||||
}
|
||||
|
||||
|
||||
} })
|
||||
})
|
||||
.catch( (err) => {
|
||||
|
@ -127,9 +128,9 @@ module.exports = (app, passport) => {
|
|||
mw.throwErr(err, req)
|
||||
res.redirect('/login#signup')
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Validate email
|
||||
req.checkBody('email', 'Please enter a valid email address.').isEmail()
|
||||
|
||||
|
@ -225,6 +226,7 @@ module.exports = (app, passport) => {
|
|||
|
||||
// Save user and send the token by email
|
||||
Promise.all([slug, sk32])
|
||||
//.then(() => { user.save() })
|
||||
.then(() => { sendToken(user) })
|
||||
.catch((err) => {
|
||||
debug('Failed to save user after creating slug and sk32!')
|
||||
|
@ -273,6 +275,7 @@ module.exports = (app, passport) => {
|
|||
|
||||
// User with that email does exist
|
||||
} else {
|
||||
|
||||
// Create reset token
|
||||
user.createPassToken()
|
||||
.then( (token) => {
|
||||
|
@ -319,7 +322,7 @@ module.exports = (app, passport) => {
|
|||
mw.throwErr(err, req)
|
||||
res.redirect('/login/forgot')
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
// Android
|
||||
|
|
|
@ -8,7 +8,7 @@ const mw = require('../middleware.js')
|
|||
const User = require('../models.js').user
|
||||
const mail = require('../mail.js')
|
||||
const env = require('../env/env.js')
|
||||
const debug = require('debug')('tracman-settings')
|
||||
const debug = require('debug')('tracman-routes-settings')
|
||||
const router = require('express').Router()
|
||||
|
||||
// Settings form
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
FAKE_EMAIL: 'nobody@tracman.org',
|
||||
TEST_EMAIL: 'test@tracman.org',
|
||||
TEST_PASSWORD: 'mDAQYe2VYE',
|
||||
BAD_PASSWORD: 'password123',
|
||||
FUZZED_EMAIL_TRIES: 3,
|
||||
FUZZED_PASSWORD_TRIES: 10,
|
||||
}
|
|
@ -827,6 +827,43 @@
|
|||
"methods": "1.1.2",
|
||||
"qs": "6.5.1",
|
||||
"superagent": "2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "1.0.0-rc4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
|
||||
"integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "1.5.2",
|
||||
"combined-stream": "1.0.5",
|
||||
"mime-types": "2.1.17"
|
||||
}
|
||||
},
|
||||
"superagent": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz",
|
||||
"integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"cookiejar": "2.0.6",
|
||||
"debug": "2.6.9",
|
||||
"extend": "3.0.1",
|
||||
"form-data": "1.0.0-rc4",
|
||||
"formidable": "1.1.1",
|
||||
"methods": "1.1.2",
|
||||
"mime": "1.4.1",
|
||||
"qs": "6.5.1",
|
||||
"readable-stream": "2.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
|
@ -5983,16 +6020,16 @@
|
|||
}
|
||||
},
|
||||
"superagent": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-2.3.0.tgz",
|
||||
"integrity": "sha1-cDUpoHFOV+EjlZ3e+84ZOy5Q0RU=",
|
||||
"version": "3.8.2",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz",
|
||||
"integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"cookiejar": "2.0.6",
|
||||
"debug": "2.6.9",
|
||||
"cookiejar": "2.1.1",
|
||||
"debug": "3.1.0",
|
||||
"extend": "3.0.1",
|
||||
"form-data": "1.0.0-rc4",
|
||||
"form-data": "2.3.1",
|
||||
"formidable": "1.1.1",
|
||||
"methods": "1.1.2",
|
||||
"mime": "1.4.1",
|
||||
|
@ -6000,21 +6037,19 @@
|
|||
"readable-stream": "2.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
|
||||
"cookiejar": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz",
|
||||
"integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=",
|
||||
"dev": true
|
||||
},
|
||||
"form-data": {
|
||||
"version": "1.0.0-rc4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz",
|
||||
"integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=",
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "1.5.2",
|
||||
"combined-stream": "1.0.5",
|
||||
"mime-types": "2.1.17"
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"mocha": "^4.0.1",
|
||||
"nodemon": "^1.11.0",
|
||||
"standard": "^10.0.3",
|
||||
"superagent": "^3.8.2",
|
||||
"supertest": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
254
test/auth.js
254
test/auth.js
|
@ -1,18 +1,19 @@
|
|||
'use strict'
|
||||
|
||||
const chai = require('chai')
|
||||
const app = require('../server')
|
||||
const User = require('../config/models').user
|
||||
const request = require('supertest')
|
||||
.agent(require('../server'))
|
||||
// const superagent = require('superagent').agent()
|
||||
const request = require('supertest').agent(app)
|
||||
chai.use(
|
||||
require('chai-http')
|
||||
)
|
||||
|
||||
// Constants for dummy accounts
|
||||
const FAKE_EMAIL = 'nobody@tracman.org'
|
||||
const TEST_EMAIL = 'test@tracman.org'
|
||||
const TEST_PASSWORD = 'mDAQYe2VYE'
|
||||
const BAD_PASSWORD = 'password123'
|
||||
// Import test config by object destructuring
|
||||
const { FAKE_EMAIL, TEST_EMAIL,
|
||||
TEST_PASSWORD, BAD_PASSWORD,
|
||||
FUZZED_EMAIL_TRIES, FUZZED_PASSWORD_TRIES,
|
||||
} = require('../config/test.js')
|
||||
|
||||
describe('Authentication', () => {
|
||||
|
||||
|
@ -20,9 +21,131 @@ describe('Authentication', () => {
|
|||
let passwordless_user
|
||||
|
||||
// Make sure test user doesn't exist
|
||||
before( async () => {
|
||||
let existing_test_user = await User.findOne({'email':TEST_EMAIL})
|
||||
if (existing_test_user) existing_test_user.remove()
|
||||
before( (done) => {
|
||||
User.findOne({'email':TEST_EMAIL})
|
||||
.then( (user) => {
|
||||
if (!user) done()
|
||||
else {
|
||||
user.remove()
|
||||
.then( (user) => { done() })
|
||||
.catch(console.error)
|
||||
}
|
||||
}).catch(console.error)
|
||||
})
|
||||
|
||||
// These tests require the test user to have been created
|
||||
after( () => {
|
||||
console.log('running after tests')
|
||||
|
||||
describe('Logged out', () => {
|
||||
|
||||
it('Fails to log in with bad password', async () => {
|
||||
|
||||
// Confirm redirect
|
||||
chai.expect( await request.post('/login')
|
||||
.type('form').send({
|
||||
'email': TEST_EMAIL,
|
||||
'password': BAD_PASSWORD
|
||||
})
|
||||
).to.redirectTo('/login') // Hey! Incorrect email or password.
|
||||
|
||||
})
|
||||
|
||||
// TODO: Implement fuzzer
|
||||
it.skip(`Fails to log in with ${FUZZED_PASSWORD_TRIES} fuzzed passwords`, () => {
|
||||
|
||||
// Fuzz passwords
|
||||
// loop with let fuzzed_password
|
||||
|
||||
// Confirm redirect
|
||||
// chai.expect( await request.post('/login')
|
||||
// .type('form').send({
|
||||
// 'email': TEST_EMAIL,
|
||||
// 'password': fuzzed_password
|
||||
// })
|
||||
// ).to.redirectTo('/login') // Hey! Incorrect email or password.
|
||||
|
||||
})
|
||||
|
||||
it('Logs in with password', async () => {
|
||||
let res = await request.post('/login')
|
||||
.type('form').send({
|
||||
email: TEST_EMAIL,
|
||||
password: TEST_PASSWORD
|
||||
})
|
||||
request.saveCookies(res)
|
||||
chai.expect(res).to.redirectTo('/map')
|
||||
})
|
||||
|
||||
// it('Forgets password', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes forgotten password', async () => {
|
||||
|
||||
// })
|
||||
|
||||
})
|
||||
|
||||
describe('Logged in', () => {
|
||||
|
||||
it('Logs out', async () => {
|
||||
let res = request.get('/logout')
|
||||
request.attachCookies(res)
|
||||
chai.expect(res).to.redirectTo('/')
|
||||
|
||||
})
|
||||
|
||||
// it('Changes email address', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes password', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes settings', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Connects a Google account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Connects a Facebook account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Connects a Twitter account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs in with Google', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs in with Facebook', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs in with Twitter', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Disconnects a Google account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Disconnects a Facebook account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Disconnects a Twitter account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
it('Fails to create an account with a fake email', async () => {
|
||||
|
@ -32,10 +155,11 @@ describe('Authentication', () => {
|
|||
.type('form').send({ 'email':FAKE_EMAIL })
|
||||
).to.redirectTo('/login#signup')
|
||||
|
||||
// Ensure user was deleted after email failed to send
|
||||
// Users with bad emails are removed asynchronously and may happen after
|
||||
// the response was recieved. Ensure it's happened in a kludgy way by
|
||||
// waiting 2 seconds before asserting that the user doesn't exist
|
||||
/* Ensure user was deleted after email failed to send
|
||||
/* Users with bad emails are removed asynchronously and may happen after
|
||||
/* the response was recieved. Ensure it's happened in a kludgy way by
|
||||
/* waiting 2 seconds before asserting that the user doesn't exist
|
||||
*/
|
||||
setTimeout( async () => {
|
||||
chai.assert.isNull( await User.findOne({
|
||||
'email': FAKE_EMAIL
|
||||
|
@ -44,6 +168,30 @@ describe('Authentication', () => {
|
|||
|
||||
})
|
||||
|
||||
// TODO: Implement fuzzer
|
||||
it.skip(`Fails to create accounts with ${FUZZED_EMAIL_TRIES} fuzzed emails`, () => {
|
||||
|
||||
// Fuzz emails
|
||||
// loop with let fuzzed_email
|
||||
|
||||
// Confirm redirect
|
||||
// chai.expect( await request.post('/signup')
|
||||
// .type('form').send({ 'email':fuzzed_email })
|
||||
// ).to.redirectTo('/login#signup')
|
||||
|
||||
/* Ensure user was deleted after email failed to send
|
||||
/* Users with bad emails are removed asynchronously and may happen after
|
||||
/* the response was recieved. Ensure it's happened in a kludgy way by
|
||||
/* waiting 2 seconds before asserting that the user doesn't exist
|
||||
*/
|
||||
// setTimeout( async () => {
|
||||
// chai.assert.isNull( await User.findOne({
|
||||
// 'email': FAKE_EMAIL
|
||||
// }), 'Account with fake email was created')
|
||||
// }, 2000)
|
||||
|
||||
})
|
||||
|
||||
it('Creates an account with a valid email', async () => {
|
||||
|
||||
// Set email address
|
||||
|
@ -96,82 +244,4 @@ describe('Authentication', () => {
|
|||
|
||||
})
|
||||
|
||||
describe('Account usage', () => {
|
||||
|
||||
// Create account to play with
|
||||
before( () => {
|
||||
// Create user
|
||||
// Set password
|
||||
})
|
||||
|
||||
// it('Logs in', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs out', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Forgets password', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes forgotten password', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs back in', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes email address', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes password', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Changes settings', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Connects a Google account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Connects a Facebook account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Connects a Twitter account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs in with Google', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs in with Facebook', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Logs in with Twitter', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Disconnects a Google account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Disconnects a Facebook account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
// it('Disconnects a Twitter account', async () => {
|
||||
|
||||
// })
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue