Add PHPUnit integration tests, remove legacy pre-Symfony files, fix password reset
- Delete legacy root files (api.php, index.php, app.js, style.css, logo.png, include/)
- Install symfony/test-pack, add 34 integration tests covering auth, goals, invites, register, password reset
- Fix bug: users_resets.selector was varchar(20) but controller generates 24-char selectors; widen to varchar(64)
- Remove doctrine dbname_suffix from test env (tests run against live DB, cleanup via tearDown)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 08:18:21 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Tests;
|
|
|
|
|
|
|
|
|
|
use App\Entity\Goal;
|
|
|
|
|
use App\Entity\Invite;
|
|
|
|
|
use App\Entity\User;
|
|
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
|
|
|
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
|
|
|
|
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
|
|
|
|
|
|
|
|
|
class AppIntegrationTest extends WebTestCase
|
|
|
|
|
{
|
|
|
|
|
private EntityManagerInterface $em;
|
|
|
|
|
private KernelBrowser $client;
|
|
|
|
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
{
|
|
|
|
|
parent::setUp();
|
|
|
|
|
$this->client = static::createClient();
|
|
|
|
|
$this->em = static::getContainer()->get(EntityManagerInterface::class);
|
|
|
|
|
$this->cleanup();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function tearDown(): void
|
|
|
|
|
{
|
|
|
|
|
$this->cleanup();
|
|
|
|
|
parent::tearDown();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function cleanup(): void
|
|
|
|
|
{
|
|
|
|
|
$conn = $this->em->getConnection();
|
|
|
|
|
$conn->executeStatement("DELETE FROM goals WHERE user_id IN (SELECT id FROM users WHERE email LIKE '%@test.dudi')");
|
|
|
|
|
$conn->executeStatement("DELETE FROM invites WHERE created_by IN (SELECT id FROM users WHERE email LIKE '%@test.dudi')");
|
|
|
|
|
$conn->executeStatement("DELETE FROM users_resets WHERE user IN (SELECT id FROM users WHERE email LIKE '%@test.dudi')");
|
|
|
|
|
$conn->executeStatement("DELETE FROM users WHERE email LIKE '%@test.dudi'");
|
|
|
|
|
$this->em->clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function createUser(string $suffix = 'main', string $password = 'test1234'): User
|
|
|
|
|
{
|
|
|
|
|
$hasher = static::getContainer()->get(UserPasswordHasherInterface::class);
|
|
|
|
|
$user = new User();
|
|
|
|
|
$user->setEmail($suffix . '@test.dudi')
|
|
|
|
|
->setUsername('Tester ' . $suffix)
|
|
|
|
|
->setVerified(true);
|
|
|
|
|
$user->setPassword($hasher->hashPassword($user, $password));
|
|
|
|
|
$this->em->persist($user);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
return $user;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function authClient(User $user): KernelBrowser
|
|
|
|
|
{
|
|
|
|
|
$this->client->loginUser($user);
|
|
|
|
|
return $this->client;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function json(KernelBrowser $client, string $method, string $url, array $body = []): array
|
|
|
|
|
{
|
|
|
|
|
$client->request($method, $url, [], [], ['CONTENT_TYPE' => 'application/json'], $body ? json_encode($body) : null);
|
|
|
|
|
return json_decode($client->getResponse()->getContent(), true) ?? [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Auth ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testLoginSuccess(): void
|
|
|
|
|
{
|
|
|
|
|
$this->createUser('login', 'geheim99');
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/login', ['email' => 'login@test.dudi', 'password' => 'geheim99']);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->assertSame('login@test.dudi', $data['email']);
|
|
|
|
|
$this->assertSame('Tester login', $data['name']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testLoginWrongPassword(): void
|
|
|
|
|
{
|
|
|
|
|
$this->createUser('login2');
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/login', ['email' => 'login2@test.dudi', 'password' => 'falsch']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(401, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testLoginUnknownEmail(): void
|
|
|
|
|
{
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/login', ['email' => 'nobody@test.dudi', 'password' => 'test1234']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(401, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testMe(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('me');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/me');
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->assertSame('me@test.dudi', $data['email']);
|
|
|
|
|
$this->assertSame('Tester me', $data['name']);
|
|
|
|
|
$this->assertIsInt($data['id']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testMeUnauthenticated(): void
|
|
|
|
|
{
|
|
|
|
|
$this->json($this->client, 'GET', '/api/me');
|
|
|
|
|
$this->assertSame(401, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateName(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('name');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/me', ['name' => 'Neuer Name']);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->assertSame('Neuer Name', $data['name']);
|
|
|
|
|
|
|
|
|
|
$this->em->refresh($user);
|
|
|
|
|
$this->assertSame('Neuer Name', $user->getUsername());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateNameEmpty(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('nameempty');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/me', ['name' => '']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangePassword(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('pw', 'altesPasswort1');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/change-password', [
|
|
|
|
|
'old_password' => 'altesPasswort1',
|
|
|
|
|
'new_password' => 'neuesPasswort1',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
|
|
|
|
|
// verify new password works
|
|
|
|
|
$hasher = static::getContainer()->get(UserPasswordHasherInterface::class);
|
|
|
|
|
$this->em->refresh($user);
|
|
|
|
|
$this->assertTrue($hasher->isPasswordValid($user, 'neuesPasswort1'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangePasswordWrongOld(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('pwwrong');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/change-password', [
|
|
|
|
|
'old_password' => 'falsch1234',
|
|
|
|
|
'new_password' => 'neuesPasswort1',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(401, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testChangePasswordTooShort(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('pwshort');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/change-password', [
|
|
|
|
|
'old_password' => 'test1234',
|
|
|
|
|
'new_password' => 'kurz',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Password reset ────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testResetRequestAlwaysReturnsOk(): void
|
|
|
|
|
{
|
|
|
|
|
// unknown email — still returns ok (don't leak existence)
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/reset-request', ['email' => 'ghost@test.dudi']);
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testResetRequestWritesToken(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('reset');
|
|
|
|
|
$this->json($this->client, 'POST', '/api/reset-request', ['email' => 'reset@test.dudi']);
|
|
|
|
|
|
|
|
|
|
$row = $this->em->getConnection()->fetchAssociative(
|
|
|
|
|
'SELECT * FROM users_resets WHERE user = ?',
|
|
|
|
|
[$user->getId()]
|
|
|
|
|
);
|
|
|
|
|
$this->assertNotFalse($row);
|
|
|
|
|
$this->assertGreaterThan(time(), $row['expires']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testResetPassword(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('resetpw');
|
|
|
|
|
|
|
|
|
|
$selector = bin2hex(random_bytes(12));
|
|
|
|
|
$token = bin2hex(random_bytes(32));
|
|
|
|
|
$hash = password_hash($token, PASSWORD_BCRYPT);
|
|
|
|
|
$this->em->getConnection()->executeStatement(
|
|
|
|
|
'INSERT INTO users_resets (user, selector, token, expires) VALUES (?, ?, ?, ?)',
|
|
|
|
|
[$user->getId(), $selector, $hash, time() + 3600]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/reset-password', [
|
|
|
|
|
'selector' => $selector,
|
|
|
|
|
'token' => $token,
|
|
|
|
|
'password' => 'neuesPasswort99',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
|
|
|
|
|
$hasher = static::getContainer()->get(UserPasswordHasherInterface::class);
|
|
|
|
|
$this->em->refresh($user);
|
|
|
|
|
$this->assertTrue($hasher->isPasswordValid($user, 'neuesPasswort99'));
|
|
|
|
|
|
|
|
|
|
// token must be deleted after use
|
|
|
|
|
$row = $this->em->getConnection()->fetchAssociative(
|
|
|
|
|
'SELECT * FROM users_resets WHERE selector = ?', [$selector]
|
|
|
|
|
);
|
|
|
|
|
$this->assertFalse($row);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testResetPasswordExpiredToken(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('resetexp');
|
|
|
|
|
$selector = bin2hex(random_bytes(12));
|
|
|
|
|
$token = bin2hex(random_bytes(32));
|
|
|
|
|
$this->em->getConnection()->executeStatement(
|
|
|
|
|
'INSERT INTO users_resets (user, selector, token, expires) VALUES (?, ?, ?, ?)',
|
|
|
|
|
[$user->getId(), $selector, password_hash($token, PASSWORD_BCRYPT), time() - 1]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/reset-password', [
|
|
|
|
|
'selector' => $selector,
|
|
|
|
|
'token' => $token,
|
|
|
|
|
'password' => 'neuesPasswort99',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Goals ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testGoalListEmpty(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goallist');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/goals');
|
|
|
|
|
|
|
|
|
|
$this->assertSame([], $data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalCreate(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalcreate');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/goals', [
|
|
|
|
|
'name' => 'Liegestütz',
|
|
|
|
|
'unit' => 'Stück',
|
|
|
|
|
'daily' => 50,
|
|
|
|
|
'days' => 30,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertSame('Liegestütz', $data['name']);
|
|
|
|
|
$this->assertSame('Stück', $data['unit']);
|
|
|
|
|
$this->assertEquals(50.0, $data['daily']);
|
|
|
|
|
$this->assertSame(30, $data['days']);
|
|
|
|
|
$this->assertSame([], $data['sets']);
|
|
|
|
|
$this->assertIsString($data['id']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalCreateMissingName(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalnoname');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/goals', ['unit' => 'Stück', 'daily' => 10, 'days' => 7]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalDefaultUnit(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalunit');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/goals', ['name' => 'Plank', 'daily' => 1, 'days' => 7]);
|
|
|
|
|
|
|
|
|
|
$this->assertSame('Stück', $data['unit']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalListReturnsOwned(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalowned');
|
|
|
|
|
$other = $this->createUser('goalother');
|
|
|
|
|
|
|
|
|
|
foreach ([$user, $other] as $u) {
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($u->getId())->setName('Ziel')->setUnit('Stück')->setDaily(1)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
}
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/goals');
|
|
|
|
|
|
|
|
|
|
$this->assertCount(1, $data);
|
|
|
|
|
$this->assertSame('Ziel', $data[0]['name']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalUpdate(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalupdate');
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($user->getId())->setName('Alt')->setUnit('Stück')->setDaily(10)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/goals/' . $goal->getId(), [
|
|
|
|
|
'name' => 'Neu',
|
|
|
|
|
'unit' => 'Min',
|
|
|
|
|
'daily' => 20,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->em->refresh($goal);
|
|
|
|
|
$this->assertSame('Neu', $goal->getName());
|
|
|
|
|
$this->assertSame('Min', $goal->getUnit());
|
|
|
|
|
$this->assertSame(20.0, $goal->getDaily());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalUpdateSets(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalsets');
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($user->getId())->setName('Test')->setUnit('Stück')->setDaily(50)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$today = date('Y-m-d');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/goals/' . $goal->getId(), [
|
|
|
|
|
'sets' => [$today => [20, 30]],
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->em->refresh($goal);
|
|
|
|
|
$this->assertSame([20, 30], $goal->getSets()[$today]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalUpdateNotFound(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalnotfound');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/goals/999999', ['name' => 'X']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(404, $client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalUpdateOwnership(): void
|
|
|
|
|
{
|
|
|
|
|
$owner = $this->createUser('goalown1');
|
|
|
|
|
$other = $this->createUser('goalown2');
|
|
|
|
|
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($owner->getId())->setName('Privat')->setUnit('Stück')->setDaily(1)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($other);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/goals/' . $goal->getId(), ['name' => 'Gehackt']);
|
|
|
|
|
|
|
|
|
|
$this->assertSame(404, $client->getResponse()->getStatusCode());
|
|
|
|
|
$this->em->refresh($goal);
|
|
|
|
|
$this->assertSame('Privat', $goal->getName());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalDelete(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goaldel');
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($user->getId())->setName('Weg')->setUnit('Stück')->setDaily(1)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
$id = $goal->getId();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'DELETE', '/api/goals/' . $id);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->assertNull($this->em->find(Goal::class, $id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalDeleteOwnership(): void
|
|
|
|
|
{
|
|
|
|
|
$owner = $this->createUser('goaldel1');
|
|
|
|
|
$other = $this->createUser('goaldel2');
|
|
|
|
|
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($owner->getId())->setName('Privat')->setUnit('Stück')->setDaily(1)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
$id = $goal->getId();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($other);
|
|
|
|
|
$this->json($client, 'DELETE', '/api/goals/' . $id);
|
|
|
|
|
|
|
|
|
|
$this->assertNotNull($this->em->find(Goal::class, $id));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalUnauthenticated(): void
|
|
|
|
|
{
|
|
|
|
|
$this->json($this->client, 'GET', '/api/goals');
|
|
|
|
|
$this->assertSame(401, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Invites ───────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testInviteCreate(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invcreate');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/invite', ['note' => 'Für Max']);
|
|
|
|
|
|
|
|
|
|
$this->assertStringContainsString('invite=', $data['url']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testInviteList(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invlist');
|
|
|
|
|
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setNote('Testeinladung')
|
|
|
|
|
->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/invites');
|
|
|
|
|
|
|
|
|
|
$this->assertCount(1, $data);
|
|
|
|
|
$this->assertSame('pending', $data[0]['status']);
|
|
|
|
|
$this->assertSame('Testeinladung', $data[0]['note']);
|
|
|
|
|
$this->assertStringContainsString('invite=', $data[0]['url']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testInviteListExpired(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invexp');
|
|
|
|
|
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('-1 day'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/invites');
|
|
|
|
|
|
|
|
|
|
$this->assertSame('expired', $data[0]['status']);
|
|
|
|
|
$this->assertNull($data[0]['url']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testInviteListUsed(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invused1');
|
|
|
|
|
$newUser = $this->createUser('invused2');
|
|
|
|
|
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'))
|
|
|
|
|
->setUsedBy($newUser->getId())
|
|
|
|
|
->setUsedAt(new \DateTimeImmutable());
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/invites');
|
|
|
|
|
|
|
|
|
|
$this->assertSame('used', $data[0]['status']);
|
|
|
|
|
$this->assertSame('invused2@test.dudi', $data[0]['used_by_email']);
|
|
|
|
|
$this->assertNull($data[0]['url']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Register ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testRegisterWithInvite(): void
|
|
|
|
|
{
|
|
|
|
|
$creator = $this->createUser('reginviter');
|
|
|
|
|
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($creator->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/register', [
|
|
|
|
|
'email' => 'regnew@test.dudi',
|
|
|
|
|
'password' => 'passwort99',
|
|
|
|
|
'name' => 'Neuer User',
|
|
|
|
|
'token' => $invite->getToken(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->assertSame('regnew@test.dudi', $data['email']);
|
|
|
|
|
|
|
|
|
|
$newUser = static::getContainer()->get(\App\Repository\UserRepository::class)->findOneBy(['email' => 'regnew@test.dudi']);
|
|
|
|
|
$this->assertNotNull($newUser);
|
|
|
|
|
$this->assertTrue($newUser->isVerified());
|
|
|
|
|
|
|
|
|
|
$this->em->refresh($invite);
|
|
|
|
|
$this->assertSame($newUser->getId(), $invite->getUsedBy());
|
|
|
|
|
$this->assertNotNull($invite->getUsedAt());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRegisterInvalidToken(): void
|
|
|
|
|
{
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/register', [
|
|
|
|
|
'email' => 'regbad@test.dudi',
|
|
|
|
|
'password' => 'passwort99',
|
|
|
|
|
'token' => 'ungueltig',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRegisterDuplicateEmail(): void
|
|
|
|
|
{
|
|
|
|
|
$existing = $this->createUser('regdup');
|
|
|
|
|
|
|
|
|
|
$creator = $this->createUser('regdupinviter');
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($creator->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/register', [
|
|
|
|
|
'email' => 'regdup@test.dudi',
|
|
|
|
|
'password' => 'passwort99',
|
|
|
|
|
'token' => $invite->getToken(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(409, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRegisterPasswordTooShort(): void
|
|
|
|
|
{
|
|
|
|
|
$creator = $this->createUser('regshortinviter');
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($creator->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/register', [
|
|
|
|
|
'email' => 'regshort@test.dudi',
|
|
|
|
|
'password' => 'kurz',
|
|
|
|
|
'token' => $invite->getToken(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
2026-05-01 07:58:21 +00:00
|
|
|
|
|
|
|
|
public function testRegisterAutoLogsIn(): void
|
|
|
|
|
{
|
|
|
|
|
$creator = $this->createUser('regautologin');
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($creator->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$this->json($this->client, 'POST', '/api/register', [
|
|
|
|
|
'email' => 'regauto@test.dudi',
|
|
|
|
|
'password' => 'passwort99',
|
|
|
|
|
'name' => 'Auto User',
|
|
|
|
|
'token' => $invite->getToken(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$me = $this->json($this->client, 'GET', '/api/me');
|
|
|
|
|
$this->assertTrue($me['ok']);
|
|
|
|
|
$this->assertSame('regauto@test.dudi', $me['email']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testRegisterUsedTokenRejected(): void
|
|
|
|
|
{
|
|
|
|
|
$creator = $this->createUser('regusedtkinviter');
|
|
|
|
|
$existing = $this->createUser('regusedtkuser');
|
|
|
|
|
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($creator->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'))
|
|
|
|
|
->setUsedBy($existing->getId())
|
|
|
|
|
->setUsedAt(new \DateTimeImmutable());
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/register', [
|
|
|
|
|
'email' => 'regusedtk@test.dudi',
|
|
|
|
|
'password' => 'passwort99',
|
|
|
|
|
'token' => $invite->getToken(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Locale ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testUpdateLocale(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('locale');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
|
|
|
|
|
foreach (['en', 'pl', 'de'] as $locale) {
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/me', ['locale' => $locale]);
|
|
|
|
|
$this->assertTrue($data['ok'], "ok missing for locale $locale");
|
|
|
|
|
$this->assertSame($locale, $data['locale']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->em->clear();
|
|
|
|
|
$user = $this->em->find(User::class, $user->getId());
|
|
|
|
|
$this->assertSame('de', $user->getLocale());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testUpdateLocaleInvalid(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('localeinvalid');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/me', ['locale' => 'fr']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Logout ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testLogout(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('logout');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
|
|
|
|
|
$me = $this->json($client, 'GET', '/api/me');
|
|
|
|
|
$this->assertTrue($me['ok']);
|
|
|
|
|
|
|
|
|
|
$this->json($client, 'POST', '/api/logout');
|
|
|
|
|
|
|
|
|
|
$this->json($client, 'GET', '/api/me');
|
|
|
|
|
$this->assertSame(401, $client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Name validation ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testUpdateNameWhitespaceOnlyIsRejected(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('namewhitespace');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/me', ['name' => ' ']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Password reset (additional) ────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testResetPasswordWrongToken(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('resetwrongtk');
|
|
|
|
|
$selector = bin2hex(random_bytes(12));
|
|
|
|
|
$token = bin2hex(random_bytes(32));
|
|
|
|
|
$this->em->getConnection()->executeStatement(
|
|
|
|
|
'INSERT INTO users_resets (user, selector, token, expires) VALUES (?, ?, ?, ?)',
|
|
|
|
|
[$user->getId(), $selector, password_hash($token, PASSWORD_BCRYPT), time() + 3600]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$data = $this->json($this->client, 'POST', '/api/reset-password', [
|
|
|
|
|
'selector' => $selector,
|
|
|
|
|
'token' => 'falschertoken123',
|
|
|
|
|
'password' => 'neuesPasswort99',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Invites (additional) ───────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testInviteCreateWithoutNote(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invnonote');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/invite', []);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('url', $data);
|
|
|
|
|
$this->assertStringContainsString('invite=', $data['url']);
|
|
|
|
|
|
|
|
|
|
$invites = $this->em->getRepository(Invite::class)->findBy(['createdBy' => $user->getId()]);
|
|
|
|
|
$this->assertCount(1, $invites);
|
|
|
|
|
$this->assertNull($invites[0]->getNote());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 08:22:16 +00:00
|
|
|
public function testInviteMaxTenPending(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invmax');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
|
|
|
|
|
for ($i = 0; $i < 10; $i++) {
|
|
|
|
|
$inv = new Invite();
|
|
|
|
|
$inv->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($inv);
|
|
|
|
|
}
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/invite', ['note' => 'over limit']);
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(400, $client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testInviteListSortOrder(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invsort');
|
|
|
|
|
$usedBy = $this->createUser('invsortu');
|
|
|
|
|
|
|
|
|
|
$pending = new Invite();
|
|
|
|
|
$pending->setToken(bin2hex(random_bytes(32)))->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
|
|
|
|
|
$expired = new Invite();
|
|
|
|
|
$expired->setToken(bin2hex(random_bytes(32)))->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('-1 day'));
|
|
|
|
|
|
|
|
|
|
$used = new Invite();
|
|
|
|
|
$used->setToken(bin2hex(random_bytes(32)))->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'))
|
|
|
|
|
->setUsedBy($usedBy->getId())->setUsedAt(new \DateTimeImmutable());
|
|
|
|
|
|
|
|
|
|
$this->em->persist($expired);
|
|
|
|
|
$this->em->persist($used);
|
|
|
|
|
$this->em->persist($pending);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/invites');
|
|
|
|
|
|
|
|
|
|
$this->assertSame('pending', $data[0]['status']);
|
|
|
|
|
$this->assertSame('used', $data[1]['status']);
|
|
|
|
|
$this->assertSame('expired', $data[2]['status']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testInviteExpiredHiddenAfter30Days(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('invexpold');
|
|
|
|
|
|
|
|
|
|
$old = new Invite();
|
|
|
|
|
$old->setToken(bin2hex(random_bytes(32)))->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('-31 days'));
|
|
|
|
|
|
|
|
|
|
$recent = new Invite();
|
|
|
|
|
$recent->setToken(bin2hex(random_bytes(32)))->setCreatedBy($user->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('-5 days'));
|
|
|
|
|
|
|
|
|
|
$this->em->persist($old);
|
|
|
|
|
$this->em->persist($recent);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/invites');
|
|
|
|
|
|
|
|
|
|
$this->assertCount(1, $data);
|
|
|
|
|
$this->assertSame('expired', $data[0]['status']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 07:58:21 +00:00
|
|
|
public function testInviteListIsolatedFromOtherUsers(): void
|
|
|
|
|
{
|
|
|
|
|
$user1 = $this->createUser('inviso1');
|
|
|
|
|
$user2 = $this->createUser('inviso2');
|
|
|
|
|
|
|
|
|
|
$invite = new Invite();
|
|
|
|
|
$invite->setToken(bin2hex(random_bytes(32)))
|
|
|
|
|
->setCreatedBy($user1->getId())
|
|
|
|
|
->setExpiresAt(new \DateTimeImmutable('+7 days'));
|
|
|
|
|
$this->em->persist($invite);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user2);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/invites');
|
|
|
|
|
|
|
|
|
|
$this->assertSame([], $data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Goals (additional) ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testGoalPartialUpdateOnlyName(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalpartial');
|
|
|
|
|
$goal = new Goal();
|
|
|
|
|
$goal->setUserId($user->getId())->setName('Alt')->setUnit('Min')->setDaily(10.0)->setDays(7)->setStart(new \DateTime())->setSets([]);
|
|
|
|
|
$this->em->persist($goal);
|
|
|
|
|
$this->em->flush();
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'PATCH', '/api/goals/' . $goal->getId(), ['name' => 'Neu']);
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->em->refresh($goal);
|
|
|
|
|
$this->assertSame('Neu', $goal->getName());
|
|
|
|
|
$this->assertSame('Min', $goal->getUnit());
|
|
|
|
|
$this->assertSame(10.0, $goal->getDaily());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testGoalCreateEmptyUnitFallsBackToDefault(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goalemptyunit');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'POST', '/api/goals', [
|
|
|
|
|
'name' => 'Plank',
|
|
|
|
|
'unit' => '',
|
|
|
|
|
'daily' => 1,
|
|
|
|
|
'days' => 7,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$this->assertSame('Stück', $data['unit']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 08:11:57 +00:00
|
|
|
// ── Admin ─────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
public function testAdminUsersUnauthenticated(): void
|
|
|
|
|
{
|
|
|
|
|
$this->json($this->client, 'GET', '/api/admin/users');
|
|
|
|
|
$this->assertSame(401, $this->client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testAdminUsersNonAdminForbidden(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('adminblocked');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/admin/users');
|
|
|
|
|
|
|
|
|
|
$this->assertArrayHasKey('error', $data);
|
|
|
|
|
$this->assertSame(403, $client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function testAdminUsersReturnsAllUsers(): void
|
|
|
|
|
{
|
|
|
|
|
$adminEmail = $_ENV['ADMIN_EMAIL'] ?? '';
|
|
|
|
|
if (!$adminEmail) {
|
|
|
|
|
$this->markTestSkipped('ADMIN_EMAIL not configured');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$admin = $this->em->getRepository(User::class)->findOneBy(['email' => $adminEmail]);
|
|
|
|
|
if (!$admin) {
|
|
|
|
|
$this->markTestSkipped("Admin user $adminEmail not found in DB");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->createUser('adminlistother');
|
|
|
|
|
|
|
|
|
|
$client = $this->authClient($admin);
|
|
|
|
|
$data = $this->json($client, 'GET', '/api/admin/users');
|
|
|
|
|
|
|
|
|
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
|
|
|
|
$this->assertIsArray($data);
|
|
|
|
|
$emails = array_column($data, 'email');
|
|
|
|
|
$this->assertContains($adminEmail, $emails);
|
|
|
|
|
$this->assertContains('adminlistother@test.dudi', $emails);
|
|
|
|
|
foreach ($data as $row) {
|
|
|
|
|
$this->assertArrayHasKey('email', $row);
|
|
|
|
|
$this->assertArrayHasKey('username', $row);
|
|
|
|
|
$this->assertArrayHasKey('registered', $row);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 07:58:21 +00:00
|
|
|
public function testGoalDeleteReturnsOkForNonExistentGoal(): void
|
|
|
|
|
{
|
|
|
|
|
$user = $this->createUser('goaldelmissing');
|
|
|
|
|
$client = $this->authClient($user);
|
|
|
|
|
$data = $this->json($client, 'DELETE', '/api/goals/999999');
|
|
|
|
|
|
|
|
|
|
$this->assertTrue($data['ok']);
|
|
|
|
|
$this->assertSame(200, $client->getResponse()->getStatusCode());
|
|
|
|
|
}
|
Add PHPUnit integration tests, remove legacy pre-Symfony files, fix password reset
- Delete legacy root files (api.php, index.php, app.js, style.css, logo.png, include/)
- Install symfony/test-pack, add 34 integration tests covering auth, goals, invites, register, password reset
- Fix bug: users_resets.selector was varchar(20) but controller generates 24-char selectors; widen to varchar(64)
- Remove doctrine dbname_suffix from test env (tests run against live DB, cleanup via tearDown)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 08:18:21 +00:00
|
|
|
}
|