<commit_before>/**
 * Statuses worked way different.
 * Sleep lasted longer, had no reset on switch and took a whole turn to wake up.
 * Frozen only thaws when hit by fire or Haze.
 *
 * Secondary effects to status (-speed, -atk) worked differently, so they are
 * separated as volatile statuses that are applied on switch in, removed
 * under certain conditions and re-applied under other conditions.
 */
exports.BattleStatuses = {
	brn: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'brn');
		},
		onAfterMoveSelf: function (pokemon) {
			this.damage(pokemon.maxhp / 16);
		},
		onSwitchIn: function (pokemon) {
			if (pokemon.side.foe.active[0] && pokemon.speed <= pokemon.side.foe.active[0].speed) {
				this.damage(pokemon.maxhp / 16);
			}
		}
	},
	par: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'par');
			target.addVolatile('parspeeddrop');
		},
		onBeforeMovePriority: 2,
		onBeforeMove: function (pokemon) {
			if (this.random(256) < 64) {
				this.add('cant', pokemon, 'par');
				pokemon.removeVolatile('bide');
				pokemon.removeVolatile('lockedmovee');
				pokemon.removeVolatile('twoturnmove');
				pokemon.removeVolatile('fly');
				pokemon.removeVolatile('dig');
				pokemon.removeVolatile('solarbeam');
				pokemon.removeVolatile('skullbash');
				pokemon.removeVolatile('partialtrappinglock');
				return false;
			}
		},
		onSwitchIn: function (pokemon) {
			pokemon.addVolatile('parspeeddrop');
		}
	},
	parspeeddrop: {
		onModifySpe: function (spe) {
			return this.clampIntRange(Math.floor(spe / 4), 1);
		}
	},
	slp: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'slp');
			// 1-7 turns. Put 1-7 since they awake at end of turn.
			this.effectData.startTime = this.random(1, 7);
			this.effectData.time = this.effectData.startTime;
		},
		onBeforeMovePriority: 2,
		onBeforeMove: function (pokemon, target, move) {
			pokemon.statusData.time--;
			this.add('cant', pokemon, 'slp');
			pokemon.lastMove = '';
			return false;
		},
		onAfterMoveSelf: function (pokemon) {
			if (pokemon.statusData.time <= 0) pokemon.cureStatus();
		}
	},
	frz: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'frz');
		},
		onBeforeMovePriority: 2,
		onBeforeMove: function (pokemon, target, move) {
			this.add('cant', pokemon, 'frz');
			pokemon.lastMove = '';
			return false;
		},
		onHit: function (target, source, move) {
			if (move.type === 'Fire' && move.category !== 'Status') {
				target.cureStatus();
			}
		}
	},
	psn: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'psn');
		},
		onAfterMoveSelf: function (pokemon) {
			this.damage(pokemon.maxhp / 16);
		},
		onSwitchIn: function (pokemon) {
			if (pokemon.side.foe.active[0] && pokemon.speed <= pokemon.side.foe.active[0].speed) {
				this.damage(pokemon.maxhp / 16);
			}
		}
	},
	tox: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'tox');
			this.effectData.stage = 0;
		},
		onAfterMoveSelf: function (pokemon) {
			if (this.effectData.stage < 15) {
				this.effectData.stage++;
			}
			this.damage(this.clampIntRange(pokemon.maxhp / 16, 1) * this.effectData.stage);
		},
		onSwitchIn: function (pokemon) {
			this.effectData.stage = 0; // probably unnecessary...
			pokemon.setStatus('psn');
			// normal poison damage...
			if (pokemon.side.foe.active[0] && pokemon.speed <= pokemon.side.foe.active[0].speed) {
				this.damage(pokemon.maxhp / 16);
			}
		}
	},
	confusion: {
		// this is a volatile status
		onStart: function (target) {
			var result = this.runEvent('TryConfusion');
			if (!result) return result;
			this.add('-start', target, 'confusion');
			this.effectData.time = this.random(2, 6);
		},
		onEnd: function (target) {
			this.add('-end', target, 'confusion');
		},
		onBeforeMove: function (pokemon) {
			pokemon.volatiles.confusion.time--;
			if (!pokemon.volatiles.confusion.time) {
				pokemon.removeVolatile('confusion');
				return;
			}
			this.add('-activate', pokemon, 'confusion');
			if (this.random(256) >= 128) {
				this.directDamage(this.getDamage(pokemon, pokemon, 40));
				pokemon.removeVolatile('bide');
				pokemon.removeVolatile('lockedmovee');
				pokemon.removeVolatile('twoturnmove');
				pokemon.removeVolatile('fly');
				pokemon.removeVolatile('dig');
				pokemon.removeVolatile('solarbeam');
				pokemon.removeVolatile('skullbash');
				pokemon.removeVolatile('partialtrappinglock');
				return false;
			}
			return;
		}
	},
	flinch: {
		duration: 1,
		onBeforeMovePriority: 1,
		onBeforeMove: function (pokemon) {
			if (!this.runEvent('Flinch', pokemon)) {
				return;
			}
			this.add('cant', pokemon, 'flinch');
			return false;
		}
	},
	trapped: {
		noCopy: true,
		onModifyPokemon: function (pokemon) {
			if (!this.effectData.source || !this.effectData.source.isActive) {
				delete pokemon.volatiles['trapped'];
				return;
			}
			pokemon.trapped = true;
		}
	},
	partiallytrapped: {
		duration: 2,
		onBeforeMovePriority: 1,
		onStart: function (target, source, effect) {
			this.add('-activate', target, 'move: ' + effect, '[of] ' + source);
		},
		onBeforeMove: function (pokemon) {
			this.add('cant', pokemon, 'partiallytrapped');
			return false;
		},
		onEnd: function (pokemon) {
			this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]');
		}
	},
	partialtrappinglock: {
		durationCallback: function () {
			var roll = this.random(6);
			var duration = [2, 2, 3, 3, 4, 5][roll];
			return duration;
		},
		onResidual: function (target) {
			if (target.lastMove === 'struggle' || target.status === 'slp') {
				delete target.volatiles['partialtrappinglock'];
			}
		},
		onStart: function (target, source, effect) {
			this.effectData.move = effect.id;
		},
		onModifyPokemon: function (pokemon) {
			if (!pokemon.hasMove(this.effectData.move)) {
				return;
			}
			var moves = pokemon.moveset;
			for (var i = 0; i < moves.length; i++) {
				if (moves[i].id !== this.effectData.move) {
					moves[i].disabled = true;
				}
			}
		}
	},
	lockedmove: {
		// Outrage, Thrash, Petal Dance...
		durationCallback: function () {
			return this.random(2, 4);
		},
		onResidual: function (target) {
			if (target.lastMove === 'struggle' || target.status === 'slp') {
				// don't lock, and bypass confusion for calming
				delete target.volatiles['lockedmove'];
			}
		},
		onStart: function (target, source, effect) {
			this.effectData.move = effect.id;
		},
		onEnd: function (target) {
			this.add('-end', target, 'rampage');
			target.addVolatile('confusion');
		},
		onLockMove: function (pokemon) {
			return this.effectData.move;
		},
		onBeforeTurn: function (pokemon) {
			var move = this.getMove(this.effectData.move);
			if (move.id) {
				this.debug('Forcing into ' + move.id);
				this.changeDecision(pokemon, {move: move.id});
			}
		}
	},
	mustrecharge: {
		duration: 2,
		onBeforeMove: function (pokemon) {
			this.add('cant', pokemon, 'recharge');
			pokemon.removeVolatile('mustrecharge');
			return false;
		},
		onLockMove: 'recharge'
	},
	futuremove: {
		// this is a side condition
		onStart: function (side) {
			this.effectData.positions = [];
			for (var i = 0; i < side.active.length; i++) {
				this.effectData.positions[i] = null;
			}
		},
		onResidualOrder: 3,
		onResidual: function (side) {
			var finished = true;
			for (var i = 0; i < side.active.length; i++) {
				var posData = this.effectData.positions[i];
				if (!posData) continue;

				posData.duration--;

				if (posData.duration > 0) {
					finished = false;
					continue;
				}

				// time's up; time to hit! :D
				var target = side.foe.active[posData.targetPosition];
				var move = this.getMove(posData.move);
				if (target.fainted) {
					this.add('-hint', '' + move.name + ' did not hit because the target is fainted.');
					this.effectData.positions[i] = null;
					continue;
				}

				this.add('-fieldactivate', move);
				target.removeVolatile('Protect');
				target.removeVolatile('Endure');

				if (typeof posData.moveData.affectedByImmunities === 'undefined') {
					posData.moveData.affectedByImmunities = true;
				}

				this.moveHit(target, posData.source, move, posData.moveData);

				this.effectData.positions[i] = null;
			}
			if (finished) {
				side.removeSideCondition('futuremove');
			}
		}
	},
	stall: {
		// Protect, Detect, Endure counter
		duration: 2,
		counterMax: 256,
		onStart: function () {
			this.effectData.counter = 2;
		},
		onStallMove: function () {
			// this.effectData.counter should never be undefined here.
			// However, just in case, use 1 if it is undefined.
			var counter = this.effectData.counter || 1;
			if (counter >= 256) {
				// 2^32 - special-cased because Battle.random(n) can't handle n > 2^16 - 1
				return (this.random() * 4294967296 < 1);
			}
			this.debug("Success chance: " + Math.round(100 / counter) + "%");
			return (this.random(counter) === 0);
		},
		onRestart: function () {
			if (this.effectData.counter < this.effect.counterMax) {
				this.effectData.counter *= 2;
			}
			this.effectData.duration = 2;
		}
	},
	ragemiss: {
		onModifyMove: function (move) {
			if (move.id === 'rage') move.accuracy = 1 / 255 * 100;
		}
	},
	diginvulnerable: {
		onAccuracy: function (accuracy, target, source, move) {
			if (move.id === 'swift') return true;
			this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
			return null;
		},
		onDamage: function (damage, target, source, move) {
			if (!move || move.effectType !== 'Move') return;
			if (!source) return;
			if (move.id === 'earthquake') {
				this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
				return null;
			}
		}
	},
	flyinvulnerable: {
		onAccuracy: function (accuracy, target, source, move) {
			if (move.id === 'swift') return true;
			this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
			return null;
		},
		onDamage: function (damage, target, source, move) {
			if (!move || move.effectType !== 'Move') return;
			if (!source || source.side === target.side) return;
			if (move.id === 'gust' || move.id === 'thunder') {
				this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
				return null;
			}
		}
	}
};
<commit_msg>Gen 1: Add Confusion self-hit bug with two Substitutes If the confused Pokemon has a Substitute up, damage will be dealt to the opponent's Substitute instead. If the opponent doesn't have a Substitute up, no damage will be dealt.<commit_after>/**
 * Statuses worked way different.
 * Sleep lasted longer, had no reset on switch and took a whole turn to wake up.
 * Frozen only thaws when hit by fire or Haze.
 *
 * Secondary effects to status (-speed, -atk) worked differently, so they are
 * separated as volatile statuses that are applied on switch in, removed
 * under certain conditions and re-applied under other conditions.
 */
