seed(CityWorldSeeder::class); $this->user = User::factory()->create(['status' => User::STATUS_ACTIVE, 'is_active' => true]); $this->character = Character::ensureForUser($this->user); $this->actingAs($this->user); } /** * City payload includes plazas with gym equipment. */ public function test_city_data_includes_plazas(): void { $this->getJson('/city/data') ->assertOk() ->assertJsonStructure([ 'plazas' => [ ['id', 'position', 'size', 'equipment' => [['id', 'kind', 'position']]], ], ]); $this->assertNotEmpty(CityPlazas::forCity($this->character->city)); } /** * Exercising near equipment raises health and increments the counter. */ public function test_exercise_gains_health_near_equipment(): void { $eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0]; $this->character->forceFill([ 'pos_x' => $eq['position'][0], 'pos_z' => $eq['position'][1], 'health' => 80, 'energy' => 90, 'state' => Character::STATE_AWAKE, ])->save(); $this->postJson('/city/actions/exercise', [ 'pos_x' => $eq['position'][0], 'pos_z' => $eq['position'][1], ])->assertOk() ->assertJsonPath('character.health', 82) ->assertJsonPath('character.state', Character::STATE_EXERCISING); $this->character->refresh(); $this->assertSame(82, (int) $this->character->health); $this->assertSame(1, (int) $this->character->exercise_count); $this->assertSame(88, (int) $this->character->energy); } /** * Far from equipment the action is rejected. */ public function test_exercise_requires_nearby_equipment(): void { $eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0]; $this->character->forceFill([ 'pos_x' => $eq['position'][0] + 50, 'pos_z' => $eq['position'][1] + 50, 'health' => 50, ])->save(); $this->postJson('/city/actions/exercise', [ 'pos_x' => $eq['position'][0] + 50, 'pos_z' => $eq['position'][1] + 50, ])->assertStatus(422); } /** * Max health refuses further gains but keeps the character healthy. */ public function test_exercise_stops_at_max_health(): void { $eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0]; $this->character->forceFill([ 'pos_x' => $eq['position'][0], 'pos_z' => $eq['position'][1], 'health' => 100, ])->save(); $this->postJson('/city/actions/exercise', [ 'pos_x' => $eq['position'][0], 'pos_z' => $eq['position'][1], ])->assertOk() ->assertJsonPath('character.health', 100); $this->assertSame(0, (int) $this->character->fresh()->exercise_count); } /** * stop-exercise returns the character to awake. */ public function test_stop_exercise_returns_awake(): void { $eq = CityPlazas::forCity($this->character->city)[0]['equipment'][0]; $this->character->forceFill([ 'pos_x' => $eq['position'][0], 'pos_z' => $eq['position'][1], 'health' => 70, 'state' => Character::STATE_AWAKE, ])->save(); $this->postJson('/city/actions/exercise', [ 'pos_x' => $eq['position'][0], 'pos_z' => $eq['position'][1], ])->assertOk(); $this->postJson('/city/actions/stop-exercise') ->assertOk() ->assertJsonPath('character.state', Character::STATE_AWAKE); } }