getProperty('resolvedSettings'); $prop->setAccessible(true); $prop->setValue(null, null); Cache::flush(); Role::findOrCreate('User', 'web'); }); // ── deleteAccount ───────────────────────────────────────────────────────────── test('delete account succeeds when correct password is supplied', function () { $user = User::factory()->create(['password' => Hash::make('secret')]); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->deleteJson('/api/v1/profile/delete', ['password' => 'secret']) ->assertOk() ->assertJsonPath('status', 'success'); expect(User::find($user->id))->toBeNull(); }); test('delete account is rejected with wrong password', function () { $user = User::factory()->create(['password' => Hash::make('secret')]); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->deleteJson('/api/v1/profile/delete', ['password' => 'wrong']) ->assertStatus(422); expect(User::find($user->id))->not->toBeNull(); }); test('delete account requires password field', function () { $user = User::factory()->create(); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->deleteJson('/api/v1/profile/delete', []) ->assertStatus(422); }); test('delete account is not accessible to guests', function () { $this->deleteJson('/api/v1/profile/delete', ['password' => 'x']) ->assertUnauthorized(); }); test('delete account also revokes all user tokens', function () { $user = User::factory()->create(['password' => Hash::make('secret')]); $user->createToken('device-a'); $user->createToken('device-b'); $token = $user->createToken('device-c')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->deleteJson('/api/v1/profile/delete', ['password' => 'secret']) ->assertOk(); expect($user->tokens()->count())->toBe(0); }); // ── updatePassword (API) ────────────────────────────────────────────────────── test('api password update succeeds with valid credentials', function () { $user = User::factory()->create(['password' => Hash::make('old-pass')]); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/v1/profile/password', [ 'current_password' => 'old-pass', 'password' => 'New-Api-Pass1', 'password_confirmation' => 'New-Api-Pass1', ]) ->assertOk() ->assertJsonPath('status', 'success'); expect(Hash::check('New-Api-Pass1', $user->fresh()->password))->toBeTrue(); }); test('api password update is rejected when current password is wrong', function () { $user = User::factory()->create(['password' => Hash::make('real-pass')]); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/v1/profile/password', [ 'current_password' => 'not-real', 'password' => 'New-Api-Pass2', 'password_confirmation' => 'New-Api-Pass2', ]) ->assertStatus(422); }); test('api password update is rejected when reusing a history password', function () { app(SystemConfigService::class)->update(['password_history_count' => 3]); $user = User::factory()->create(['password' => Hash::make('current')]); PasswordHistory::create(['user_id' => $user->id, 'password' => Hash::make('Recycled-Api!1')]); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/v1/profile/password', [ 'current_password' => 'current', 'password' => 'Recycled-Api!1', 'password_confirmation' => 'Recycled-Api!1', ]) ->assertStatus(422) ->assertJsonPath('status', 'error'); }); test('api password update records change in password history', function () { app(SystemConfigService::class)->update(['password_history_count' => 5]); $user = User::factory()->create(['password' => Hash::make('old')]); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/v1/profile/password', [ 'current_password' => 'old', 'password' => 'Api-NewPass-99', 'password_confirmation' => 'Api-NewPass-99', ]); expect(PasswordHistory::where('user_id', $user->id)->count())->toBe(1); }); // ── register with PasswordPolicyService ────────────────────────────────────── test('register enforces min length from password policy setting', function () { app(SystemConfigService::class)->update(['password_min_length' => 12]); $this->postJson('/api/v1/register', [ 'name' => 'Test', 'email' => 'policy@example.com', 'password' => 'short', ])->assertStatus(422); }); test('register succeeds when password meets policy min length', function () { app(SystemConfigService::class)->update(['password_min_length' => 6]); $this->postJson('/api/v1/register', [ 'name' => 'Policy User', 'email' => 'policy2@example.com', 'password' => 'longenough', ])->assertStatus(201); }); test('register enforces numeric requirement when setting is enabled', function () { app(SystemConfigService::class)->update([ 'password_require_numeric' => true, 'password_min_length' => 6, ]); $this->postJson('/api/v1/register', [ 'name' => 'No Digits', 'email' => 'nodigits@example.com', 'password' => 'NoDigitsHere', ])->assertStatus(422); }); // ── updateProfile ───────────────────────────────────────────────────────────── test('authenticated user can update their name via api', function () { $user = User::factory()->create(); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/v1/profile/update', [ 'name' => 'Updated Name', 'email' => $user->email, ]) ->assertOk() ->assertJsonPath('status', 'success'); expect($user->fresh()->name)->toBe('Updated Name'); }); test('profile update rejects name longer than 255 chars', function () { $user = User::factory()->create(); $token = $user->createToken('app')->plainTextToken; $this->withHeader('Authorization', "Bearer {$token}") ->postJson('/api/v1/profile/update', [ 'name' => str_repeat('a', 256), 'email' => $user->email, ]) ->assertStatus(422); });