Skip to content

Commit

Permalink
feat(users): allow passing in first dex params in user creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Robin Joseph committed Oct 24, 2016
1 parent 74de57e commit 69830d8
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 32 deletions.
4 changes: 0 additions & 4 deletions src/models/dex.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ const Bookshelf = require('../libraries/bookshelf');
module.exports = Bookshelf.model('Dex', Bookshelf.Model.extend({
tableName: 'dexes',
hasTimestamps: ['date_created', 'date_modified'],
defaults: {
shiny: false,
generation: 6
},
serialize () {
return {
id: this.get('id'),
Expand Down
21 changes: 13 additions & 8 deletions src/plugins/features/users/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,7 @@ exports.retrieve = function (username) {
exports.create = function (payload, request) {
return Bcrypt.hashAsync(payload.password, Config.SALT_ROUNDS)
.then((hash) => {
const xff = request.headers['x-forwarded-for'];
const ip = xff ? xff.split(',')[0].trim() : request.info.remoteAddress;

payload.password = hash;
payload.last_ip = ip;

return new User().where('username', payload.username).fetch();
})
Expand All @@ -43,15 +39,24 @@ exports.create = function (payload, request) {
throw new Errors.ExistingUsername();
}

const title = 'Living Dex';
const xff = request.headers['x-forwarded-for'];
const ip = xff ? xff.split(',')[0].trim() : request.info.remoteAddress;

return Knex.transaction((transacting) => {
return new User().save(payload, { transacting })
return new User().save({
username: payload.username,
password: payload.password,
friend_code: payload.friend_code,
referrer: payload.referrer,
last_ip: ip
}, { transacting })
.tap((user) => {
return new Dex().save({
user_id: user.id,
title,
slug: Slug(title, { lower: true })
title: payload.title,
slug: Slug(payload.title, { lower: true }),
shiny: payload.shiny,
generation: payload.generation
}, { transacting });
});
});
Expand Down
5 changes: 4 additions & 1 deletion src/validators/users/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ module.exports = Joi.object().keys({
username: Joi.string().token().max(20).trim().required(),
password: Joi.string().min(8).max(72).required(),
friend_code: Joi.string().regex(/^\d{4}-\d{4}-\d{4}$/).empty(['', null]),
referrer: Joi.string().empty(['', null])
referrer: Joi.string().empty(['', null]),
title: Joi.string().max(300).trim().default('Living Dex'),
shiny: Joi.boolean().default(false),
generation: Joi.number().integer().min(1).max(6).default(6)
})
.options({
language: {
Expand Down
31 changes: 12 additions & 19 deletions test/plugins/features/users/controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@ describe('user controller', () => {
describe('create', () => {

const request = { headers: {}, info: {} };
const username = 'test';
const password = 'test';
const title = 'Living Dex';
const shiny = false;
const generation = 6;

it('saves a user with a hashed password', () => {
const username = 'test';
const password = 'test';

return Controller.create({ username, password }, request)
return Controller.create({ username, password, title, shiny, generation }, request)
.then(() => new User().where('username', username).fetch())
.then((user) => {
expect(user.get('password')).to.not.eql(password);
Expand All @@ -94,9 +96,7 @@ describe('user controller', () => {
});

it('returns a session with a user token', () => {
const username = 'test';

return Controller.create({ username, password: 'test' }, request)
return Controller.create({ username, password, title, shiny, generation }, request)
.then((session) => {
expect(session.token).to.be.a('string');

Expand All @@ -107,30 +107,25 @@ describe('user controller', () => {
});

it('saves last login date', () => {
const username = 'test';

return Controller.create({ username, password: 'test' }, request)
return Controller.create({ username, password, title, shiny, generation }, request)
.then(() => new User().where('username', username).fetch())
.then((user) => {
expect(user.get('last_login')).to.be.an.instanceof(Date);
});
});

it('saves referrer', () => {
const username = 'test';
const referrer = 'http://test.com';

return Controller.create({ username, password: 'test', referrer }, request)
return Controller.create({ username, password, referrer, title, shiny, generation }, request)
.then(() => new User().where('username', username).fetch())
.then((user) => {
expect(user.get('referrer')).to.eql(referrer);
});
});

it('saves a default dex', () => {
const username = 'test';

return Controller.create({ username, password: 'test' }, request)
return Controller.create({ username, password, title, shiny, generation }, request)
.then(() => new User().where('username', username).fetch())
.then((user) => new Dex().where('user_id', user.id).fetch())
.then((dex) => {
Expand All @@ -141,9 +136,7 @@ describe('user controller', () => {

it('rejects if the username is already taken', () => {
return Knex('users').insert(firstUser)
.then(() => {
return Controller.create({ username: firstUser.username, password: 'test' }, request);
})
.then(() => Controller.create({ username: firstUser.username, password, title, shiny, generation }, request))
.catch((err) => err)
.then((err) => {
expect(err).to.be.an.instanceof(Errors.ExistingUsername);
Expand All @@ -153,7 +146,7 @@ describe('user controller', () => {
it('rejects if the username is taken after the fetch', () => {
Sinon.stub(User.prototype, 'save').throws(new Error('duplicate key value'));

return Controller.create({ username: firstUser.username, password: 'test' }, request)
return Controller.create({ username: firstUser.username, password, title, shiny, generation }, request)
.catch((err) => err)
.then((err) => {
expect(err).to.be.an.instanceof(Errors.ExistingUsername);
Expand Down
94 changes: 94 additions & 0 deletions test/validators/users/create.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ describe('users create validator', () => {

describe('username', () => {

it('is required', () => {
const data = { password: 'testtest' };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error.details[0].path).to.eql('username');
expect(result.error.details[0].type).to.eql('any.required');
});

it('allows alpha-numeric and underscore characters', () => {
const data = { username: 'test_TEST', password: 'testtest' };
const result = Joi.validate(data, UsersCreateValidator);
Expand Down Expand Up @@ -43,6 +51,14 @@ describe('users create validator', () => {

describe('password', () => {

it('is required', () => {
const data = { username: 'testing' };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error.details[0].path).to.eql('password');
expect(result.error.details[0].type).to.eql('any.required');
});

it('requires at least 8 characters', () => {
const data = { username: 'testing', password: 'a'.repeat(7) };
const result = Joi.validate(data, UsersCreateValidator);
Expand All @@ -63,6 +79,13 @@ describe('users create validator', () => {

describe('friend_code', () => {

it('is optional', () => {
const data = { username: 'testing', password: 'testtest' };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error).to.not.exist;
});

it('converts null to undefined', () => {
const data = { username: 'testing', password: 'testtest', friend_code: null };
const result = Joi.validate(data, UsersCreateValidator);
Expand Down Expand Up @@ -128,4 +151,75 @@ describe('users create validator', () => {

});

describe('title', () => {

it('defaults to "Living Dex"', () => {
const data = { username: 'testing', password: 'testtest' };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.value.title).to.eql('Living Dex');
});

it('limits to 300 characters', () => {
const data = { username: 'testing', password: 'testtest', title: 'a'.repeat(301) };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error.details[0].path).to.eql('title');
expect(result.error.details[0].type).to.eql('string.max');
});

});

describe('shiny', () => {

it('defaults to false', () => {
const data = { username: 'testing', password: 'testtest' };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.value.shiny).to.be.false;
});

});

describe('generation', () => {

it('defaults to 6', () => {
const data = { username: 'testing', password: 'testtest' };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.value.generation).to.eql(6);
});

it('allows at least 1', () => {
const data = { username: 'testing', password: 'testtest', generation: 1 };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error).to.not.exist;
});

it('allows at most 6', () => {
const data = { username: 'testing', password: 'testtest', generation: 6 };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error).to.not.exist;
});

it('disallows less than 1', () => {
const data = { username: 'testing', password: 'testtest', generation: 0 };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error.details[0].path).to.eql('generation');
expect(result.error.details[0].type).to.eql('number.min');
});

it('disallows more than 6', () => {
const data = { username: 'testing', password: 'testtest', generation: 7 };
const result = Joi.validate(data, UsersCreateValidator);

expect(result.error.details[0].path).to.eql('generation');
expect(result.error.details[0].type).to.eql('number.max');
});

});

});

0 comments on commit 69830d8

Please sign in to comment.