exports.BattleStatuses = {
	brn: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'brn');
		},
		onAfterMoveSelf: function (pokemon) {
			this.damage(pokemon.maxhp / 16);
		},
		onSwitchIn: function (pokemon) {
			if (pokemon.side.foe.active[0] && pokemon.speed <= pokemon.side.foe.active[0].speed) {
				this.damage(pokemon.maxhp / 16);
			}
		}
	},
	par: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'par');
			target.addVolatile('parspeeddrop');
		},
		onBeforeMovePriority: 2,
		onBeforeMove: function (pokemon) {
			if (this.random(256) < 64) {
				this.add('cant', pokemon, 'par');
				pokemon.removeVolatile('bide');
				pokemon.removeVolatile('lockedmovee');
				pokemon.removeVolatile('twoturnmove');
				pokemon.removeVolatile('fly');
				pokemon.removeVolatile('dig');
				pokemon.removeVolatile('solarbeam');
				pokemon.removeVolatile('skullbash');
				pokemon.removeVolatile('partialtrappinglock');
				return false;
			}
		},
		onSwitchIn: function (pokemon) {
			pokemon.addVolatile('parspeeddrop');
		}
	},
	parspeeddrop: {
		onModifySpe: function (spe) {
			return this.clampIntRange(Math.floor(spe / 4), 1);
		}
	},
	slp: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'slp');
			// 1-7 turns. Put 1-7 since they awake at end of turn.
			this.effectData.startTime = this.random(1, 7);
			this.effectData.time = this.effectData.startTime;
		},
		onBeforeMovePriority: 2,
		onBeforeMove: function (pokemon, target, move) {
			pokemon.statusData.time--;
			this.add('cant', pokemon, 'slp');
			pokemon.lastMove = '';
			return false;
		},
		onAfterMoveSelf: function (pokemon) {
			if (pokemon.statusData.time <= 0) pokemon.cureStatus();
		}
	},
	frz: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'frz');
		},
		onBeforeMovePriority: 2,
		onBeforeMove: function (pokemon, target, move) {
			this.add('cant', pokemon, 'frz');
			pokemon.lastMove = '';
			return false;
		},
		onHit: function (target, source, move) {
			if (move.type === 'Fire' && move.category !== 'Status') {
				target.cureStatus();
			}
		}
	},
	psn: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'psn');
		},
		onAfterMoveSelf: function (pokemon) {
			this.damage(pokemon.maxhp / 16);
		},
		onSwitchIn: function (pokemon) {
			if (pokemon.side.foe.active[0] && pokemon.speed <= pokemon.side.foe.active[0].speed) {
				this.damage(pokemon.maxhp / 16);
			}
		}
	},
	tox: {
		effectType: 'Status',
		onStart: function (target) {
			this.add('-status', target, 'tox');
			this.effectData.stage = 0;
		},
		onAfterMoveSelf: function (pokemon) {
			if (this.effectData.stage < 15) {
				this.effectData.stage++;
			}
			this.damage(this.clampIntRange(pokemon.maxhp / 16, 1) * this.effectData.stage);
		},
		onSwitchIn: function (pokemon) {
			this.effectData.stage = 0; // probably unnecessary...
			pokemon.setStatus('psn');
			// normal poison damage...
			if (pokemon.side.foe.active[0] && pokemon.speed <= pokemon.side.foe.active[0].speed) {
				this.damage(pokemon.maxhp / 16);
			}
		}
	},
	confusion: {
		// this is a volatile status
		onStart: function (target) {
			var result = this.runEvent('TryConfusion');
			if (!result) return result;
			this.add('-start', target, 'confusion');
			this.effectData.time = this.random(2, 6);
		},
		onEnd: function (target) {
			this.add('-end', target, 'confusion');
		},
		onBeforeMove: function (pokemon, target) {
			pokemon.volatiles.confusion.time--;
			if (!pokemon.volatiles.confusion.time) {
				pokemon.removeVolatile('confusion');
				return;
			}
			this.add('-activate', pokemon, 'confusion');
			if (this.random(256) >= 128) {
				// We check here to implement the substitute bug since otherwise we need to change directDamage to take target.
				if (pokemon.volatiles['substitute']) {
					// If there is Substitute, we check for opposing substitute.
					if (target.volatiles['substitute']) {
						// Damage that one instead.
						this.directDamage(this.getDamage(pokemon, pokemon, 40), target);
					}
				} else {
					// No substitute, direct damage to itself.
					this.directDamage(this.getDamage(pokemon, pokemon, 40));
				}
				pokemon.removeVolatile('bide');
				pokemon.removeVolatile('lockedmovee');
				pokemon.removeVolatile('twoturnmove');
				pokemon.removeVolatile('fly');
				pokemon.removeVolatile('dig');
				pokemon.removeVolatile('solarbeam');
				pokemon.removeVolatile('skullbash');
				pokemon.removeVolatile('partialtrappinglock');
				return false;
			}
			return;
		}
	},
	flinch: {
		duration: 1,
		onBeforeMovePriority: 1,
		onBeforeMove: function (pokemon) {
			if (!this.runEvent('Flinch', pokemon)) {
				return;
			}
			this.add('cant', pokemon, 'flinch');
			return false;
		}
	},
	trapped: {
		noCopy: true,
		onModifyPokemon: function (pokemon) {
			if (!this.effectData.source || !this.effectData.source.isActive) {
				delete pokemon.volatiles['trapped'];
				return;
			}
			pokemon.trapped = true;
		}
	},
	partiallytrapped: {
		duration: 2,
		onBeforeMovePriority: 1,
		onStart: function (target, source, effect) {
			this.add('-activate', target, 'move: ' + effect, '[of] ' + source);
		},
		onBeforeMove: function (pokemon) {
			this.add('cant', pokemon, 'partiallytrapped');
			return false;
		},
		onEnd: function (pokemon) {
			this.add('-end', pokemon, this.effectData.sourceEffect, '[partiallytrapped]');
		}
	},
	partialtrappinglock: {
		durationCallback: function () {
			var roll = this.random(6);
			var duration = [2, 2, 3, 3, 4, 5][roll];
			return duration;
		},
		onResidual: function (target) {
			if (target.lastMove === 'struggle' || target.status === 'slp') {
				delete target.volatiles['partialtrappinglock'];
			}
		},
		onStart: function (target, source, effect) {
			this.effectData.move = effect.id;
		},
		onModifyPokemon: function (pokemon) {
			if (!pokemon.hasMove(this.effectData.move)) {
				return;
			}
			var moves = pokemon.moveset;
			for (var i = 0; i < moves.length; i++) {
				if (moves[i].id !== this.effectData.move) {
					moves[i].disabled = true;
				}
			}
		}
	},
	lockedmove: {
		// Outrage, Thrash, Petal Dance...
		durationCallback: function () {
			return this.random(2, 4);
		},
		onResidual: function (target) {
			if (target.lastMove === 'struggle' || target.status === 'slp') {
				// don't lock, and bypass confusion for calming
				delete target.volatiles['lockedmove'];
			}
		},
		onStart: function (target, source, effect) {
			this.effectData.move = effect.id;
		},
		onEnd: function (target) {
			this.add('-end', target, 'rampage');
			target.addVolatile('confusion');
		},
		onLockMove: function (pokemon) {
			return this.effectData.move;
		},
		onBeforeTurn: function (pokemon) {
			var move = this.getMove(this.effectData.move);
			if (move.id) {
				this.debug('Forcing into ' + move.id);
				this.changeDecision(pokemon, {move: move.id});
			}
		}
	},
	mustrecharge: {
		duration: 2,
		onBeforeMove: function (pokemon) {
			this.add('cant', pokemon, 'recharge');
			pokemon.removeVolatile('mustrecharge');
			return false;
		},
		onLockMove: 'recharge'
	},
	futuremove: {
		// this is a side condition
		onStart: function (side) {
			this.effectData.positions = [];
			for (var i = 0; i < side.active.length; i++) {
				this.effectData.positions[i] = null;
			}
		},
		onResidualOrder: 3,
		onResidual: function (side) {
			var finished = true;
			for (var i = 0; i < side.active.length; i++) {
				var posData = this.effectData.positions[i];
				if (!posData) continue;

				posData.duration--;

				if (posData.duration > 0) {
					finished = false;
					continue;
				}

				// time's up; time to hit! :D
				var target = side.foe.active[posData.targetPosition];
				var move = this.getMove(posData.move);
				if (target.fainted) {
					this.add('-hint', '' + move.name + ' did not hit because the target is fainted.');
					this.effectData.positions[i] = null;
					continue;
				}

				this.add('-fieldactivate', move);
				target.removeVolatile('Protect');
				target.removeVolatile('Endure');

				if (typeof posData.moveData.affectedByImmunities === 'undefined') {
					posData.moveData.affectedByImmunities = true;
				}

				this.moveHit(target, posData.source, move, posData.moveData);

				this.effectData.positions[i] = null;
			}
			if (finished) {
				side.removeSideCondition('futuremove');
			}
		}
	},
	stall: {
		// Protect, Detect, Endure counter
		duration: 2,
		counterMax: 256,
		onStart: function () {
			this.effectData.counter = 2;
		},
		onStallMove: function () {
			// this.effectData.counter should never be undefined here.
			// However, just in case, use 1 if it is undefined.
			var counter = this.effectData.counter || 1;
			if (counter >= 256) {
				// 2^32 - special-cased because Battle.random(n) can't handle n > 2^16 - 1
				return (this.random() * 4294967296 < 1);
			}
			this.debug("Success chance: " + Math.round(100 / counter) + "%");
			return (this.random(counter) === 0);
		},
		onRestart: function () {
			if (this.effectData.counter < this.effect.counterMax) {
				this.effectData.counter *= 2;
			}
			this.effectData.duration = 2;
		}
	},
	ragemiss: {
		onModifyMove: function (move) {
			if (move.id === 'rage') move.accuracy = 1 / 255 * 100;
		}
	},
	diginvulnerable: {
		onAccuracy: function (accuracy, target, source, move) {
			if (move.id === 'swift') return true;
			this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
			return null;
		},
		onDamage: function (damage, target, source, move) {
			if (!move || move.effectType !== 'Move') return;
			if (!source) return;
			if (move.id === 'earthquake') {
				this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
				return null;
			}
		}
	},
	flyinvulnerable: {
		onAccuracy: function (accuracy, target, source, move) {
			if (move.id === 'swift') return true;
			this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
			return null;
		},
		onDamage: function (damage, target, source, move) {
			if (!move || move.effectType !== 'Move') return;
			if (!source || source.side === target.side) return;
			if (move.id === 'gust' || move.id === 'thunder') {
				this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
				return null;
			}
		}
	}
};
<|endoftext|>


