- Delete unused Livewire Search test and fuel type select Blade component - Move subscription webhook listener from EventServiceProvider to AppServiceProvider - Add FUEL_TYPES global config to app layout for client-side use - Add Billable trait to User model and include email_verified_at in fillable - Implement monthly/annual cadence toggle with pricing display and smart CTA routing on homepage - Update VerifyApiKeyMiddlewareTest to use e10 instead of petrol - Refactor PollFuelPrices to auto-refresh stale stations based on last_seen_at - Add incremental polling with cached timestamp and effective-start-timestamp param to FuelPriceService - Normalize amenities/fuel_types from API objects to flat arrays, skip stations missing required fields - Log response body on API failures in ApiLogger - Default homepage sort to 'reliable' instead of 'price'
201 lines
6.8 KiB
PHP
201 lines
6.8 KiB
PHP
<?php
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Laravel\Sanctum\Sanctum;
|
|
|
|
it('returns user preferences for authenticated user', function (): void {
|
|
$user = User::factory()->create(['preferred_fuel_type' => 'b7_standard']);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->getJson('/api/user/preferences')
|
|
->assertOk()
|
|
->assertJsonFragment(['preferred_fuel_type' => 'b7_standard']);
|
|
});
|
|
|
|
it('updates user preferences', function (): void {
|
|
$user = User::factory()->create(['preferred_fuel_type' => 'e10']);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/preferences', ['preferred_fuel_type' => 'b7_standard'])
|
|
->assertOk()
|
|
->assertJsonFragment(['preferred_fuel_type' => 'b7_standard']);
|
|
|
|
expect($user->fresh()->preferred_fuel_type)->toBe('b7_standard');
|
|
});
|
|
|
|
it('rejects invalid fuel type in preferences update', function (): void {
|
|
$user = User::factory()->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/preferences', ['preferred_fuel_type' => 'aviation_fuel'])
|
|
->assertUnprocessable();
|
|
});
|
|
|
|
it('returns saved stations for authenticated user', function (): void {
|
|
$user = User::factory()->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->getJson('/api/user/saved-stations')
|
|
->assertOk()
|
|
->assertJsonStructure(['data']);
|
|
});
|
|
|
|
it('saves a station', function (): void {
|
|
$user = User::factory()->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->postJson('/api/user/saved-stations', ['station_id' => 'abc123'])
|
|
->assertCreated();
|
|
|
|
expect($user->savedStations()->where('station_id', 'abc123')->exists())->toBeTrue();
|
|
});
|
|
|
|
it('removes a saved station', function (): void {
|
|
$user = User::factory()->create();
|
|
$user->savedStations()->create(['station_id' => 'abc123']);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->deleteJson('/api/user/saved-stations/abc123')
|
|
->assertNoContent();
|
|
|
|
expect($user->savedStations()->where('station_id', 'abc123')->exists())->toBeFalse();
|
|
});
|
|
|
|
it('rejects unauthenticated requests to user endpoints', function (): void {
|
|
$this->getJson('/api/user/preferences')->assertUnauthorized();
|
|
$this->getJson('/api/user/saved-stations')->assertUnauthorized();
|
|
});
|
|
|
|
// --- Profile update ---
|
|
|
|
it('updates user profile name and email', function (): void {
|
|
$user = User::factory()->create(['name' => 'Old Name', 'email' => 'old@example.com']);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/profile', ['name' => 'New Name', 'email' => 'new@example.com'])
|
|
->assertOk()
|
|
->assertJsonFragment(['name' => 'New Name', 'email' => 'new@example.com']);
|
|
|
|
expect($user->fresh()->name)->toBe('New Name');
|
|
expect($user->fresh()->email)->toBe('new@example.com');
|
|
});
|
|
|
|
it('nulls email_verified_at when email changes', function (): void {
|
|
$user = User::factory()->create(['email_verified_at' => now()]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/profile', ['name' => $user->name, 'email' => 'changed@example.com'])
|
|
->assertOk();
|
|
|
|
expect($user->fresh()->email_verified_at)->toBeNull();
|
|
});
|
|
|
|
it('does not null email_verified_at when email is unchanged', function (): void {
|
|
$user = User::factory()->create(['email_verified_at' => now()]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/profile', ['name' => 'New Name', 'email' => $user->email])
|
|
->assertOk();
|
|
|
|
expect($user->fresh()->email_verified_at)->not->toBeNull();
|
|
});
|
|
|
|
it('rejects profile update with duplicate email', function (): void {
|
|
User::factory()->create(['email' => 'taken@example.com']);
|
|
$user = User::factory()->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/profile', ['name' => 'Name', 'email' => 'taken@example.com'])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(['email']);
|
|
});
|
|
|
|
it('rejects profile update with missing name', function (): void {
|
|
$user = User::factory()->create();
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/profile', ['email' => 'new@example.com'])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(['name']);
|
|
});
|
|
|
|
// --- Password update ---
|
|
|
|
it('updates user password', function (): void {
|
|
$user = User::factory()->create(['password' => Hash::make('old-password')]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/password', [
|
|
'current_password' => 'old-password',
|
|
'password' => 'new-password',
|
|
'password_confirmation' => 'new-password',
|
|
])->assertOk();
|
|
|
|
expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue();
|
|
});
|
|
|
|
it('rejects wrong current password', function (): void {
|
|
$user = User::factory()->create(['password' => Hash::make('correct-password')]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/password', [
|
|
'current_password' => 'wrong-password',
|
|
'password' => 'new-password',
|
|
'password_confirmation' => 'new-password',
|
|
])->assertUnprocessable()
|
|
->assertJsonValidationErrors(['current_password']);
|
|
});
|
|
|
|
it('rejects mismatched password confirmation', function (): void {
|
|
$user = User::factory()->create(['password' => Hash::make('correct-password')]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->putJson('/api/user/password', [
|
|
'current_password' => 'correct-password',
|
|
'password' => 'new-password',
|
|
'password_confirmation' => 'different',
|
|
])->assertUnprocessable()
|
|
->assertJsonValidationErrors(['password']);
|
|
});
|
|
|
|
// --- Delete account ---
|
|
|
|
it('deletes user account with correct password', function (): void {
|
|
$user = User::factory()->create(['password' => Hash::make('my-password')]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->deleteJson('/api/user', ['password' => 'my-password'])
|
|
->assertNoContent();
|
|
|
|
expect(User::find($user->id))->toBeNull();
|
|
});
|
|
|
|
it('revokes sanctum tokens on account deletion', function (): void {
|
|
$user = User::factory()->create(['password' => Hash::make('my-password')]);
|
|
$user->createToken('test');
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->deleteJson('/api/user', ['password' => 'my-password'])
|
|
->assertNoContent();
|
|
|
|
expect($user->tokens()->count())->toBe(0);
|
|
});
|
|
|
|
it('rejects account deletion with wrong password', function (): void {
|
|
$user = User::factory()->create(['password' => Hash::make('correct-password')]);
|
|
Sanctum::actingAs($user);
|
|
|
|
$this->deleteJson('/api/user', ['password' => 'wrong-password'])
|
|
->assertUnprocessable()
|
|
->assertJsonValidationErrors(['password']);
|
|
|
|
expect(User::find($user->id))->not->toBeNull();
|
|
});
|
|
|
|
it('rejects unauthenticated requests to profile, password, and delete endpoints', function (): void {
|
|
$this->putJson('/api/user/profile', [])->assertUnauthorized();
|
|
$this->putJson('/api/user/password', [])->assertUnauthorized();
|
|
$this->deleteJson('/api/user', [])->assertUnauthorized();
|
|
});
|