Files
pictureFrame-webApp/tests/Functional/Controller/UserApiControllerTest.php
T
football2801 2adb07518c
CI / test (push) Has been cancelled
feat(account): change-password endpoint + Settings modal
PATCH /api/user/password — verifies the current password, enforces
8-char minimum on the new one, and rehashes via the configured
password hasher. Returns 204 on success, 422 with an `error` body
on every validation failure (wrong current, too-short new, missing
fields).

Settings adds a "Change password" link under the Account section
that opens a modal with current/new/confirm fields and posts to the
new endpoint. Confirm-mismatch and submit-disabled wiring is
client-side; backend errors surface inline.

Tests: 4 new controller tests cover success, wrong-current,
short-new, and missing-fields; success path also re-fetches the
user and checks the hash actually changed.
2026-05-09 15:25:54 -04:00

181 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Tests\Functional\Controller;
use App\Tests\Functional\AppWebTestCase;
class UserApiControllerTest extends AppWebTestCase
{
// US-01: search q < 2 chars → []
public function test_search_short_query_returns_empty(): void
{
$user = $this->createUser('us01@example.com');
$client = $this->loginAs($user);
$client->request('GET', '/api/user/search?q=a');
$this->assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertSame([], $data);
}
// US-02: search valid q → results containing matching users
public function test_search_valid_query_returns_results(): void
{
$user = $this->createUser('us02_me@example.com');
$other = $this->createUser('us02_other@example.com');
$client = $this->loginAs($user);
$client->request('GET', '/api/user/search?q=us02_other');
$this->assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertNotEmpty($data);
$emails = array_column($data, 'email');
$this->assertContains('us02_other@example.com', $emails);
// Own email should not appear
$this->assertNotContains('us02_me@example.com', $emails);
}
// US-03: updateTheme valid → 200, returns {theme}
public function test_update_theme_valid_returns_200(): void
{
$user = $this->createUser('us03@example.com');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/theme', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['theme' => 'warm-craft']));
$this->assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertSame('warm-craft', $data['theme']);
}
// US-04: updateTheme invalid → 422
public function test_update_theme_invalid_returns_422(): void
{
$user = $this->createUser('us04@example.com');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/theme', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['theme' => 'not-a-valid-theme']));
$this->assertResponseStatusCodeSame(422);
}
// US-05: updateTimezone valid → 200, returns {timezone}
public function test_update_timezone_valid_returns_200(): void
{
$user = $this->createUser('us05@example.com');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/timezone', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['timezone' => 'America/New_York']));
$this->assertResponseIsSuccessful();
$data = json_decode($client->getResponse()->getContent(), true);
$this->assertSame('America/New_York', $data['timezone']);
}
// US-06: updateTimezone missing/null body → 422
public function test_update_timezone_missing_returns_422(): void
{
$user = $this->createUser('us06@example.com');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/timezone', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['other' => 'value']));
$this->assertResponseStatusCodeSame(422);
}
// US-07: updateTimezone invalid string → 422
public function test_update_timezone_invalid_string_returns_422(): void
{
$user = $this->createUser('us07@example.com');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/timezone', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['timezone' => 'Not/A/Valid/Timezone']));
$this->assertResponseStatusCodeSame(422);
}
// US-08: unauthenticated → redirects to /login
public function test_unauthenticated_redirects_to_login(): void
{
$this->client->request('GET', '/api/user/search?q=test');
$this->assertResponseRedirects('/login');
}
// US-09: updatePassword valid → 204 and the new password works for re-login
public function test_update_password_valid_returns_204_and_rehashes(): void
{
$user = $this->createUser('us09@example.com', 'currentPass1');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/password', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['currentPassword' => 'currentPass1', 'newPassword' => 'brandNew2025']));
$this->assertResponseStatusCodeSame(204);
// Re-fetch the user; the stored hash must accept the new password and
// reject the old one.
$hasher = static::getContainer()->get('security.user_password_hasher');
$em = static::getContainer()->get('doctrine.orm.entity_manager');
$em->clear();
$reloaded = $em->getRepository(\App\Entity\User::class)->findOneBy(['email' => 'us09@example.com']);
$this->assertNotNull($reloaded);
$this->assertTrue($hasher->isPasswordValid($reloaded, 'brandNew2025'));
$this->assertFalse($hasher->isPasswordValid($reloaded, 'currentPass1'));
}
// US-10: updatePassword wrong current → 422
public function test_update_password_wrong_current_returns_422(): void
{
$user = $this->createUser('us10@example.com', 'rightPass1');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/password', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['currentPassword' => 'wrongPass1', 'newPassword' => 'brandNew2025']));
$this->assertResponseStatusCodeSame(422);
}
// US-11: updatePassword too-short new → 422
public function test_update_password_short_new_returns_422(): void
{
$user = $this->createUser('us11@example.com', 'currentPass1');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/password', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['currentPassword' => 'currentPass1', 'newPassword' => 'short']));
$this->assertResponseStatusCodeSame(422);
}
// US-12: updatePassword missing fields → 422
public function test_update_password_missing_fields_returns_422(): void
{
$user = $this->createUser('us12@example.com', 'currentPass1');
$client = $this->loginAs($user);
$client->request('PATCH', '/api/user/password', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode(['newPassword' => 'brandNew2025']));
$this->assertResponseStatusCodeSame(422);
}
}