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(); });