mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-14 21:27:23 +01:00
358 lines
14 KiB
JavaScript
358 lines
14 KiB
JavaScript
import get from 'lodash/get';
|
|
import unlock from '../../../website/common/script/ops/unlock';
|
|
import i18n from '../../../website/common/script/i18n';
|
|
import { generateUser } from '../../helpers/common.helper';
|
|
import {
|
|
NotAuthorized,
|
|
BadRequest,
|
|
} from '../../../website/common/script/libs/errors';
|
|
|
|
describe('shared.ops.unlock', () => {
|
|
let user;
|
|
let clock;
|
|
const unlockPath = 'shirt.convict,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie';
|
|
const unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars';
|
|
const backgroundUnlockPath = 'background.giant_florals';
|
|
const hairUnlockPath = 'hair.color.rainbow,hair.color.yellow,hair.color.green,hair.color.purple,hair.color.blue,hair.color.TRUred';
|
|
const facialHairUnlockPath = 'hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3';
|
|
const usersStartingGems = 50 / 4;
|
|
|
|
beforeEach(() => {
|
|
user = generateUser();
|
|
user.balance = usersStartingGems;
|
|
clock = sandbox.useFakeTimers(new Date('2024-04-10'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
clock.restore();
|
|
});
|
|
|
|
it('returns an error when path is not provided', async () => {
|
|
try {
|
|
await unlock(user);
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('pathRequired'));
|
|
}
|
|
});
|
|
|
|
it('does not unlock lost gear', async () => {
|
|
user.items.gear.owned.headAccessory_special_bearEars = false;
|
|
|
|
await unlock(user, {
|
|
query: { path: 'items.gear.owned.headAccessory_special_bearEars' },
|
|
});
|
|
|
|
expect(user.balance).to.equal(usersStartingGems);
|
|
});
|
|
|
|
it('returns an error when user balance is too low', async () => {
|
|
user.balance = 0;
|
|
|
|
try {
|
|
await unlock(user, { query: { path: unlockPath } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(NotAuthorized);
|
|
expect(err.message).to.equal(i18n.t('notEnoughGems'));
|
|
}
|
|
});
|
|
|
|
it('returns an error when user already owns a full set', async () => {
|
|
let expectedBalance;
|
|
|
|
try {
|
|
await unlock(user, { query: { path: unlockPath } });
|
|
expectedBalance = user.balance;
|
|
await unlock(user, { query: { path: unlockPath } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(NotAuthorized);
|
|
expect(err.message).to.equal(i18n.t('alreadyUnlocked'));
|
|
expect(user.balance).to.equal(expectedBalance);
|
|
}
|
|
});
|
|
|
|
it('returns an error when user already owns a full set of gear', async () => {
|
|
let expectedBalance;
|
|
|
|
try {
|
|
await unlock(user, { query: { path: unlockGearSetPath } });
|
|
expectedBalance = user.balance;
|
|
await unlock(user, { query: { path: unlockGearSetPath } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(NotAuthorized);
|
|
expect(err.message).to.equal(i18n.t('alreadyUnlocked'));
|
|
expect(user.balance).to.equal(expectedBalance);
|
|
}
|
|
});
|
|
|
|
it('returns an error if an item does not exists', async () => {
|
|
try {
|
|
await unlock(user, { query: { path: 'background.invalid_background' } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
|
}
|
|
});
|
|
|
|
it('returns an error if there are items from multiple sets', async () => {
|
|
try {
|
|
await unlock(user, { query: { path: 'shirt.convict,skin.0ff591' } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
|
}
|
|
});
|
|
|
|
it('returns an error if gear is not from the animal set', async () => {
|
|
try {
|
|
await unlock(user, {
|
|
query: { path: 'items.gear.owned.back_mystery_202004' },
|
|
});
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
|
}
|
|
});
|
|
|
|
it('returns an error if the item is free', async () => {
|
|
try {
|
|
await unlock(user, { query: { path: 'shirt.black' } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
|
}
|
|
});
|
|
|
|
it('returns an error if an item does not belong to a set (appearances)', async () => {
|
|
try {
|
|
await unlock(user, { query: { path: 'shirt.pink' } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
|
}
|
|
});
|
|
|
|
it('returns an error when user already owns items in a full set and it would be more expensive to buy the entire set', async () => {
|
|
try {
|
|
// There are 11 shirts in the set, each cost 2 gems, the full set 5 gems
|
|
// In order for the full purchase not to be worth, we must own 9
|
|
const partialUnlockPaths = unlockPath.split(',');
|
|
await unlock(user, { query: { path: partialUnlockPaths[0] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[1] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[2] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[3] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[4] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[5] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[6] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[7] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[8] } });
|
|
|
|
await unlock(user, { query: { path: unlockPath } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(NotAuthorized);
|
|
expect(err.message).to.equal(i18n.t('alreadyUnlockedPart'));
|
|
}
|
|
});
|
|
|
|
it('does not return an error when user already owns items in a full set and it would not be more expensive to buy the entire set', async () => {
|
|
// There are 11 shirts in the set, each cost 2 gems, the full set 5 gems
|
|
// In order for the full purchase to be worth, we can own already 8
|
|
const partialUnlockPaths = unlockPath.split(',');
|
|
await unlock(user, { query: { path: partialUnlockPaths[0] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[1] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[2] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[3] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[4] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[5] } });
|
|
await unlock(user, { query: { path: partialUnlockPaths[6] } });
|
|
|
|
await unlock(user, { query: { path: unlockPath } });
|
|
});
|
|
|
|
it('equips an item already owned', async () => {
|
|
expect(user.purchased.background.giant_florals).to.not.exist;
|
|
|
|
await unlock(user, { query: { path: backgroundUnlockPath } });
|
|
const afterBalance = user.balance;
|
|
const response = await unlock(user, {
|
|
query: { path: backgroundUnlockPath },
|
|
});
|
|
expect(user.balance).to.equal(afterBalance); // do not bill twice
|
|
|
|
expect(response.message).to.not.exist;
|
|
expect(user.preferences.background).to.equal('giant_florals');
|
|
});
|
|
|
|
it('un-equips a background already equipped', async () => {
|
|
expect(user.purchased.background.giant_florals).to.not.exist;
|
|
|
|
await unlock(user, { query: { path: backgroundUnlockPath } }); // unlock
|
|
const afterBalance = user.balance;
|
|
await unlock(user, { query: { path: backgroundUnlockPath } }); // equip
|
|
const response = await unlock(user, {
|
|
query: { path: backgroundUnlockPath },
|
|
});
|
|
expect(user.balance).to.equal(afterBalance); // do not bill twice
|
|
|
|
expect(response.message).to.not.exist;
|
|
expect(user.preferences.background).to.equal('');
|
|
});
|
|
|
|
it('unlocks a full set of appearance items', async () => {
|
|
const initialShirts = Object.keys(user.purchased.shirt).length;
|
|
const [, message] = await unlock(user, { query: { path: unlockPath } });
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
const individualPaths = unlockPath.split(',');
|
|
individualPaths.forEach(path => {
|
|
expect(get(user.purchased, path)).to.be.true;
|
|
});
|
|
expect(Object.keys(user.purchased.shirt).length).to.equal(
|
|
initialShirts + individualPaths.length,
|
|
);
|
|
expect(user.balance).to.equal(usersStartingGems - 1.25);
|
|
});
|
|
|
|
it('unlocks a full set of hair items', async () => {
|
|
user.purchased.hair.color = {};
|
|
|
|
const initialHairColors = Object.keys(user.purchased.hair.color).length;
|
|
const [, message] = await unlock(user, { query: { path: hairUnlockPath } });
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
const individualPaths = hairUnlockPath.split(',');
|
|
individualPaths.forEach(path => {
|
|
expect(get(user.purchased, path)).to.be.true;
|
|
});
|
|
expect(Object.keys(user.purchased.hair.color).length).to.equal(
|
|
initialHairColors + individualPaths.length,
|
|
);
|
|
expect(user.balance).to.equal(usersStartingGems - 1.25);
|
|
});
|
|
|
|
it('unlocks the facial hair set', async () => {
|
|
user.purchased.hair.mustache = {};
|
|
user.purchased.hair.beard = {};
|
|
|
|
const initialMustache = Object.keys(user.purchased.hair.mustache).length;
|
|
const initialBeard = Object.keys(user.purchased.hair.mustache).length;
|
|
const [, message] = await unlock(user, {
|
|
query: { path: facialHairUnlockPath },
|
|
});
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
const individualPaths = facialHairUnlockPath.split(',');
|
|
individualPaths.forEach(path => {
|
|
expect(get(user.purchased, path)).to.be.true;
|
|
});
|
|
expect(
|
|
Object.keys(user.purchased.hair.mustache).length
|
|
+ Object.keys(user.purchased.hair.beard).length,
|
|
) // eslint-disable-line max-len
|
|
.to.equal(initialMustache + initialBeard + individualPaths.length);
|
|
expect(user.balance).to.equal(usersStartingGems - 1.25);
|
|
});
|
|
|
|
it('unlocks a full set of gear', async () => {
|
|
const initialGear = Object.keys(user.items.gear.owned).length;
|
|
const [, message] = await unlock(user, {
|
|
query: { path: unlockGearSetPath },
|
|
});
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
|
|
const individualPaths = unlockGearSetPath.split(',');
|
|
individualPaths.forEach(path => {
|
|
expect(get(user, path)).to.be.true;
|
|
});
|
|
expect(Object.keys(user.items.gear.owned).length).to.equal(
|
|
initialGear + individualPaths.length,
|
|
);
|
|
expect(user.balance).to.equal(usersStartingGems - 1.25);
|
|
});
|
|
|
|
it('unlocks an item (appearance)', async () => {
|
|
const path = unlockPath.split(',')[0];
|
|
const initialShirts = Object.keys(user.purchased.shirt).length;
|
|
const [, message] = await unlock(user, { query: { path } });
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
expect(Object.keys(user.purchased.shirt).length).to.equal(
|
|
initialShirts + 1,
|
|
);
|
|
expect(get(user.purchased, path)).to.be.true;
|
|
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
|
});
|
|
|
|
it('unlocks an item (hair color)', async () => {
|
|
user.purchased.hair.color = {};
|
|
|
|
const path = hairUnlockPath.split(',')[0];
|
|
const initialColorHair = Object.keys(user.purchased.hair.color).length;
|
|
const [, message] = await unlock(user, { query: { path } });
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
expect(Object.keys(user.purchased.hair.color).length).to.equal(
|
|
initialColorHair + 1,
|
|
);
|
|
expect(get(user.purchased, path)).to.be.true;
|
|
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
|
});
|
|
|
|
it('unlocks an item (facial hair)', async () => {
|
|
user.purchased.hair.mustache = {};
|
|
user.purchased.hair.beard = {};
|
|
|
|
const path = facialHairUnlockPath.split(',')[0];
|
|
const initialMustache = Object.keys(user.purchased.hair.mustache).length;
|
|
const initialBeard = Object.keys(user.purchased.hair.beard).length;
|
|
const [, message] = await unlock(user, { query: { path } });
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
|
|
expect(Object.keys(user.purchased.hair.mustache).length).to.equal(
|
|
initialMustache + 1,
|
|
);
|
|
expect(Object.keys(user.purchased.hair.beard).length).to.equal(
|
|
initialBeard,
|
|
);
|
|
|
|
expect(get(user.purchased, path)).to.be.true;
|
|
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
|
});
|
|
|
|
it('unlocks an item (gear)', async () => {
|
|
const path = unlockGearSetPath.split(',')[0];
|
|
const initialGear = Object.keys(user.items.gear.owned).length;
|
|
const [, message] = await unlock(user, { query: { path } });
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
expect(Object.keys(user.items.gear.owned).length).to.equal(initialGear + 1);
|
|
expect(get(user, path)).to.be.true;
|
|
expect(user.balance).to.equal(usersStartingGems - 0.5);
|
|
});
|
|
|
|
it('unlocks an item (background)', async () => {
|
|
const initialBackgrounds = Object.keys(user.purchased.background).length;
|
|
const [, message] = await unlock(user, {
|
|
query: { path: backgroundUnlockPath },
|
|
});
|
|
|
|
expect(message).to.equal(i18n.t('unlocked'));
|
|
expect(Object.keys(user.purchased.background).length).to.equal(
|
|
initialBackgrounds + 1,
|
|
);
|
|
expect(get(user.purchased, backgroundUnlockPath)).to.be.true;
|
|
expect(user.balance).to.equal(usersStartingGems - 1.75);
|
|
});
|
|
|
|
it('handles an invalid hair path gracefully', async () => {
|
|
try {
|
|
await unlock(user, { query: { path: 'hair.invalid' } });
|
|
} catch (err) {
|
|
expect(err).to.be.an.instanceof(BadRequest);
|
|
expect(err.message).to.equal(i18n.t('invalidUnlockSet'));
|
|
}
|
|
});
|
|
});
|