<commit_before>exports.BattleMovedex = {
	bind: {
		inherit: true,
		onBeforeMove: function () {}
	},
	clamp: {
		inherit: true,
		onBeforeMove: function () {}
	},
	firespin: {
		inherit: true,
		onBeforeMove: function () {}
	},
	highjumpkick: {
		inherit: true,
		desc: "If this attack misses the target, the user takes 1 HP of damage.",
		shortDesc: "User takes 1 HP damage it would have dealt if miss.",
		onMoveFail: function (target, source, move) {
			if (target.type !== 'ghost') {
				this.directDamage(1, source);
			}
		}
	},
	jumpkick: {
		inherit: true,
		desc: "If this attack misses the target, the user 1HP of damage.",
		shortDesc: "User takes 1 HP damage if miss.",
		onMoveFail: function (target, source, move) {
			this.damage(1, source);
		}
	},
	leechseed: {
		inherit: true,
		onHit: function () {},
		effect: {
			onStart: function (target) {
				this.add('-start', target, 'move: Leech Seed');
			},
			onAfterMoveSelfPriority: 1,
			onAfterMoveSelf: function (pokemon) {
				var leecher = pokemon.side.foe.active[pokemon.volatiles['leechseed'].sourcePosition];
				if (!leecher || leecher.fainted || leecher.hp <= 0) {
					this.debug('Nothing to leech into');
					return;
				}
				var toLeech = this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1);
				var damage = this.damage(toLeech, pokemon, leecher);
				if (damage) this.heal(damage, leecher, pokemon);
			}
		}
	},
	rage: {
		inherit: true,
		self: {
			volatileStatus: 'rage'
		},
		effect: {
			// Rage lock
			duration: 255,
			onStart: function (target, source, effect) {
				this.effectData.move = 'rage';
			},
			onLockMove: 'rage',
			onTryHit: function (target, source, move) {
				if (target.boosts.atk < 6 && move.id === 'disable') {
					this.boost({atk:1});
				}
			},
			onHit: function (target, source, move) {
				if (target.boosts.atk < 6 && move.category !== 'Status') {
					this.boost({atk:1});
				}
			},
			onMoveFail: function () {}
		}
	},
	recover: {
		inherit: true,
		heal: null,
		onHit: function (target) {
			if (target.hp === target.maxhp) {
				return false;
			}
			this.heal(Math.floor(target.maxhp / 2), target, target);
		}
	},
	rest: {
		inherit: true,
		onHit: function (target) {
			// Fails if the difference between
			// max HP and current HP is 0, 255, or 511
			if (target.hp >= target.maxhp) return false;
			if (!target.setStatus('slp')) return false;
			target.statusData.time = 2;
			target.statusData.startTime = 2;
			this.heal(target.maxhp); // Aeshetic only as the healing happens after you fall asleep in-game
			this.add('-status', target, 'slp', '[from] move: Rest');
		}
	},
	softboiled: {
		inherit: true,
		heal: null,
		onHit: function (target) {
			// Fail when health is 255 or 511 less than max
			if (target.hp === target.maxhp) {
				return false;
			}
			this.heal(Math.floor(target.maxhp / 2), target, target);
		}
	},
	substitute: {
		inherit: true,
		effect: {
			onStart: function (target) {
				this.add('-start', target, 'Substitute');
				this.effectData.hp = Math.floor(target.maxhp / 4);
				delete target.volatiles['partiallytrapped'];
			},
			onTryHitPriority: -1,
			onTryHit: function (target, source, move) {
				if (target === source) {
					this.debug('sub bypass: self hit');
					return;
				}
				if (move.drain) {
					this.add('-miss', source);
					return null;
				}
				if (move.category === 'Status') {
					var SubBlocked = {
						leechseed:1, lockon:1, mindreader:1, nightmare:1
					};
					if (move.status || move.boosts || move.volatileStatus === 'confusion' || SubBlocked[move.id]) {
						this.add('-activate', target, 'Substitute', '[block] ' + move.name);
						return null;
					}
					return;
				}
				if (move.volatileStatus && target === source) return;
				var damage = this.getDamage(source, target, move);
				if (!damage) return null;
				damage = this.runEvent('SubDamage', target, source, move, damage);
				if (!damage) return damage;
				target.volatiles['substitute'].hp -= damage;
				source.lastDamage = damage;
				if (target.volatiles['substitute'].hp <= 0) {
					this.debug('Substitute broke');
					target.removeVolatile('substitute');
					target.subFainted = true;
				} else {
					this.add('-activate', target, 'Substitute', '[damage]');
				}
				if (move.recoil) {
					this.damage(Math.round(damage * move.recoil[0] / move.recoil[1]), source, target, 'recoil');
				}
				if (move.drain) {
					this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
				}
				this.runEvent('AfterSubDamage', target, source, move, damage);
				// Add here counter damage
				if (!target.lastAttackedBy) target.lastAttackedBy = {pokemon: source, thisTurn: true};
				target.lastAttackedBy.move = move.id;
				target.lastAttackedBy.damage = damage;
				return 0;
			},
			onEnd: function (target) {
				this.add('-end', target, 'Substitute');
			}
		},
		secondary: false,
		target: "self",
		type: "Normal"
	},
	wrap: {
		inherit: true,
		onBeforeMove: function () {}
	}
};
<commit_msg>Acid, Aurora Beam, Bubblebeam Effect chances<commit_after>exports.BattleMovedex = {
	"acid": {
		inherit: true,
		secondary: {
			chance: 20,
			boosts: {
				def: -1
			}
		}
	},
	aurorabeam: {
		inherit: true,
		secondary: {
			chance: 30,
			boosts: {
				atk: -1
			}
		}
	},
	bind: {
		inherit: true,
		onBeforeMove: function () {}
	},
	bubblebeam: {
		inherit: true,
		secondary: {
			chance: 30,
			boosts: {
				spe: -1
			}
		}
	},
	clamp: {
		inherit: true,
		onBeforeMove: function () {}
	},
	firespin: {
		inherit: true,
		onBeforeMove: function () {}
	},
	highjumpkick: {
		inherit: true,
		desc: "If this attack misses the target, the user takes 1 HP of damage.",
		shortDesc: "User takes 1 HP damage it would have dealt if miss.",
		onMoveFail: function (target, source, move) {
			if (target.type !== 'ghost') {
				this.directDamage(1, source);
			}
		}
	},
	jumpkick: {
		inherit: true,
		desc: "If this attack misses the target, the user 1HP of damage.",
		shortDesc: "User takes 1 HP damage if miss.",
		onMoveFail: function (target, source, move) {
			this.damage(1, source);
		}
	},
	leechseed: {
		inherit: true,
		onHit: function () {},
		effect: {
			onStart: function (target) {
				this.add('-start', target, 'move: Leech Seed');
			},
			onAfterMoveSelfPriority: 1,
			onAfterMoveSelf: function (pokemon) {
				var leecher = pokemon.side.foe.active[pokemon.volatiles['leechseed'].sourcePosition];
				if (!leecher || leecher.fainted || leecher.hp <= 0) {
					this.debug('Nothing to leech into');
					return;
				}
				var toLeech = this.clampIntRange(Math.floor(pokemon.maxhp / 16), 1);
				var damage = this.damage(toLeech, pokemon, leecher);
				if (damage) this.heal(damage, leecher, pokemon);
			}
		}
	},
	rage: {
		inherit: true,
		self: {
			volatileStatus: 'rage'
		},
		effect: {
			// Rage lock
			duration: 255,
			onStart: function (target, source, effect) {
				this.effectData.move = 'rage';
			},
			onLockMove: 'rage',
			onTryHit: function (target, source, move) {
				if (target.boosts.atk < 6 && move.id === 'disable') {
					this.boost({atk:1});
				}
			},
			onHit: function (target, source, move) {
				if (target.boosts.atk < 6 && move.category !== 'Status') {
					this.boost({atk:1});
				}
			},
			onMoveFail: function () {}
		}
	},
	recover: {
		inherit: true,
		heal: null,
		onHit: function (target) {
			if (target.hp === target.maxhp) {
				return false;
			}
			this.heal(Math.floor(target.maxhp / 2), target, target);
		}
	},
	rest: {
		inherit: true,
		onHit: function (target) {
			// Fails if the difference between
			// max HP and current HP is 0, 255, or 511
			if (target.hp >= target.maxhp) return false;
			if (!target.setStatus('slp')) return false;
			target.statusData.time = 2;
			target.statusData.startTime = 2;
			this.heal(target.maxhp); // Aeshetic only as the healing happens after you fall asleep in-game
			this.add('-status', target, 'slp', '[from] move: Rest');
		}
	},
	softboiled: {
		inherit: true,
		heal: null,
		onHit: function (target) {
			// Fail when health is 255 or 511 less than max
			if (target.hp === target.maxhp) {
				return false;
			}
			this.heal(Math.floor(target.maxhp / 2), target, target);
		}
	},
	substitute: {
		inherit: true,
		effect: {
			onStart: function (target) {
				this.add('-start', target, 'Substitute');
				this.effectData.hp = Math.floor(target.maxhp / 4);
				delete target.volatiles['partiallytrapped'];
			},
			onTryHitPriority: -1,
			onTryHit: function (target, source, move) {
				if (target === source) {
					this.debug('sub bypass: self hit');
					return;
				}
				if (move.drain) {
					this.add('-miss', source);
					return null;
				}
				if (move.category === 'Status') {
					var SubBlocked = {
						leechseed:1, lockon:1, mindreader:1, nightmare:1
					};
					if (move.status || move.boosts || move.volatileStatus === 'confusion' || SubBlocked[move.id]) {
						this.add('-activate', target, 'Substitute', '[block] ' + move.name);
						return null;
					}
					return;
				}
				if (move.volatileStatus && target === source) return;
				var damage = this.getDamage(source, target, move);
				if (!damage) return null;
				damage = this.runEvent('SubDamage', target, source, move, damage);
				if (!damage) return damage;
				target.volatiles['substitute'].hp -= damage;
				source.lastDamage = damage;
				if (target.volatiles['substitute'].hp <= 0) {
					this.debug('Substitute broke');
					target.removeVolatile('substitute');
					target.subFainted = true;
				} else {
					this.add('-activate', target, 'Substitute', '[damage]');
				}
				if (move.recoil) {
					this.damage(Math.round(damage * move.recoil[0] / move.recoil[1]), source, target, 'recoil');
				}
				if (move.drain) {
					this.heal(Math.ceil(damage * move.drain[0] / move.drain[1]), source, target, 'drain');
				}
				this.runEvent('AfterSubDamage', target, source, move, damage);
				// Add here counter damage
				if (!target.lastAttackedBy) target.lastAttackedBy = {pokemon: source, thisTurn: true};
				target.lastAttackedBy.move = move.id;
				target.lastAttackedBy.damage = damage;
				return 0;
			},
			onEnd: function (target) {
				this.add('-end', target, 'Substitute');
			}
		},
		secondary: false,
		target: "self",
		type: "Normal"
	},
	wrap: {
		inherit: true,
		onBeforeMove: function () {}
	}
};
<|endoftext|>


