Fix API early Stat Point allocation (#10680)

* Refactor hasClass check to common so it can be used in shared & server-side code

* Check that user has selected class before allocating stat points
This commit is contained in:
Carl Vuorinen
2018-09-21 17:55:55 +03:00
committed by Matteo Pagliazzi
parent 26c8323e70
commit 71c0939a15
11 changed files with 204 additions and 7 deletions

View File

@@ -8,7 +8,11 @@ describe('POST /user/allocate', () => {
let user; let user;
beforeEach(async () => { beforeEach(async () => {
user = await generateUser(); user = await generateUser({
'stats.lvl': 10,
'flags.classSelected': true,
'preferences.disableClasses': false,
});
}); });
// More tests in common code unit tests // More tests in common code unit tests
@@ -31,6 +35,16 @@ describe('POST /user/allocate', () => {
}); });
}); });
it('returns an error if the user hasn\'t selected class', async () => {
await user.update({'flags.classSelected': false});
await expect(user.post('/user/allocate'))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('classNotSelected'),
});
});
it('allocates attribute points', async () => { it('allocates attribute points', async () => {
await user.update({'stats.points': 1}); await user.update({'stats.points': 1});
let res = await user.post('/user/allocate?stat=con'); let res = await user.post('/user/allocate?stat=con');

View File

@@ -13,7 +13,11 @@ describe('POST /user/allocate-bulk', () => {
}; };
beforeEach(async () => { beforeEach(async () => {
user = await generateUser(); user = await generateUser({
'stats.lvl': 10,
'flags.classSelected': true,
'preferences.disableClasses': false,
});
}); });
// More tests in common code unit tests // More tests in common code unit tests
@@ -27,6 +31,16 @@ describe('POST /user/allocate-bulk', () => {
}); });
}); });
it('returns an error if user has not selected class', async () => {
await user.update({'flags.classSelected': false});
await expect(user.post('/user/allocate-bulk', statsUpdate))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('classNotSelected'),
});
});
it('allocates attribute points', async () => { it('allocates attribute points', async () => {
await user.update({'stats.points': 3}); await user.update({'stats.points': 3});

View File

@@ -0,0 +1,52 @@
import hasClass from '../../../website/common/script/libs/hasClass';
import { generateUser } from '../../helpers/common.helper';
describe('hasClass', () => {
it('returns false for user with level below 10', () => {
let userLvl9 = generateUser({
'stats.lvl': 9,
'flags.classSelected': true,
'preferences.disableClasses': false,
});
let result = hasClass(userLvl9);
expect(result).to.eql(false);
});
it('returns false for user with class not selected', () => {
let userClassNotSelected = generateUser({
'stats.lvl': 10,
'flags.classSelected': false,
'preferences.disableClasses': false,
});
let result = hasClass(userClassNotSelected);
expect(result).to.eql(false);
});
it('returns false for user with classes disabled', () => {
let userClassesDisabled = generateUser({
'stats.lvl': 10,
'flags.classSelected': true,
'preferences.disableClasses': true,
});
let result = hasClass(userClassesDisabled);
expect(result).to.eql(false);
});
it('returns true for user with class', () => {
let userClassSelected = generateUser({
'stats.lvl': 10,
'flags.classSelected': true,
'preferences.disableClasses': false,
});
let result = hasClass(userClassSelected);
expect(result).to.eql(true);
});
});

View File

@@ -13,7 +13,11 @@ describe('shared.ops.allocate', () => {
let user; let user;
beforeEach(() => { beforeEach(() => {
user = generateUser(); user = generateUser({
'stats.lvl': 10,
'flags.classSelected': true,
'preferences.disableClasses': false,
});
}); });
it('throws an error if an invalid attribute is supplied', (done) => { it('throws an error if an invalid attribute is supplied', (done) => {
@@ -28,6 +32,39 @@ describe('shared.ops.allocate', () => {
} }
}); });
it('throws an error if the user is below lvl 10', (done) => {
user.stats.lvl = 9;
try {
allocate(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('classNotSelected'));
done();
}
});
it('throws an error if the user hasn\'t selected class', (done) => {
user.flags.classSelected = false;
try {
allocate(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('classNotSelected'));
done();
}
});
it('throws an error if the user has disabled classes', (done) => {
user.preferences.disableClasses = true;
try {
allocate(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('classNotSelected'));
done();
}
});
it('throws an error if the user doesn\'t have attribute points', (done) => { it('throws an error if the user doesn\'t have attribute points', (done) => {
try { try {
allocate(user); allocate(user);

View File

@@ -13,7 +13,11 @@ describe('shared.ops.allocateBulk', () => {
let user; let user;
beforeEach(() => { beforeEach(() => {
user = generateUser(); user = generateUser({
'stats.lvl': 10,
'flags.classSelected': true,
'preferences.disableClasses': false,
});
}); });
it('throws an error if an invalid attribute is supplied', (done) => { it('throws an error if an invalid attribute is supplied', (done) => {
@@ -43,6 +47,60 @@ describe('shared.ops.allocateBulk', () => {
} }
}); });
it('throws an error if the user is below lvl 10', (done) => {
user.stats.lvl = 9;
try {
allocateBulk(user, {
body: {
stats: {
int: 1,
str: 2,
},
},
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('classNotSelected'));
done();
}
});
it('throws an error if the user hasn\'t selected class', (done) => {
user.flags.classSelected = false;
try {
allocateBulk(user, {
body: {
stats: {
int: 1,
str: 2,
},
},
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('classNotSelected'));
done();
}
});
it('throws an error if the user has disabled classes', (done) => {
user.preferences.disableClasses = true;
try {
allocateBulk(user, {
body: {
stats: {
int: 1,
str: 2,
},
},
});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('classNotSelected'));
done();
}
});
it('throws an error if the user doesn\'t have attribute points', (done) => { it('throws an error if the user doesn\'t have attribute points', (done) => {
try { try {
allocateBulk(user, { allocateBulk(user, {

View File

@@ -1,3 +1,5 @@
import memberHasClass from 'common/script/libs/hasClass';
export function isBuffed () { export function isBuffed () {
return (member) => { return (member) => {
const buffs = member.stats.buffs; const buffs = member.stats.buffs;
@@ -6,7 +8,5 @@ export function isBuffed () {
} }
export function hasClass () { export function hasClass () {
return (member) => { return memberHasClass;
return member.stats.lvl >= 10 && !member.preferences.disableClasses && member.flags.classSelected;
};
} }

View File

@@ -204,6 +204,7 @@
"hideQuickAllocation": "Hide Stat Allocation", "hideQuickAllocation": "Hide Stat Allocation",
"quickAllocationLevelPopover": "Each level earns you one Point to assign to a Stat of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User Icon > Stats.", "quickAllocationLevelPopover": "Each level earns you one Point to assign to a Stat of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options found in User Icon > Stats.",
"notEnoughAttrPoints": "You don't have enough Stat Points.", "notEnoughAttrPoints": "You don't have enough Stat Points.",
"classNotSelected": "You must select Class before you can assign Stat Points.",
"style": "Style", "style": "Style",
"facialhair": "Facial", "facialhair": "Facial",
"photo": "Photo", "photo": "Photo",

View File

@@ -110,6 +110,9 @@ api.achievements = achievements;
import randomVal from './libs/randomVal'; import randomVal from './libs/randomVal';
api.randomVal = randomVal; api.randomVal = randomVal;
import hasClass from './libs/hasClass';
api.hasClass = hasClass;
import autoAllocate from './fns/autoAllocate'; import autoAllocate from './fns/autoAllocate';
import crit from './fns/crit'; import crit from './fns/crit';
import handleTwoHanded from './fns/handleTwoHanded'; import handleTwoHanded from './fns/handleTwoHanded';

View File

@@ -0,0 +1,8 @@
// Check if user has Class system enabled
module.exports = function hasClass (member) {
return (
member.stats.lvl >= 10 &&
!member.preferences.disableClasses &&
member.flags.classSelected
);
};

View File

@@ -8,6 +8,7 @@ import {
} from '../../libs/errors'; } from '../../libs/errors';
import i18n from '../../i18n'; import i18n from '../../i18n';
import errorMessage from '../../libs/errorMessage'; import errorMessage from '../../libs/errorMessage';
import hasClass from '../../libs/hasClass';
module.exports = function allocate (user, req = {}) { module.exports = function allocate (user, req = {}) {
let stat = get(req, 'query.stat', 'str'); let stat = get(req, 'query.stat', 'str');
@@ -16,6 +17,10 @@ module.exports = function allocate (user, req = {}) {
throw new BadRequest(errorMessage('invalidAttribute', {attr: stat})); throw new BadRequest(errorMessage('invalidAttribute', {attr: stat}));
} }
if (!hasClass(user)) {
throw new NotAuthorized(i18n.t('classNotSelected', req.language));
}
if (user.stats.points > 0) { if (user.stats.points > 0) {
user.stats[stat]++; user.stats[stat]++;
user.stats.points--; user.stats.points--;

View File

@@ -8,6 +8,7 @@ import {
} from '../../libs/errors'; } from '../../libs/errors';
import i18n from '../../i18n'; import i18n from '../../i18n';
import errorMessage from '../../libs/errorMessage'; import errorMessage from '../../libs/errorMessage';
import hasClass from '../../libs/hasClass';
module.exports = function allocateBulk (user, req = {}) { module.exports = function allocateBulk (user, req = {}) {
const stats = get(req, 'body.stats'); const stats = get(req, 'body.stats');
@@ -21,6 +22,10 @@ module.exports = function allocateBulk (user, req = {}) {
throw new BadRequest(errorMessage('invalidAttribute', {attr: invalidStats.join(',')})); throw new BadRequest(errorMessage('invalidAttribute', {attr: invalidStats.join(',')}));
} }
if (!hasClass(user)) {
throw new NotAuthorized(i18n.t('classNotSelected', req.language));
}
if (user.stats.points <= 0) { if (user.stats.points <= 0) {
throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language)); throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));
} }