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;
|
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');
|
||||||
|
|||||||
@@ -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});
|
||||||
|
|
||||||
|
|||||||
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;
|
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);
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
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';
|
} 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--;
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user