<commit_before>exports.BattleMovedex = {
	"distortion": {
		num: 701,
		accuracy: 100,
		basePower: 80,
		category: "Special",
		desc: "Blah Blah Blah",
		shortDesc: "Blah Blah Blah",
		id: "distortion",
		isViable: true,
		name: "Distortion",
		pp: 10,
		priority: -1,
		beforeTurnCallback: function(pokemon, target) {
			decision = this.willMove(target);
			if (decision.move.priority !== 0) {
				this.willMove(pokemon).move.priority = decision.move.priority + 1;
			}
		},
		secondary: false,
		target: "normal",
		type: "Psychic"
	},
	"divingtalon": {
		num: 702,
		accuracy: 100,
		basePower: 40,
		category: "Physical",
		desc: "Deals damage to one adjacent target. Makes contact. Priority +1.",
		shortDesc: "Usually goes first.",
		id: "divingtalon",
		isViable: true,
		name: "Diving Talon",
		pp: 20,
		priority: 1,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Flying"
	},
	"firerocket": {
		num: 703,
		accuracy: 100,
		basePower: 40,
		category: "Physical",
		desc: "Deals damage to one adjacent target. Makes contact. Priority +1.",
		shortDesc: "Usually goes first.",
		id: "firerocket",
		isViable: true,
		name: "Fire Rocket",
		pp: 20,
		priority: 1,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Fire"
	},
	"focusball": {
		num: 704,
		accuracy: 100,
		basePower: 80,
		category: "Special",
		desc: "Deals damage to one adjacent target with a 20% chance to lower its Special Defense by 1 stage.",
		shortDesc: "20% chance to lower the target's Sp. Def by 1.",
		id: "focusball",
		isViable: true,
		name: "Focus Ball",
		pp: 15,
		priority: 0,
		secondary: {
			chance: 20,
			boosts: {
				spd: -1
			}
		},
		target: "normal",
		type: "Fighting"
	},
	"mindforce": {
		num: 705,
		accuracy: 100,
		basePower: 80,
		category: "Special",
		desc: "Deals damage to one adjacent target. Fails if the target did not select a status move for use this turn, or if the target moves before the user. Priority +1.",
		shortDesc: "Usually goes first. Fails if target is not using a status move.",
		id: "mindforce",
		isViable: true,
		name: "Mind Force",
		pp: 5,
		priority: 1,
		onTryHit: function(target) {
			decision = this.willMove(target);
			if (!decision || decision.choice !== 'move' || (decision.move.category !== 'Status' && decision.move.id !== 'mefirst')) {
				return false;
			}
		},
		secondary: false,
		target: "normal",
		type: "Psychic"
	},
	"rabidfrenzy": {
		num: 706,
		accuracy: 100,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target and lowers the user's Defense and Special Defense by 1 stage. Makes contact.",
		shortDesc: "Lowers the user's Defense and Sp. Def by 1.",
		id: "rabidfrenzy",
		isViable: true,
		name: "Rabid Frenzy",
		pp: 5,
		priority: 0,
		isContact: true,
		self: {
			boosts: {
				def: -1,
				spd: -1
			}
		},
		secondary: false,
		target: "normal",
		type: "Dark"
	},
	"rootbomb": {
		num: 707,
		accuracy: 100,
		basePower: 30,
		category: "Physical",
		desc: "Deals damage to all adjacent foes and changes type to Grass before hitting.",
		shortDesc: "Changes foes type to Grass before hitting.",
		id: "rootbomb",
		name: "Root Bomb",
		pp: 15,
		priority: 0,
		onHit: function(target) {
			this.add('-start', target, 'typechange', 'Grass');
			target.types = ['Grass'];
		},
		secondary: false,
		target: "normal",
		type: "Fire"
	},
	"shadowsword": {
		num: 708,
		accuracy: 100,
		basePower: 85,
		category: "Physical",
		desc: "Deals damage to one adjacent target with a 20% chance to lower its Defense by 1 stage. Makes contact.",
		shortDesc: "20% chance to lower the target's Defense by 1",
		id: "shadowsword",
		name: "Shadow Sword",
		pp: 20,
		priority: 0,
		isContact: true,
		secondary: {
			chance: 20,
			boosts: {
				def: -1
			}
		},
		target: "ghost",
		type: "Ghost"
	},
	"shortcircuit": {
		num: 709,
		accuracy: 100,
		basePower: 40,
		category: "Special",
		desc: "Deals damage to one adjacent target. Makes contact. Priority +1.",
		shortDesc: "Usually goes first.",
		id: "shortcircuit",
		isViable: true,
		name: "Short Circuit",
		pp: 20,
		priority: 1,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Electric"
	},
        "vendetta": {
                num: 710,
                accuracy: 85,
                basePower: 120,
                category: "Special",
                desc: "Deals damage to one adjacent target with a 20 chance to hit it with Spite.",
                shortDesc: "20% chance to spite the target.",
                id: "vendetta",
                isViable: true,
                name: "Vendetta",
                pp: 5,
                priority: 0,
                onHit: function(target) {
                        var spiteroll = this.random(100);
                        if (spiteroll > 80) {
                                if (target.deductPP(target.lastMove, 4)) {
                                        this.add("-activate", target, 'move: vendetta', target.lastMove, 4);
                                        return;
                                }
                        }
                        return false;
                },
                target: "normal",
                type: "Ghost"
        },
	"voidhorn": {
		num: 711,
		accuracy: 85,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target",
		shortDesc: "Strong Ghost-Type Move",
		id: "voidhorn",
		isViable: true,
		name: "Void Horn",
		pp: 10,
		priority: 0,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Ghost"
	},
};
<commit_msg>Root Bomb Fix<commit_after>exports.BattleMovedex = {
	"distortion": {
		num: 701,
		accuracy: 100,
		basePower: 80,
		category: "Special",
		desc: "Blah Blah Blah",
		shortDesc: "Blah Blah Blah",
		id: "distortion",
		isViable: true,
		name: "Distortion",
		pp: 10,
		priority: -1,
		beforeTurnCallback: function(pokemon, target) {
			decision = this.willMove(target);
			if (decision.move.priority !== 0) {
				this.willMove(pokemon).move.priority = decision.move.priority + 1;
			}
		},
		secondary: false,
		target: "normal",
		type: "Psychic"
	},
	"divingtalon": {
		num: 702,
		accuracy: 100,
		basePower: 40,
		category: "Physical",
		desc: "Deals damage to one adjacent target. Makes contact. Priority +1.",
		shortDesc: "Usually goes first.",
		id: "divingtalon",
		isViable: true,
		name: "Diving Talon",
		pp: 20,
		priority: 1,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Flying"
	},
	"firerocket": {
		num: 703,
		accuracy: 100,
		basePower: 40,
		category: "Physical",
		desc: "Deals damage to one adjacent target. Makes contact. Priority +1.",
		shortDesc: "Usually goes first.",
		id: "firerocket",
		isViable: true,
		name: "Fire Rocket",
		pp: 20,
		priority: 1,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Fire"
	},
	"focusball": {
		num: 704,
		accuracy: 100,
		basePower: 80,
		category: "Special",
		desc: "Deals damage to one adjacent target with a 20% chance to lower its Special Defense by 1 stage.",
		shortDesc: "20% chance to lower the target's Sp. Def by 1.",
		id: "focusball",
		isViable: true,
		name: "Focus Ball",
		pp: 15,
		priority: 0,
		secondary: {
			chance: 20,
			boosts: {
				spd: -1
			}
		},
		target: "normal",
		type: "Fighting"
	},
	"mindforce": {
		num: 705,
		accuracy: 100,
		basePower: 80,
		category: "Special",
		desc: "Deals damage to one adjacent target. Fails if the target did not select a status move for use this turn, or if the target moves before the user. Priority +1.",
		shortDesc: "Usually goes first. Fails if target is not using a status move.",
		id: "mindforce",
		isViable: true,
		name: "Mind Force",
		pp: 5,
		priority: 1,
		onTryHit: function(target) {
			decision = this.willMove(target);
			if (!decision || decision.choice !== 'move' || (decision.move.category !== 'Status' && decision.move.id !== 'mefirst')) {
				return false;
			}
		},
		secondary: false,
		target: "normal",
		type: "Psychic"
	},
	"rabidfrenzy": {
		num: 706,
		accuracy: 100,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target and lowers the user's Defense and Special Defense by 1 stage. Makes contact.",
		shortDesc: "Lowers the user's Defense and Sp. Def by 1.",
		id: "rabidfrenzy",
		isViable: true,
		name: "Rabid Frenzy",
		pp: 5,
		priority: 0,
		isContact: true,
		self: {
			boosts: {
				def: -1,
				spd: -1
			}
		},
		secondary: false,
		target: "normal",
		type: "Dark"
	},
	"rootbomb": {
		num: 707,
		accuracy: 100,
		basePower: 30,
		category: "Physical",
		desc: "Deals damage to all adjacent foes and changes type to Grass before hitting.",
		shortDesc: "Changes foes type to Grass before hitting.",
		id: "rootbomb",
		name: "Root Bomb",
		pp: 15,
		priority: 0,
		effect: onBeforeMove: function(target) {
			this.add('-start', target, 'typechange', 'Grass');
			target.types = ['Grass'];
		},
		secondary: false,
		target: "normal",
		type: "Fire"
	},
	"shadowsword": {
		num: 708,
		accuracy: 100,
		basePower: 85,
		category: "Physical",
		desc: "Deals damage to one adjacent target with a 20% chance to lower its Defense by 1 stage. Makes contact.",
		shortDesc: "20% chance to lower the target's Defense by 1",
		id: "shadowsword",
		name: "Shadow Sword",
		pp: 20,
		priority: 0,
		isContact: true,
		secondary: {
			chance: 20,
			boosts: {
				def: -1
			}
		},
		target: "ghost",
		type: "Ghost"
	},
	"shortcircuit": {
		num: 709,
		accuracy: 100,
		basePower: 40,
		category: "Special",
		desc: "Deals damage to one adjacent target. Makes contact. Priority +1.",
		shortDesc: "Usually goes first.",
		id: "shortcircuit",
		isViable: true,
		name: "Short Circuit",
		pp: 20,
		priority: 1,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Electric"
	},
        "vendetta": {
                num: 710,
                accuracy: 85,
                basePower: 120,
                category: "Special",
                desc: "Deals damage to one adjacent target with a 20 chance to hit it with Spite.",
                shortDesc: "20% chance to spite the target.",
                id: "vendetta",
                isViable: true,
                name: "Vendetta",
                pp: 5,
                priority: 0,
                onHit: function(target) {
                        var spiteroll = this.random(100);
                        if (spiteroll > 80) {
                                if (target.deductPP(target.lastMove, 4)) {
                                        this.add("-activate", target, 'move: vendetta', target.lastMove, 4);
                                        return;
                                }
                        }
                        return false;
                },
                target: "normal",
                type: "Ghost"
        },
	"voidhorn": {
		num: 711,
		accuracy: 85,
		basePower: 120,
		category: "Physical",
		desc: "Deals damage to one adjacent target",
		shortDesc: "Strong Ghost-Type Move",
		id: "voidhorn",
		isViable: true,
		name: "Void Horn",
		pp: 10,
		priority: 0,
		isContact: true,
		secondary: false,
		target: "normal",
		type: "Ghost"
	},
};
<|endoftext|>


