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')); } }); });