Wire command zone menu, harden counter command handling, and expand tests

- Connect aMoveToCommandZone to cmMoveToCommandZone; the menu item was
    added, shown, and shortcut-bound but never wired, so it did nothing
  - Extract evaluateModifyCounter() and route cmdIncCounter/cmdSetCounter
    through it, replacing isCommandZoneCounterBlocked; reject inc/set on
    an inactive tax counter so a hidden counter cannot accumulate value
  - Reject reserved tax counter names in cmdCreateCounter to prevent a
    client from spoofing a system tax counter via a user-created counter
  - Route AbstractCounter::valueChanged through the virtual setValue() so
    CommanderTaxCounter clamps and refreshes its tooltip on value changes
  - Deduplicate actPlayAndIncreaseTax/actPlayAndIncreasePartnerTax into a
    single playAndIncreaseTax(counterId) helper
  - Move MINIMUM_STACKING_HEIGHT from CommandZone to the PlayerGraphicsItem
    layout code, its only consumer
  - Remove unused AbstractCounter forward declaration from player_logic.h
  - Add EvaluateModifyCounter tests and PartnerTax cases for
    evaluateSetCounterActive
  - Pin the FirstUserId floor in new_counter_id_test using ids 3 and 5 so
    a naive "highest id + 1" regression fails the test
This commit is contained in:
DawnFire42 2026-06-28 21:43:42 -04:00
parent 0bfb0f4131
commit bc09a19b50
No known key found for this signature in database
GPG key ID: 24BB855EE2911B33
11 changed files with 172 additions and 47 deletions

View file

@ -1,7 +1,8 @@
// Unit tests for the pure authorization logic extracted from the counter command
// handlers. Server_Player::evaluateDelCounter() and evaluateSetCounterActive() are
// static and depend only on their arguments, so each guard branch can be exercised
// directly without a started game, network stack, or player fixture.
// handlers. Server_Player::evaluateDelCounter(), evaluateSetCounterActive() and
// evaluateModifyCounter() are static and depend only on their arguments, so each
// guard branch can be exercised directly without a started game, network stack, or
// player fixture.
#include "game/server_counter.h"
#include "game/server_player.h"
@ -158,6 +159,89 @@ TEST(EvaluateSetCounterActive, AllowsDisablingWhenCounterIsZero)
Response::RespOk);
}
// PartnerTax is the second reserved tax counter and must flow through the same auth
// path as CommanderTax: enabling is always permitted...
TEST(EvaluateSetCounterActive, AllowsEnablingPartnerTax)
{
Server_Counter counter = makeCounter(CounterIds::PartnerTax, 0);
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::PartnerTax, &counter,
/*requestedActive=*/true),
Response::RespOk);
}
// ...and disabling is rejected while it still holds accumulated tax.
TEST(EvaluateSetCounterActive, RejectsDisablingPartnerTaxWhenAccumulated)
{
Server_Counter counter = makeCounter(CounterIds::PartnerTax, 2);
EXPECT_EQ(Server_Player::evaluateSetCounterActive(true, false, true, CounterIds::PartnerTax, &counter,
/*requestedActive=*/false),
Response::RespContextError);
}
// ---------------------------------------------------------------------------
// evaluateModifyCounter (shared by cmdIncCounter / cmdSetCounter)
// ---------------------------------------------------------------------------
// No counter may be modified before the game starts.
TEST(EvaluateModifyCounter, RejectsWhenGameNotStarted)
{
Server_Counter counter = makeCounter(UserCounterId, 0);
EXPECT_EQ(Server_Player::evaluateModifyCounter(/*gameStarted=*/false, false, /*commandZoneEnabled=*/true,
UserCounterId, &counter),
Response::RespGameNotStarted);
}
// A conceded player may no longer modify counters.
TEST(EvaluateModifyCounter, RejectsWhenPlayerConceded)
{
Server_Counter counter = makeCounter(UserCounterId, 0);
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, /*playerConceded=*/true, true, UserCounterId, &counter),
Response::RespContextError);
}
// An ordinary user counter can be modified regardless of command-zone state.
TEST(EvaluateModifyCounter, AllowsUserCounter)
{
Server_Counter counter = makeCounter(UserCounterId, 0);
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/false, UserCounterId, &counter),
Response::RespOk);
}
// A non-existent counter is reported as not found.
TEST(EvaluateModifyCounter, RejectsMissingCounter)
{
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, true, UserCounterId, nullptr),
Response::RespNameNotFound);
}
// A tax counter may not be modified outside a Commander game (command zone disabled).
TEST(EvaluateModifyCounter, RejectsTaxCounterWhenCommandZoneDisabled)
{
Server_Counter counter = makeCounter(CounterIds::CommanderTax, 0);
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/false, CounterIds::CommanderTax,
&counter),
Response::RespContextError);
}
// An inactive (hidden) tax counter must not accumulate value behind the scenes.
TEST(EvaluateModifyCounter, RejectsInactiveTaxCounter)
{
Server_Counter counter = makeCounter(CounterIds::PartnerTax, 0);
(void)counter.setActive(false);
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/true, CounterIds::PartnerTax,
&counter),
Response::RespContextError);
}
// An active tax counter in a Commander game may be modified.
TEST(EvaluateModifyCounter, AllowsActiveTaxCounter)
{
Server_Counter counter = makeCounter(CounterIds::CommanderTax, 0);
EXPECT_EQ(Server_Player::evaluateModifyCounter(true, false, /*commandZoneEnabled=*/true, CounterIds::CommanderTax,
&counter),
Response::RespOk);
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);

View file

@ -41,13 +41,15 @@ TEST(NewCounterId, ReturnsFirstUserIdWhenNoCounters)
EXPECT_EQ(f.player.newCounterId(), CounterIds::FirstUserId);
}
// Reserved ids 0-9 (standard player counters and the commander tax counters) must
// not drag a new user counter down into the reserved range.
// Reserved ids must not drag a new user counter down into the reserved range. The
// ids here (3 and 5) are chosen so a naive "highest id + 1" implementation would
// return 6 (inside the reserved range) and fail this test; only the FirstUserId
// floor yields the correct 10, so this pins the floor against regression.
TEST(NewCounterId, SkipsReservedRangeWhenOnlyReservedCountersExist)
{
PlayerFixture f;
f.player.addCounter(new Server_Counter(3, "g", color(), 20, 0));
f.player.addCounter(new Server_Counter(CounterIds::PartnerTax, CounterNames::PartnerTax, color(), 20, 0));
f.player.addCounter(new Server_Counter(5, "h", color(), 20, 0));
EXPECT_EQ(f.player.newCounterId(), CounterIds::FirstUserId);
}