<commit_before>'use strict';

const Code = require('code');
const Lab = require('lab');
const rewire = require('rewire');
const sinon = require('sinon');
const EventEmitter = require('events').EventEmitter;
var MongooseConnector = rewire('../lib/MongooseConnector');
// This is required to trick lab coverage
var MongooseConnector2 = require('../lib/MongooseConnector'); //eslint-disable-line
MongooseConnector2 = MongooseConnector;

const lab = exports.lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const beforeEach = lab.beforeEach;
const expect = Code.expect;

describe('Default Options', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseConnector instanceof EventEmitter).to.equal(true);

    done();
  });

  it('s connection property is an instance of EventEmitter', done => {
    expect(mongooseConnector.connection instanceof EventEmitter).to.equal(true);

    done();
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('info');
      expect(args[1]).to.equal('Connected');

      done();
    }, 2);
  });
});

describe('With bluebird (bluebird)', () => {
  let mongooseConnector, mongooseEmitter, pluginStub; //eslint-disable-line
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'bluebird'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseEmitter.Promise).to.equal(require('bluebird'));

    done();
  });
});

describe('With es6 (es6)', () => {
  let mongooseConnector, mongooseEmitter, pluginStub; //eslint-disable-line
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'es6'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseEmitter.Promise).to.equal(global.Promise);

    done();
  });
});

describe('Default Options with failed connection', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('error', {message: 'test'});
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    mongooseConnector.on('error', err => err);
    done();
  });

  it('emits an error event from Connector', done => {
    mongooseConnector.on('error', err => {
      expect(err.message).to.equal('test');

      done();
    });
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('error');
      expect(args[1]).to.equal('Unable to connect to database: test');

      done();
    }, 10); // TODO : Avoid using a timeout for events
  });
});

describe('Default Options with closed connection', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('close');
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    mongooseConnector.on('error', err => err);
    done();
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('info');
      expect(args[1]).to.equal('Connection to database closed');

      done();
    }, 10);
  });
});

describe('Default Options with disconnected connection', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('disconnected');
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    mongooseConnector.on('error', err => err);
    done();
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('warn');
      expect(args[1]).to.equal('Connection to database disconnected');

      done();
    }, 10);
  });
});
<commit_msg>Add test for ES6 (native) promises<commit_after>'use strict';

const Code = require('code');
const Lab = require('lab');
const rewire = require('rewire');
const sinon = require('sinon');
const EventEmitter = require('events').EventEmitter;
var MongooseConnector = rewire('../lib/MongooseConnector');
// This is required to trick lab coverage
var MongooseConnector2 = require('../lib/MongooseConnector'); //eslint-disable-line
MongooseConnector2 = MongooseConnector;

const lab = exports.lab = Lab.script();
const describe = lab.describe;
const it = lab.it;
const beforeEach = lab.beforeEach;
const expect = Code.expect;

