using PkmnLib.Dynamic.Models;
using PkmnLib.Plugin.Gen7.Scripts.Moves;
using PkmnLib.Static;
namespace PkmnLib.Plugin.Gen7.Tests.Scripts.Moves;
///
/// Tests for the move script.
/// Gen VII Bulbapedia behavior: "Acupressure chooses one of the target's stats at random and raises it by
/// two stages. It can raise either the target's Attack, Defense, Special Attack, Special Defense, Speed,
/// accuracy, or evasion stat but will not attempt to raise a stat that is already maximized, meaning that
/// the move will fail if all stats are maximized."
///
public class AcupressureTests
{
private static (Acupressure script, IExecutingMove move, IPokemon target, IBattleRandom random, IHitData hitData)
CreateTestSetup()
{
var script = new Acupressure();
var move = Substitute.For();
var target = Substitute.For();
var hitData = Substitute.For();
move.GetHitData(target, 0).Returns(hitData);
var random = Substitute.For();
var battle = Substitute.For();
battle.Random.Returns(random);
var battleData = Substitute.For();
battleData.Battle.Returns(battle);
var user = Substitute.For();
user.BattleData.Returns(battleData);
move.User.Returns(user);
target.StatBoost.Returns(new StatBoostStatisticSet());
return (script, move, target, random, hitData);
}
///
/// Helper to extract the stat argument of a ChangeStatBoost call received by the target.
///
private static object?[]? GetStatBoostCallArgs(IPokemon target)
{
var call = target.ReceivedCalls().FirstOrDefault(c => c.GetMethodInfo().Name == "ChangeStatBoost");
return call?.GetArguments();
}
///
/// Bulbapedia: "Acupressure chooses one of the target's stats at random and raises it by two stages."
/// When the random roll selects Attack, the target's Attack is raised by exactly two stages.
///
[Test]
public async Task OnSecondaryEffect_RandomSelectsAttack_RaisesAttackByTwoStages()
{
// Arrange
var (script, move, target, random, _) = CreateTestSetup();
// Attack is the first entry in the pool of raisable stats (HP is excluded).
random.GetInt(Arg.Any(), Arg.Any()).Returns(0);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var args = GetStatBoostCallArgs(target);
await Assert.That(args).IsNotNull();
await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.Attack);
await Assert.That((sbyte)args[1]!).IsEqualTo((sbyte)2);
}
///
/// Bulbapedia: "It can raise either the target's Attack, Defense, Special Attack, Special Defense, Speed,
/// accuracy, or evasion stat" — HP is not one of the raisable stats. When the random roll returns the lowest
/// value of its range, the selected stat must be Attack, not HP.
///
[Test]
public async Task OnSecondaryEffect_RandomMinimumRoll_SelectsAttackNotHp()
{
// Arrange
var (script, move, target, random, _) = CreateTestSetup();
// Return the inclusive lower bound of whatever range is requested.
random.GetInt(Arg.Any(), Arg.Any()).Returns(ci => ci.ArgAt(0));
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
var args = GetStatBoostCallArgs(target);
await Assert.That(args).IsNotNull();
await Assert.That((Statistic)args![0]!).IsEqualTo(Statistic.Attack);
}
///
/// Bulbapedia: "It can raise either the target's Attack, Defense, Special Attack, Special Defense, Speed,
/// accuracy, or evasion stat". Accuracy and evasion must be selectable: when the random roll returns the
/// highest value of its range, the selected stat should be one of the stats beyond Speed in the
/// enum (Evasion or Accuracy).
///
[Test]
public async Task OnSecondaryEffect_RandomMaximumRoll_CanSelectAccuracyOrEvasion()
{
// Arrange
var (script, move, target, random, _) = CreateTestSetup();
// Return the highest value of whatever range is requested (upper bound is exclusive).
random.GetInt(Arg.Any(), Arg.Any()).Returns(ci => ci.ArgAt(1) - 1);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert - the highest selectable stat should be Evasion or Accuracy
var args = GetStatBoostCallArgs(target);
await Assert.That(args).IsNotNull();
var stat = (Statistic)args![0]!;
await Assert.That(stat is Statistic.Evasion or Statistic.Accuracy).IsTrue();
}
///
/// Bulbapedia: "the move will fail if all stats are maximized." All raisable stats (Attack through accuracy;
/// HP boosts cannot be raised by any move and stay at 0) are at +6, so the move must fail without attempting
/// a stat change.
///
[Test]
public async Task OnSecondaryEffect_AllRaisableStatsMaximized_Fails()
{
// Arrange
var (script, move, target, _, hitData) = CreateTestSetup();
var statBoost = new StatBoostStatisticSet(0, 6, 6, 6, 6, 6) { Evasion = 6, Accuracy = 6 };
target.StatBoost.Returns(statBoost);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert
hitData.Received(1).Fail();
await Assert.That(target.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ChangeStatBoost")).IsFalse();
}
///
/// Bulbapedia: "will not attempt to raise a stat that is already maximized". With the target's Attack at +6
/// and other stats available, Attack must never be the raised stat, regardless of what the random roll
/// returns. The rolls cover every index of the six-stat selection pool that remains.
///
[Test, Arguments(0), Arguments(1), Arguments(2), Arguments(3), Arguments(4), Arguments(5)]
public async Task OnSecondaryEffect_StatAlreadyMaximized_NeverRaisesThatStat(int roll)
{
// Arrange
var (script, move, target, random, _) = CreateTestSetup();
var statBoost = new StatBoostStatisticSet(0, 6, 0, 0, 0, 0);
target.StatBoost.Returns(statBoost);
random.GetInt(Arg.Any(), Arg.Any()).Returns(roll);
// Act
script.OnSecondaryEffect(move, target, 0);
// Assert - a stat is raised, but never the maximized Attack
var args = GetStatBoostCallArgs(target);
await Assert.That(args).IsNotNull();
await Assert.That((Statistic)args![0]!).IsNotEqualTo(Statistic.Attack);
}
}