Files
City10/tests/Feature/HospitalAdmissionTest.php
T
marcocosta10000 79ce2a1825 feat(world): add persistent citizen simulation with needs, jobs, and social features
- Adds Character model with survival needs (hunger, thirst, energy) and state machine
- Implements city:tick world ticker for offline progression every minute
- Adds city territory expansion and building construction system
- Introduces job economy, inventory, and transaction audit trail
- Adds social graph (friends, marriage, children) and character chat
- Implements admin approval workflow for new registrations
- Adds hospital admission system for traffic accidents
- Includes comprehensive feature tests and world seeders
2026-10-17 21:46:23 -03:00

151 lines
4.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\Building;
use App\Models\Character;
use App\Models\City;
use App\Models\User;
use Database\Seeders\CityWorldSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
/**
* Traffic accident → hospital admission for two game days.
*/
class HospitalAdmissionTest extends TestCase
{
use RefreshDatabase;
protected User $user;
protected Character $character;
/**
* Seed world with the hospital building.
*/
protected function setUp(): void
{
parent::setUp();
$this->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);
}
/**
* A vehicle collision admits the character for two game days at the hospital.
*/
public function test_accident_admits_character_for_two_game_days(): void
{
$response = $this->postJson('/city/actions/accident');
$response->assertOk()
->assertJsonPath('character.state', Character::STATE_HOSPITAL)
->assertJsonStructure(['message', 'hospitalized_until', 'bed' => ['pos_x', 'pos_z']]);
$this->character->refresh();
$this->assertSame(Character::STATE_HOSPITAL, $this->character->state);
$this->assertNotNull($this->character->hospitalized_until);
$this->assertEqualsWithDelta(
now()->addSeconds(Character::HOSPITAL_STAY_SECONDS)->getTimestamp(),
$this->character->hospitalized_until->getTimestamp(),
5,
);
$this->assertSame(Character::HOSPITAL_STAY_SECONDS, 480, 'Two game days = 2 × 240s');
$hospital = Building::where('type', Building::TYPE_HOSPITAL)->firstOrFail();
$this->assertSame((float) $hospital->pos_x, (float) $this->character->pos_x);
}
/**
* While admitted, ordinary actions are rejected.
*/
public function test_actions_blocked_while_hospitalized(): void
{
$this->character->admitToHospital();
$this->postJson('/city/actions/eat')->assertStatus(422);
$this->postJson('/city/actions/work')->assertStatus(422);
$this->postJson('/city/actions/build', [
'pos_x' => 10, 'pos_z' => 50,
])->assertStatus(422);
}
/**
* Position updates keep the character in the hospital bed while admitted.
*/
public function test_position_frozen_while_hospitalized(): void
{
$bed = $this->character->admitToHospital();
$this->postJson('/city/actions/position', ['pos_x' => 10, 'pos_z' => 60])
->assertOk()
->assertJsonPath('state', Character::STATE_HOSPITAL);
$this->character->refresh();
$this->assertSame($bed[0], (float) $this->character->pos_x);
$this->assertSame($bed[1], (float) $this->character->pos_z);
}
/**
* After the two-day stay the ticker releases the character.
*/
public function test_ticker_releases_after_two_game_days(): void
{
$this->character->admitToHospital();
// Move the stay into the past to simulate elapsed time.
$this->character->forceFill([
'hospitalized_until' => now()->subSecond(),
'last_tick_at' => null,
])->save();
$this->artisan('city:tick')->assertSuccessful();
$this->character->refresh();
$this->assertSame(Character::STATE_AWAKE, $this->character->state);
$this->assertNull($this->character->hospitalized_until);
}
/**
* While still within the stay the ticker keeps the hospital state.
*/
public function test_ticker_keeps_admission_before_discharge(): void
{
$this->character->admitToHospital();
$this->character->forceFill(['last_tick_at' => null])->save();
$this->artisan('city:tick')->assertSuccessful();
$this->character->refresh();
$this->assertSame(Character::STATE_HOSPITAL, $this->character->state);
$this->assertTrue($this->character->hospitalized_until->isFuture());
}
/**
* City data payload exposes the discharge timestamp.
*/
public function test_city_data_includes_hospital_state(): void
{
$this->character->admitToHospital();
$this->getJson('/city/data')
->assertOk()
->assertJsonPath('me.state', Character::STATE_HOSPITAL)
->assertJsonPath('me.hospitalized_until', $this->character->hospitalized_until->toIso8601String());
}
/**
* Hospital building exists in the seeded city.
*/
public function test_hospital_building_seeded(): void
{
$city = City::where('slug', 'city10')->firstOrFail();
$hospital = Building::where('city_id', $city->id)
->where('type', Building::TYPE_HOSPITAL)
->first();
$this->assertNotNull($hospital);
$this->assertTrue($hospital->is_complete);
}
}