describe('Default Options', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseConnector instanceof EventEmitter).to.equal(true);

    done();
  });

  it('s connection property is an instance of EventEmitter', done => {
    expect(mongooseConnector.connection instanceof EventEmitter).to.equal(true);

    done();
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('info');
      expect(args[1]).to.equal('Connected');

      done();
    }, 2);
  });
});

describe('With bluebird (bluebird)', () => {
  let mongooseConnector, mongooseEmitter, pluginStub; //eslint-disable-line
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'bluebird'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseEmitter.Promise).to.equal(require('bluebird'));

    done();
  });
});

describe('With es6 (es6)', () => {
  let mongooseConnector, mongooseEmitter, pluginStub; //eslint-disable-line
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'es6'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseEmitter.Promise).to.equal(global.Promise);

    done();
  });
});

describe('With es6 (native)', () => {
  let mongooseConnector, mongooseEmitter, pluginStub; //eslint-disable-line
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('connected');
    pluginStub = {
      log: log => log
    };
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'native'}, pluginStub);
    done();
  });

  it('is an instance of EventEmitter', done => {
    expect(mongooseEmitter.Promise).to.equal(global.Promise);

    done();
  });
});

describe('Default Options with failed connection', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('error', {message: 'test'});
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    mongooseConnector.on('error', err => err);
    done();
  });

  it('emits an error event from Connector', done => {
    mongooseConnector.on('error', err => {
      expect(err.message).to.equal('test');

      done();
    });
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('error');
      expect(args[1]).to.equal('Unable to connect to database: test');

      done();
    }, 10); // TODO : Avoid using a timeout for events
  });
});

describe('Default Options with closed connection', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('close');
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    mongooseConnector.on('error', err => err);
    done();
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('info');
      expect(args[1]).to.equal('Connection to database closed');

      done();
    }, 10);
  });
});

describe('Default Options with disconnected connection', () => {
  let mongooseConnector, mongooseEmitter, logSpy, pluginStub;
  let MongooseEmitter = require('./artifacts/MongooseEmitter');

  beforeEach(done => {
    mongooseEmitter = new MongooseEmitter('disconnected');
    pluginStub = {
      log: log => log
    };
    logSpy = sinon.spy(pluginStub, 'log');
    MongooseConnector.__set__('mongoose', mongooseEmitter);
    mongooseConnector = new MongooseConnector({promises: 'mpromise'}, pluginStub);
    mongooseConnector.on('error', err => err);
    done();
  });

  it('logs a connection event', done => {
    setTimeout(() => {
      const args = logSpy.getCall(0).args;

      expect(args[0][0]).to.equal('warn');
      expect(args[1]).to.equal('Connection to database disconnected');

      done();
    }, 10);
  });
});
<|endoftext|>


<commit_before>var mongoose = require("mongoose")

var siteSchema = new mongoose.Schema({
    name:        String,
    location:    String,
    img:         String,
    description: String,
    comments:    [
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: "Comment"
        }
    ],
    author: {
        id: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User"
        },
        username: String
    }
})

module.exports = mongoose.model("Site", siteSchema)<commit_msg>Add new feature to landingsites, "distance"   - Used to track distance from earth   - Calls to this are already included from previous commit, whoops!<commit_after>var mongoose = require("mongoose")

var siteSchema = new mongoose.Schema({
    name:        String,
    location:    String,
    img:         String,
    description: String,
    distance:    String,
    comments:    [
        {
            type: mongoose.Schema.Types.ObjectId,
            ref: "Comment"
        }
    ],
    author: {
        id: {
            type: mongoose.Schema.Types.ObjectId,
            ref: "User"
        },
        username: String
    }
})

module.exports = mongoose.model("Site", siteSchema)<|endoftext|>


<commit_before>var VError, async, mongoose, jsonSelect, nconf, courses, calendar, history, Schema, schema;

VError = require('verror');
async = require('async');
mongoose = require('mongoose');
jsonSelect = require('mongoose-json-select');
courses = require('dacos-courses-driver');
calendar = require('dacos-calendar-driver');
history = require('dacos-history-driver');
nconf = require('nconf');
Schema = mongoose.Schema;

schema = new Schema({
  'enrollment' : {
    'type'     : Schema.ObjectId,
    'ref'      : 'Enrollment',
    'required' : true
  },
  'discipline' : {
    'type'     : String,
    'required' : true
  },
  'offering'   : {
    'type'     : String,
    'required' : true
  },
  'status'     : {
    'type'    : String,
    'enum'    : [ 'new', 'approved', 'rejected', 'quit' ],
    'default' : 'new'
  },
  'comment'    : {
    'type' : String
  },
  'priority'   : {
    'type' : Number
  },
  'createdAt'  : {
    'type'    : Date,
    'default' : Date.now
  },
  'updatedAt'  : {
    'type' : Date
  }
}, {
  'collection' : 'requirements',
  'strict'     : true,
  'toJSON'     : {
    'virtuals' : true
  }
});

schema.index({
  'enrollment' : 1,
  'discipline' : 1,
  'offering'   : 1
}, {
  'unique' : true
});

schema.plugin(jsonSelect, {
  '_id'        : 0,
  'enrollment' : 1,
  'discipline' : 1,
  'offering'   : 1,
  'status'     : 1,
  'comment'    : 1,
  'priority'   : 1,
  'createdAt'  : 1,
  'updatedAt'  : 1
});

schema.pre('save', function setRequirementUpdatedAt(next) {
  'use strict';

  this.updatedAt = new Date();
  next();
});

schema.pre('save', function setPriorityScore(next) {
  'use strict';

  var priorityScore;

  this.populate('enrollment');
  this.populate(function () {
    getDisciplineInformation(this.enrollment, this.discipline, this.offering, function (blockType, isAhead, isReserved) {

      // Calculating priority score
      if (blockType === 'required') {
        // Obligatory discipline
        if (isAhead) {
          if (isReserved) {
            priorityScore = 6;
          } else {
            priorityScore = 3;
          }
        } else {
          if (isReserved) {
            priorityScore = 9;
          } else {
            priorityScore = 7;
          }
        }
      } else if (blockType === 'optional') {
        // Optional discipline
        if (isAhead) {
          if (isReserved) {
            priorityScore = 5;
          } else {
            priorityScore = 2;
          }
        } else {
          if (isReserved) {
            priorityScore = 8;
          } else {
            priorityScore = 4;
          }
        }
      } else if (blockType === 'extra') {
        // Extra-curricular discipline
        if (isAhead) {
          if (isReserved) {
            priorityScore = 1;
          } else {
            priorityScore = 0;
          }
        } else {
          // TODO check why this is equal to the last conditional
          if (isReserved) {
            priorityScore = 1;
          } else {
            priorityScore = 0;
          }
        }
      }

      this.priority = priorityScore;
      //console.log(this.priority);

      next();
    }.bind(this));
  }.bind(this));
});

function getDisciplineInformation(enrollment, discipline, disciplineOffering, next) {
  'use strict';

  var blockType , isAhead, isReserved;

  blockType = 'optional';
  isAhead = false;
  isReserved = false;

  history.currentHistory(enrollment.user, function (error, currentHistory) {

    if (error) {
      error = new VError(error, 'Error when trying to get the history');
      return next(error);
    }

    if (!currentHistory) {
      return next(false);
    }

    courses.blocks(currentHistory.year, currentHistory.course + ' ' + currentHistory.modality, function (error, blocks) {
      if (error) {
        return next('');
      }

      async.some(blocks, function(block) {

        courses.requirement(currentHistory.year, currentHistory.course + ' ' + currentHistory.modality, block.code, this.discipline, function (error, requirement) {
          if (requirement) {
            var currentDate = new Date();

            var currentSemester;
            if (currentDate.getMonth() < 6) {
              currentSemester = 1;
            } else if(currentDate.getMonth() > 6) {
              currentSemester = 2;
            } else {
              currentSemester = currentDate.getDay() < 15 ? 1 : 2;
            }

            //TODO figure out how to get the offering reservation

            var userSemester = (currentDate.getFullYear() - currentHistory.year) * 2 + currentSemester;
            //var offeringReservations = disciplineOffering && disciplineOffering.reservations || [];

            blockType = block.type;
            //isAhead = offeringReservations.some(function (offeringReservation) { return offeringReservation === historyId; });
            isReserved = requirement && (userSemester < requirement.suggestedSemester);

            //requirement.code

            next(true);
          }
          else {
            next(false);
          }
        });
      }, function(result) {
        next(blockType, isAhead, isReserved);
      });
    });
  });
}

/**
 * verifica se uma solicitação de aumento de limite de créditos deve ser aberta
 */
schema.pre('save', function openCreditRaiseRequestIfNecessary(next) {
  'use strict';

  this.populate('enrollment');
  this.populate(function () {
    var user = this.enrollment.user;
    history.currentHistory(user, function (error, currentHistory) {
      if (error) {
        error = new VError(error, 'Error when trying to get the history');
        return next(error);
      }

      if (!currentHistory) {
        return next(false);
      }

      courses.modality(currentHistory.year, currentHistory.course + '-' + currentHistory.modality, function (error, modality) {
        if (error) {
          error = new VError(error, 'Error when trying to get the modality');
          return next(error);
        }

        if (!modality) {
          return next(new VError(error, 'Modality not found'));
        }

        var query;
        query = this.model('Requirement').find();
        query.where('enrollment').equals(this.enrollment._id);
        query.where('_id').ne(this._id);
        query.exec(function foundRequirements(error, requirements) {
          requirements.push(this);

          async.reduce(requirements, 0, function (sum, disciplineRequirement, next) {
            courses.discipline(disciplineRequirement.discipline, function (error, discipline) {
              next(error, discipline ? discipline.credits + sum : 0);
            }.bind(this));
          }.bind(this), function (error, credits) {
            if (error) {
              error = new VError(error, 'Error when trying to sum the credits');
              return next(error);
            }

            if (credits > modality.creditLimit) {
              this.enrollment.creditRaiseRequest.credits = credits;
              this.enrollment.creditRaiseRequest.date = new Date();
              this.enrollment.creditRaiseRequest.status = 'pending';
              this.enrollment.save(next);
            }
            else {
              next();
            }
          }.bind(this));
        }.bind(this));
      }.bind(this));
    }.bind(this));
  }.bind(this));
});

