Files
crewli/api/tests/Feature/Api/V1/Portal/PortalProfileTest.php
bert.hausmans 59ad09fad2 feat(portal): auth persistence, shift visibility, profile page, and UI polish
- Fix session persistence: add loading state to App.vue, hydrate portal store
  in router guards so page refresh preserves auth + event context
- Fix shift visibility for festivals: query child event time slots so shifts
  on sub-events appear in the portal
- Add profile page with editable personal info and password change
- Add backend endpoints: PUT /portal/profile and PUT /portal/password
- Fix registration form: make first_name/last_name editable for logged-in users
- Restyle login page: remove Vuexy illustration, center form with Crewli branding
- Improve dashboard StatusCard with action cards, icons, and upcoming shift count
- Enhance shift cards with status border colors and availability progress bars

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 10:19:14 +02:00

197 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\V1\Portal;
use App\Models\CrowdType;
use App\Models\Event;
use App\Models\Organisation;
use App\Models\Person;
use App\Models\User;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class PortalProfileTest extends TestCase
{
use RefreshDatabase;
private User $volunteer;
private Organisation $organisation;
private Event $event;
private Person $person;
protected function setUp(): void
{
parent::setUp();
$this->seed(RoleSeeder::class);
$this->organisation = Organisation::factory()->create();
$this->volunteer = User::factory()->create([
'first_name' => 'Jan',
'last_name' => 'Jansen',
'password' => Hash::make('old-password'),
]);
$this->organisation->users()->attach($this->volunteer, ['role' => 'org_member']);
$this->event = Event::factory()->create(['organisation_id' => $this->organisation->id]);
$crowdType = CrowdType::factory()->systemType('VOLUNTEER')->create([
'organisation_id' => $this->organisation->id,
]);
$this->person = Person::factory()->approved()->create([
'event_id' => $this->event->id,
'crowd_type_id' => $crowdType->id,
'user_id' => $this->volunteer->id,
'first_name' => 'Jan',
'last_name' => 'Jansen',
'phone' => '0612345678',
]);
}
// =========================================================================
// Profile update
// =========================================================================
public function test_update_profile_updates_user_and_person(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/profile', [
'event_id' => $this->event->id,
'first_name' => 'Piet',
'last_name' => 'Pietersen',
'phone' => '0687654321',
'date_of_birth' => '1990-05-15',
'remarks' => 'Vegetarisch',
]);
$response->assertOk()
->assertJsonPath('data.message', 'Profiel bijgewerkt.');
$this->volunteer->refresh();
$this->assertEquals('Piet', $this->volunteer->first_name);
$this->assertEquals('Pietersen', $this->volunteer->last_name);
$this->person->refresh();
$this->assertEquals('Piet', $this->person->first_name);
$this->assertEquals('Pietersen', $this->person->last_name);
$this->assertEquals('0687654321', $this->person->phone);
$this->assertEquals('1990-05-15', $this->person->date_of_birth->toDateString());
$this->assertEquals('Vegetarisch', $this->person->remarks);
}
public function test_update_profile_partial_update(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/profile', [
'event_id' => $this->event->id,
'phone' => '0699999999',
]);
$response->assertOk();
$this->person->refresh();
$this->assertEquals('0699999999', $this->person->phone);
$this->assertEquals('Jan', $this->person->first_name); // unchanged
}
public function test_update_profile_requires_event_id(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/profile', [
'first_name' => 'Piet',
]);
$response->assertUnprocessable();
}
public function test_update_profile_unauthenticated(): void
{
$response = $this->putJson('/api/v1/portal/profile', [
'event_id' => $this->event->id,
'first_name' => 'Piet',
]);
$response->assertUnauthorized();
}
// =========================================================================
// Password update
// =========================================================================
public function test_update_password(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/password', [
'current_password' => 'old-password',
'password' => 'new-secure-password',
'password_confirmation' => 'new-secure-password',
]);
$response->assertOk()
->assertJsonPath('data.message', 'Wachtwoord gewijzigd.');
$this->volunteer->refresh();
$this->assertTrue(Hash::check('new-secure-password', $this->volunteer->password));
}
public function test_update_password_wrong_current(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/password', [
'current_password' => 'wrong-password',
'password' => 'new-secure-password',
'password_confirmation' => 'new-secure-password',
]);
$response->assertUnprocessable()
->assertJsonValidationErrors(['current_password']);
}
public function test_update_password_mismatch(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/password', [
'current_password' => 'old-password',
'password' => 'new-secure-password',
'password_confirmation' => 'different-password',
]);
$response->assertUnprocessable()
->assertJsonValidationErrors(['password']);
}
public function test_update_password_too_short(): void
{
Sanctum::actingAs($this->volunteer);
$response = $this->putJson('/api/v1/portal/password', [
'current_password' => 'old-password',
'password' => 'short',
'password_confirmation' => 'short',
]);
$response->assertUnprocessable()
->assertJsonValidationErrors(['password']);
}
public function test_update_password_unauthenticated(): void
{
$response = $this->putJson('/api/v1/portal/password', [
'current_password' => 'old-password',
'password' => 'new-secure-password',
'password_confirmation' => 'new-secure-password',
]);
$response->assertUnauthorized();
}
}