mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 22:27:26 +01:00
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:
committed by
Matteo Pagliazzi
parent
26c8323e70
commit
71c0939a15
@@ -8,7 +8,11 @@ describe('POST /user/allocate', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
// 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 () => {
|
||||
await user.update({'stats.points': 1});
|
||||
let res = await user.post('/user/allocate?stat=con');
|
||||
|
||||
@@ -13,7 +13,11 @@ describe('POST /user/allocate-bulk', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
user = await generateUser();
|
||||
user = await generateUser({
|
||||
'stats.lvl': 10,
|
||||
'flags.classSelected': true,
|
||||
'preferences.disableClasses': false,
|
||||
});
|
||||
});
|
||||
|
||||
// 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 () => {
|
||||
await user.update({'stats.points': 3});
|
||||
|
||||
|
||||
52
test/common/libs/hasClass.test.js
Normal file
52
test/common/libs/hasClass.test.js
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,11 @@ describe('shared.ops.allocate', () => {
|
||||
let user;
|
||||
|
||||
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) => {
|
||||
@@ -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) => {
|
||||
try {
|
||||
allocate(user);
|
||||
|
||||
@@ -13,7 +13,11 @@ describe('shared.ops.allocateBulk', () => {
|
||||
let user;
|
||||
|
||||
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) => {
|
||||
@@ -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) => {
|
||||
try {
|
||||
allocateBulk(user, {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import memberHasClass from 'common/script/libs/hasClass';
|
||||
|
||||
export function isBuffed () {
|
||||
return (member) => {
|
||||
const buffs = member.stats.buffs;
|
||||
@@ -6,7 +8,5 @@ export function isBuffed () {
|
||||
}
|
||||
|
||||
export function hasClass () {
|
||||
return (member) => {
|
||||
return member.stats.lvl >= 10 && !member.preferences.disableClasses && member.flags.classSelected;
|
||||
};
|
||||
return memberHasClass;
|
||||
}
|
||||
@@ -204,6 +204,7 @@
|
||||
"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.",
|
||||
"notEnoughAttrPoints": "You don't have enough Stat Points.",
|
||||
"classNotSelected": "You must select Class before you can assign Stat Points.",
|
||||
"style": "Style",
|
||||
"facialhair": "Facial",
|
||||
"photo": "Photo",
|
||||
|
||||
@@ -110,6 +110,9 @@ api.achievements = achievements;
|
||||
import randomVal from './libs/randomVal';
|
||||
api.randomVal = randomVal;
|
||||
|
||||
import hasClass from './libs/hasClass';
|
||||
api.hasClass = hasClass;
|
||||
|
||||
import autoAllocate from './fns/autoAllocate';
|
||||
import crit from './fns/crit';
|
||||
import handleTwoHanded from './fns/handleTwoHanded';
|
||||
|
||||
8
website/common/script/libs/hasClass.js
Normal file
8
website/common/script/libs/hasClass.js
Normal 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
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../libs/errors';
|
||||
import i18n from '../../i18n';
|
||||
import errorMessage from '../../libs/errorMessage';
|
||||
import hasClass from '../../libs/hasClass';
|
||||
|
||||
module.exports = function allocate (user, req = {}) {
|
||||
let stat = get(req, 'query.stat', 'str');
|
||||
@@ -16,6 +17,10 @@ module.exports = function allocate (user, req = {}) {
|
||||
throw new BadRequest(errorMessage('invalidAttribute', {attr: stat}));
|
||||
}
|
||||
|
||||
if (!hasClass(user)) {
|
||||
throw new NotAuthorized(i18n.t('classNotSelected', req.language));
|
||||
}
|
||||
|
||||
if (user.stats.points > 0) {
|
||||
user.stats[stat]++;
|
||||
user.stats.points--;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '../../libs/errors';
|
||||
import i18n from '../../i18n';
|
||||
import errorMessage from '../../libs/errorMessage';
|
||||
import hasClass from '../../libs/hasClass';
|
||||
|
||||
module.exports = function allocateBulk (user, req = {}) {
|
||||
const stats = get(req, 'body.stats');
|
||||
@@ -21,6 +22,10 @@ module.exports = function allocateBulk (user, req = {}) {
|
||||
throw new BadRequest(errorMessage('invalidAttribute', {attr: invalidStats.join(',')}));
|
||||
}
|
||||
|
||||
if (!hasClass(user)) {
|
||||
throw new NotAuthorized(i18n.t('classNotSelected', req.language));
|
||||
}
|
||||
|
||||
if (user.stats.points <= 0) {
|
||||
throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user