schema.path('offering').validate(function validateIfDisciplineOfferingExists(value, next) {
  'use strict';

  courses.offering(this.discipline, this.offering, function foundDisciplineOffering(error, offering) {
    next(!error && !!offering);
  });
}, 'discipline offering not found');

schema.path('discipline').validate(function validateDisciplineRequirement(value, next) {
  'use strict';

  courses.discipline(this.discipline, function (error, discipline) {
    if (error) {
      error = new VError(error, 'Error when trying to get the discipline');
      return next(error);
    }

    if (!discipline) {
      return next(false);
    }

    this.populate('enrollment');
    this.populate(function () {
      var user = this.enrollment.user;
      history.histories(user, function (error, histories) {
        if (error) {
          error = new VError(error, 'Error when trying to get the histories');
          return next(error);
        }

        if (!histories) {
          return next(false);
        }

        async.every(histories, function (userHistory) {
          if (discipline.requirements.length > 0) {
            discipline.requirements.forEach(function (disciplineRequirement) {
              history.discipline(user, userHistory.year, disciplineRequirement.code, function (error, disciplineHistory) {
                next(!error && !!disciplineHistory &&
                [1, 2, 3, 4, 7, 10, 11, 12, 13, 14, 15, 16, 20].lastIndexOf(disciplineHistory.status) > -1);
              }.bind(this));
            }.bind(this));
          }
          else {
            next();
          }
        }.bind(this), next);
      }.bind(this));
    }.bind(this));
  }.bind(this));
}, 'discipline requirement not fulfilled');

schema.path('status').validate(function validateIfRequirementCanBeQuit(value, next) {
  'use strict';
  var todayDate, year;
  todayDate = new Date();
  year = todayDate.getFullYear();

  if (this.status === 'quit') {
    calendar.betweenEvents(todayDate, year, 'discipline-quit-starts', year, 'discipline-quit-ends', next);
  }
  else {
    next();
  }
}, 'outside of discipline quit period');

schema.path('discipline').validate(function validateDisciplineApproval(value, next) {
  'use strict';

  async.waterfall([function (next) {
    this.populate('enrollment');
    this.populate(next);
  }.bind(this), function (_, next) {
    async.parallel({
      'histories'  : function (next) {
        history.histories(this.enrollment.user, next);
      }.bind(this),
      'discipline' : function (next) {
        courses.discipline(this.discipline, next);
      }.bind(this)
    }, next);
  }.bind(this), function (data, next) {
    async.every(data.histories, function (userHistory, next) {
      history.discipline(this.enrollment.user, userHistory.year, data.discipline.code, function (error, disciplineHistory) {
        next(!!error || !(disciplineHistory && [1, 2, 3, 4, 7, 10, 11, 12, 13, 14, 15, 16, 20].lastIndexOf(disciplineHistory.status) > -1));
      }.bind(this));
    }.bind(this), next);
  }.bind(this)], next);
}, 'user was already approved on discipline');

schema.path('offering').validate(function validateIfRequirementHasTimeConflict(value, next) {
  'use strict';

  async.waterfall([function (next) {
    this.populate('enrollment');
    this.populate(next);
  }.bind(this), function (_, next) {
    async.parallel({
      'offering'    : function (next) {
        courses.offering(this.discipline, this.offering, next);
      }.bind(this),
      'requirements': function (next) {
        var query;

        query = this.model('Requirement').find();
        query.where('enrollment').equals(this.enrollment._id);
        query.where('_id').ne(this._id);
        query.where('discipline').ne(this.discipline);
        query.exec(next);
      }.bind(this)
    }, next);
  }.bind(this), function (data, next) {
    async.every(data.requirements, function (requirement, next) {
      courses.offering(requirement.discipline, requirement.offering, function foundDisciplineOffering(error, offeringConflict) {
        var conflict = data.offering.schedules.some(function (schedule) {
          return offeringConflict.schedules.some(function (otherSchedule) {
            return (schedule.weekday === otherSchedule.weekday && schedule.hour === otherSchedule.hour);
          });
        });

        next(!!error || !conflict);
      });
    }.bind(this), next);
  }.bind(this)], next);
}, 'discipline with time conflict');

module.exports = mongoose.model('Requirement', schema);
<commit_msg>Detectar se pré requisitos são preenchidos #6 - Refactoring<commit_after>var VError, async, mongoose, jsonSelect, nconf, courses, calendar, history, Schema, schema;

VError = require('verror');
async = require('async');
mongoose = require('mongoose');
jsonSelect = require('mongoose-json-select');
courses = require('dacos-courses-driver');
calendar = require('dacos-calendar-driver');
history = require('dacos-history-driver');
nconf = require('nconf');
Schema = mongoose.Schema;

schema = new Schema({
  'enrollment' : {
    'type'     : Schema.ObjectId,
    'ref'      : 'Enrollment',
    'required' : true
  },
  'discipline' : {
    'type'     : String,
    'required' : true
  },
  'offering'   : {
    'type'     : String,
    'required' : true
  },
  'status'     : {
    'type'    : String,
    'enum'    : [ 'new', 'approved', 'rejected', 'quit' ],
    'default' : 'new'
  },
  'comment'    : {
    'type' : String
  },
  'priority'   : {
    'type' : Number
  },
  'createdAt'  : {
    'type'    : Date,
    'default' : Date.now
  },
  'updatedAt'  : {
    'type' : Date
  }
}, {
  'collection' : 'requirements',
  'strict'     : true,
  'toJSON'     : {
    'virtuals' : true
  }
});

schema.index({
  'enrollment' : 1,
  'discipline' : 1,
  'offering'   : 1
}, {
  'unique' : true
});

schema.plugin(jsonSelect, {
  '_id'        : 0,
  'enrollment' : 1,
  'discipline' : 1,
  'offering'   : 1,
  'status'     : 1,
  'comment'    : 1,
  'priority'   : 1,
  'createdAt'  : 1,
  'updatedAt'  : 1
});

schema.pre('save', function setRequirementUpdatedAt(next) {
  'use strict';

  this.updatedAt = new Date();
  next();
});

schema.pre('save', function setPriorityScore(next) {
  'use strict';

  var priorityScore;

  this.populate('enrollment');
  this.populate(function () {
    getDisciplineInformation(this.enrollment, this.discipline, this.offering, function (blockType, isAhead, isReserved) {

      // Calculating priority score
      if (blockType === 'required') {
        // Obligatory discipline
        if (isAhead) {
          if (isReserved) {
            priorityScore = 6;
          } else {
            priorityScore = 3;
          }
        } else {
          if (isReserved) {
            priorityScore = 9;
          } else {
            priorityScore = 7;
          }
        }
      } else if (blockType === 'optional') {
        // Optional discipline
        if (isAhead) {
          if (isReserved) {
            priorityScore = 5;
          } else {
            priorityScore = 2;
          }
        } else {
          if (isReserved) {
            priorityScore = 8;
          } else {
            priorityScore = 4;
          }
        }
      } else if (blockType === 'extra') {
        // Extra-curricular discipline
        if (isAhead) {
          if (isReserved) {
            priorityScore = 1;
          } else {
            priorityScore = 0;
          }
        } else {
          // TODO check why this is equal to the last conditional
          if (isReserved) {
            priorityScore = 1;
          } else {
            priorityScore = 0;
          }
        }
      }

      this.priority = priorityScore;
      //console.log(this.priority);

      next();
    }.bind(this));
  }.bind(this));
});

function getDisciplineInformation(enrollment, discipline, disciplineOffering, next) {
  'use strict';

  var blockType , isAhead, isReserved;

  blockType = 'optional';
  isAhead = false;
  isReserved = false;

  history.currentHistory(enrollment.user, function (error, currentHistory) {

    if (error) {
      error = new VError(error, 'Error when trying to get the history');
      return next(error);
    }

    if (!currentHistory) {
      return next(false);
    }

    courses.blocks(currentHistory.year, currentHistory.course + ' ' + currentHistory.modality, function (error, blocks) {
      if (error) {
        return next('');
      }

      async.some(blocks, function(block) {

        courses.requirement(currentHistory.year, currentHistory.course + ' ' + currentHistory.modality, block.code, this.discipline, function (error, requirement) {
          if (requirement) {
            var currentDate = new Date();

            var currentSemester;
            if (currentDate.getMonth() < 6) {
              currentSemester = 1;
            } else if(currentDate.getMonth() > 6) {
              currentSemester = 2;
            } else {
              currentSemester = currentDate.getDay() < 15 ? 1 : 2;
            }

            //TODO figure out how to get the offering reservation

            var userSemester = (currentDate.getFullYear() - currentHistory.year) * 2 + currentSemester;
            //var offeringReservations = disciplineOffering && disciplineOffering.reservations || [];

            blockType = block.type;
            //isAhead = offeringReservations.some(function (offeringReservation) { return offeringReservation === historyId; });
            isReserved = requirement && (userSemester < requirement.suggestedSemester);

            //requirement.code

            next(true);
          }
          else {
            next(false);
          }
        });
      }, function(result) {
        next(blockType, isAhead, isReserved);
      });
    });
  });
}

