Skip to content

Commit

Permalink
convert buyGear to buyMarketGearOperation + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
negue committed Feb 16, 2018
1 parent b0ae0ef commit d3304a9
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 87 deletions.
Expand Up @@ -4,14 +4,20 @@ import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../../helpers/common.helper';
import buyGear from '../../../../website/common/script/ops/buy/buyGear';
import {BuyMarketGearOperation} from '../../../../website/common/script/ops/buy/buyMarketGear';
import shared from '../../../../website/common/script';
import {
BadRequest, NotAuthorized, NotFound,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';

describe('shared.ops.buyGear', () => {
function buyGear (user, req, analytics) {
let buyOp = new BuyMarketGearOperation(user, req, analytics);

return buyOp.purchase();
}

describe('shared.ops.buyMarketGear', () => {
let user;
let analytics = {track () {}};

Expand Down Expand Up @@ -100,6 +106,31 @@ describe('shared.ops.buyGear', () => {
}
});

it('does not buy equipment of different class', (done) => {
user.stats.gp = 82;
user.stats.class = 'warrior';

try {
buyGear(user, {params: {key: 'weapon_special_winter2018Rogue'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('cannotBuyItem'));
done();
}
});

it('does not buy equipment in bulk', (done) => {
user.stats.gp = 82;

try {
buyGear(user, {params: {key: 'armor_warrior_1'}, quantity: 3});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotAbleToBuyInBulk'));
done();
}
});

// TODO after user.ops.equip is done
xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => {
user.stats.gp = 100;
Expand Down
133 changes: 133 additions & 0 deletions website/common/script/ops/buy/abstractBuyOperation.js
@@ -0,0 +1,133 @@
import i18n from '../../i18n';
import {
NotAuthorized,
} from '../../libs/errors';
import _merge from 'lodash/merge';
import _get from 'lodash/get';

export class NotImplementedError extends Error {
constructor (str) {
super(`Method: '${str}' not implemented`);
}
}

export class AbstractBuyOperation {
/**
* @param {User} user - the User-Object
* @param {Request} req - the Request-Object
* @param {analytics} analytics
*/
constructor (user, req, analytics) {
this.user = user;
this.req = req || {};
this.analytics = analytics;

this.quantity = _get(req, 'quantity', 1);
}

/**
* Shortcut to get the translated string without passing `req.language`
* @param {String} key - translation key
* @param {*=} params
* @returns {*|string}
*/
// eslint-disable-next-line no-unused-vars
i18n (key, params = {}) {
return i18n.t.apply(null, [...arguments, this.req.language]);
}

/**
* If the Operation allows purchasing items by quantity
* @returns Boolean
*/
multiplePurchaseAllowed () {
throw new NotImplementedError('multiplePurchaseAllowed');
}

/**
* Method is called to save the params as class-fields in order to access them
*/
extractAndValidateParams () {
throw new NotImplementedError('extractAndValidateParams');
}

executeChanges () {
throw new NotImplementedError('executeChanges');
}

analyticsData () {
throw new NotImplementedError('sendToAnalytics');
}

purchase () {
if (!this.multiplePurchaseAllowed() && this.quantity > 1) {
throw new NotAuthorized(this.i18n('messageNotAbleToBuyInBulk'));
}

this.extractAndValidateParams(this.user, this.req);

let resultObj = this.executeChanges(this.user, this.item, this.req);

if (this.analytics) {
this.sendToAnalytics(this.analyticsData());
}

return resultObj;
}

sendToAnalytics (additionalData = {}) {
// spread-operator produces an "unexpected token" error
let analyticsData = _merge(additionalData, {
// ...additionalData,
uuid: this.user._id,
category: 'behavior',
headers: this.req.headers,
});

if (this.multiplePurchaseAllowed()) {
analyticsData.quantityPurchased = this.quantity;
}

this.analytics.track('acquire item', analyticsData);
}
}

export class AbstractGoldItemOperation extends AbstractBuyOperation {
constructor (user, req, analytics) {
super(user, req, analytics);
}

getItemValue (item) {
return item.value;
}

canUserPurchase (user, item) {
this.item = item;
let itemValue = this.getItemValue(item);

let userGold = user.stats.gp;

if (userGold < itemValue * this.quantity) {
throw new NotAuthorized(this.i18n('messageNotEnoughGold'));
}

if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(this.i18n('cannotBuyItem'));
}
}

substractCurrency (user, item, quantity = 1) {
let itemValue = this.getItemValue(item);

user.stats.gp -= itemValue * quantity;
}

analyticsData () {
return {
itemKey: this.item.key,
itemType: 'Market',
acquireMethod: 'Gold',
goldCost: this.getItemValue(this.item),
};
}
}
9 changes: 6 additions & 3 deletions website/common/script/ops/buy/buy.js
Expand Up @@ -5,7 +5,7 @@ import {
} from '../../libs/errors';
import buyHealthPotion from './buyHealthPotion';
import buyArmoire from './buyArmoire';
import buyGear from './buyGear';
import {BuyMarketGearOperation} from './buyMarketGear';
import buyMysterySet from './buyMysterySet';
import buyQuest from './buyQuest';
import buySpecialSpell from './buySpecialSpell';
Expand Down Expand Up @@ -58,9 +58,12 @@ module.exports = function buy (user, req = {}, analytics) {
case 'special':
buyRes = buySpecialSpell(user, req, analytics);
break;
default:
buyRes = buyGear(user, req, analytics);
default: {
const buyOp = new BuyMarketGearOperation(user, req, analytics);

buyRes = buyOp.purchase();
break;
}
}

return buyRes;
Expand Down
82 changes: 0 additions & 82 deletions website/common/script/ops/buy/buyGear.js

This file was deleted.

78 changes: 78 additions & 0 deletions website/common/script/ops/buy/buyMarketGear.js
@@ -0,0 +1,78 @@
import content from '../../content/index';
import get from 'lodash/get';
import pick from 'lodash/pick';
import splitWhitespace from '../../libs/splitWhitespace';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../../libs/errors';
import handleTwoHanded from '../../fns/handleTwoHanded';
import ultimateGear from '../../fns/ultimateGear';

import {removePinnedGearAddPossibleNewOnes} from '../pinnedGearUtils';

import { AbstractGoldItemOperation } from './abstractBuyOperation';

export class BuyMarketGearOperation extends AbstractGoldItemOperation {
constructor (user, req, analytics) {
super(user, req, analytics);
}

multiplePurchaseAllowed () {
return false;
}

extractAndValidateParams (user, req) {
let key = this.key = get(req, 'params.key');
if (!key) throw new BadRequest(this.i18n('missingKeyParam'));

let item = content.gear.flat[key];

if (!item) throw new NotFound(this.i18n('itemNotFound', {key}));

super.canUserPurchase(user, item);

if (user.items.gear.owned[item.key]) {
throw new NotAuthorized(this.i18n('equipmentAlreadyOwned'));
}

let itemIndex = Number(item.index);

if (Number.isInteger(itemIndex) && content.classes.includes(item.klass)) {
let previousLevelGear = key.replace(/[0-9]/, itemIndex - 1);
let hasPreviousLevelGear = user.items.gear.owned[previousLevelGear];
let checkIndexToType = itemIndex > (item.type === 'weapon' || item.type === 'shield' && item.klass === 'rogue' ? 0 : 1);

if (checkIndexToType && !hasPreviousLevelGear) {
throw new NotAuthorized(this.i18n('previousGearNotOwned'));
}
}
}

executeChanges (user, item, req) {
let message;

if (user.preferences.autoEquip) {
user.items.gear.equipped[item.type] = item.key;
message = handleTwoHanded(user, item, undefined, req);
}

removePinnedGearAddPossibleNewOnes(user, `gear.flat.${item.key}`, item.key);

if (item.last) ultimateGear(user);

this.substractCurrency(user, item);

if (!message) {
message = this.i18n('messageBought', {
itemText: item.text(req.language),
});
}

return [
pick(user, splitWhitespace('items achievements stats flags')),
message,
];
}
}

0 comments on commit d3304a9

Please sign in to comment.