/**
 * verifica se uma solicitação de aumento de limite de créditos deve ser aberta
 */
schema.pre('save', function openCreditRaiseRequestIfNecessary(next) {
  'use strict';

  this.populate('enrollment');
  this.populate(function () {
    var user = this.enrollment.user;
    history.currentHistory(user, function (error, currentHistory) {
      if (error) {
        error = new VError(error, 'Error when trying to get the history');
        return next(error);
      }

      if (!currentHistory) {
        return next(false);
      }

      courses.modality(currentHistory.year, currentHistory.course + '-' + currentHistory.modality, function (error, modality) {
        if (error) {
          error = new VError(error, 'Error when trying to get the modality');
          return next(error);
        }

        if (!modality) {
          return next(new VError(error, 'Modality not found'));
        }

        var query;
        query = this.model('Requirement').find();
        query.where('enrollment').equals(this.enrollment._id);
        query.where('_id').ne(this._id);
        query.exec(function foundRequirements(error, requirements) {
          requirements.push(this);

          async.reduce(requirements, 0, function (sum, disciplineRequirement, next) {
            courses.discipline(disciplineRequirement.discipline, function (error, discipline) {
              next(error, discipline ? discipline.credits + sum : 0);
            }.bind(this));
          }.bind(this), function (error, credits) {
            if (error) {
              error = new VError(error, 'Error when trying to sum the credits');
              return next(error);
            }

            if (credits > modality.creditLimit) {
              this.enrollment.creditRaiseRequest.credits = credits;
              this.enrollment.creditRaiseRequest.date = new Date();
              this.enrollment.creditRaiseRequest.status = 'pending';
              this.enrollment.save(next);
            }
            else {
              next();
            }
          }.bind(this));
        }.bind(this));
      }.bind(this));
    }.bind(this));
  }.bind(this));
});

schema.path('offering').validate(function validateIfDisciplineOfferingExists(value, next) {
  'use strict';

  courses.offering(this.discipline, this.offering, function foundDisciplineOffering(error, offering) {
    next(!error && !!offering);
  });
}, 'discipline offering not found');

schema.path('discipline').validate(function validateDisciplineRequirement(value, next) {
  'use strict';

  async.waterfall([function (next) {
    this.populate('enrollment');
    this.populate(next);
  }.bind(this), function (_, next) {
    async.parallel({
      'discipline': function (next) {
        courses.discipline(this.discipline, next);
      }.bind(this),
      'histories' : function (next) {
        history.histories(this.enrollment.user, next);
      }.bind(this)
    }, next);
  }.bind(this), function (data, next) {
    async.every(data.histories, function (userHistory) {
      if (data.discipline.requirements.length > 0) {
        data.discipline.requirements.forEach(function (disciplineRequirement) {
          history.discipline(this.enrollment.user, userHistory.year, disciplineRequirement.code, function (error, disciplineHistory) {
            next(!!error || (disciplineHistory && [1, 2, 3, 4, 7, 10, 11, 12, 13, 14, 15, 16, 20].lastIndexOf(disciplineHistory.status) > -1));
          }.bind(this));
        }.bind(this));
      }
      else {
        next();
      }
    }.bind(this), next);
  }.bind(this)], next);
}, 'discipline requirement not fulfilled');

schema.path('status').validate(function validateIfRequirementCanBeQuit(value, next) {
  'use strict';
  var todayDate, year;
  todayDate = new Date();
  year = todayDate.getFullYear();

  if (this.status === 'quit') {
    calendar.betweenEvents(todayDate, year, 'discipline-quit-starts', year, 'discipline-quit-ends', next);
  }
  else {
    next();
  }
}, 'outside of discipline quit period');

schema.path('discipline').validate(function validateDisciplineApproval(value, next) {
  'use strict';

  async.waterfall([function (next) {
    this.populate('enrollment');
    this.populate(next);
  }.bind(this), function (_, next) {
    async.parallel({
      'histories'  : function (next) {
        history.histories(this.enrollment.user, next);
      }.bind(this),
      'discipline' : function (next) {
        courses.discipline(this.discipline, next);
      }.bind(this)
    }, next);
  }.bind(this), function (data, next) {
    async.every(data.histories, function (userHistory, next) {
      history.discipline(this.enrollment.user, userHistory.year, data.discipline.code, function (error, disciplineHistory) {
        next(!!error || !(disciplineHistory && [1, 2, 3, 4, 7, 10, 11, 12, 13, 14, 15, 16, 20].lastIndexOf(disciplineHistory.status) > -1));
      }.bind(this));
    }.bind(this), next);
  }.bind(this)], next);
}, 'user was already approved on discipline');

schema.path('offering').validate(function validateIfRequirementHasTimeConflict(value, next) {
  'use strict';

  async.waterfall([function (next) {
    this.populate('enrollment');
    this.populate(next);
  }.bind(this), function (_, next) {
    async.parallel({
      'offering'    : function (next) {
        courses.offering(this.discipline, this.offering, next);
      }.bind(this),
      'requirements': function (next) {
        var query;

        query = this.model('Requirement').find();
        query.where('enrollment').equals(this.enrollment._id);
        query.where('_id').ne(this._id);
        query.where('discipline').ne(this.discipline);
        query.exec(next);
      }.bind(this)
    }, next);
  }.bind(this), function (data, next) {
    async.every(data.requirements, function (requirement, next) {
      courses.offering(requirement.discipline, requirement.offering, function foundDisciplineOffering(error, offeringConflict) {
        var conflict = data.offering.schedules.some(function (schedule) {
          return offeringConflict.schedules.some(function (otherSchedule) {
            return (schedule.weekday === otherSchedule.weekday && schedule.hour === otherSchedule.hour);
          });
        });

        next(!!error || !conflict);
      });
    }.bind(this), next);
  }.bind(this)], next);
}, 'discipline with time conflict');

module.exports = mongoose.model('Requirement', schema);
<|endoftext|>


<commit_before>
"use strict";

var SuperJS = require('../../index');

/**
 * The base model class is the basic building block for models in database engines.
 * They come bottled with methods to validate and sanitize attributes.
 *
 * @exports Model
 * @namespace SuperJS
 * @extends SuperJS.Class
 */

module.exports = SuperJS.Class.extend({

  init: function (app) {

    //maintain a reference to the app
    this.app = app;

    //maintain a reference to the log engine
    this.log = app.log;

    //check the configuration for issues
    this.processConfiguration();
  },

  processConfiguration: function() {

    if( this.attributes ) {

      for( var attribute in this.attributes ) {

        if( !this.attributes[attribute].validate ) {
          this.attributes[attribute].validate = {};
        }

        if( !this.attributes[attribute].sanitize ) {
          this.attributes[attribute].sanitize = {};
        }

        if( this.attributes[attribute].type ) {
          this.attributes[attribute].validate[this.attributes[attribute].type] = true;
        }

        //warn & remove any missing validations
        for( var validation in this.attributes[attribute].validate ) {
          if( !this.app.services.validate[validation] ) {
            this.log.warn('validation missing:',validation);
            delete this.attributes[attribute].validate[validation];
          }
        }

        //warn & remove any missing sanitizations
        for( var sanitization in this.attributes[attribute].sanitize ) {
          if( !this.app.services.sanitize[sanitization] ) {
            this.log.warn('sanitization missing:',sanitization);
            delete this.attributes[attribute].sanitize[sanitization];
          }
        }

      }

    } else {

      this.log.warn('model missing attributes object...');

    }

  },

  validate: function(req) {
  },

  sanitize: function(req) {
  }

});
<commit_msg>Added the model of the missing validation to the log message.<commit_after>
"use strict";

var SuperJS = require('../../index');

/**
 * The base model class is the basic building block for models in database engines.
 * They come bottled with methods to validate and sanitize attributes.
 *
 * @exports Model
 * @namespace SuperJS
 * @extends SuperJS.Class
 */

module.exports = SuperJS.Class.extend({

  init: function (app) {

    //maintain a reference to the app
    this.app = app;

    //maintain a reference to the log engine
    this.log = app.log;

  },

  processConfiguration: function() {

    if( this.attributes ) {

      for( var attribute in this.attributes ) {

        if( !this.attributes[attribute].validate ) {
          this.attributes[attribute].validate = {};
        }

        if( !this.attributes[attribute].sanitize ) {
          this.attributes[attribute].sanitize = {};
        }

        if( this.attributes[attribute].type ) {
          this.attributes[attribute].validate[this.attributes[attribute].type] = true;
        }

        //warn & remove any missing validations
        for( var validation in this.attributes[attribute].validate ) {
          if( !this.app.services.validate[validation] ) {
            this.log.warn('validation missing:',{validation: validation, model: this.name});
            delete this.attributes[attribute].validate[validation];
          }
        }

        //warn & remove any missing sanitizations
        for( var sanitization in this.attributes[attribute].sanitize ) {
          if( !this.app.services.sanitize[sanitization] ) {
            this.log.warn('sanitization missing:',sanitization);
            delete this.attributes[attribute].sanitize[sanitization];
          }
        }

      }

    } else {

      this.log.warn('model missing attributes object...');

    }

  },

  validate: function(req) {
  },

  sanitize: function(req) {
  }

});
<|endoftext|>