client = static::createClient(); $this->em = static::getContainer()->get(EntityManagerInterface::class); $this->ensureFreePlan(); } /** * The «free» plan is SubscriptionService::getEffectivePlan's fallback for any * tenant without a paid subscription. Without it every hasFeature() is false * and the patient / service / insurance endpoints answer 403 instead of doing * their job. db_test is never reset, so the insert is idempotent. * * Mirrors the row shipped in the dev database. */ private function ensureFreePlan(): void { $repo = $this->em->getRepository(SubscriptionPlan::class); if ($repo->findOneBy(['name' => 'free']) !== null) { return; } $this->em->persist(new SubscriptionPlan('free', 0, 1, [ 'patient_records' => true, 'services' => true, 'sms_panel' => true, 'insurance' => true, ])); $this->em->flush(); } /** * Persist a user with the given roles. Mobile is randomised per test to * avoid unique-constraint clashes across cases without a full DB reset. */ protected function createUser(array $roles = ['ROLE_USER'], ?string $mobile = null): User { // 9 random digits after 09 (full ^09\d{9}$ space) — db_test is never reset, // so a narrower space eventually collides on the unique mobile. $mobile ??= '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT); $user = new User($mobile); $user->setRoles($roles); $user->setStatus(1); $this->em->persist($user); $this->em->flush(); return $user; } protected function jwtFor(User $user): string { return static::getContainer() ->get(JWTTokenManagerInterface::class) ->create($user); } /** * Issue an authenticated JSON request and return the decoded response body. */ protected function authJson(string $method, string $uri, User $user, array $body = []): array { $this->client->request( $method, $uri, server: [ 'HTTP_AUTHORIZATION' => 'Bearer ' . $this->jwtFor($user), 'CONTENT_TYPE' => 'application/json', ], content: $body ? json_encode($body) : null, ); return json_decode($this->client->getResponse()->getContent(), true) ?? []; } protected function responseCode(): int { return $this->client->getResponse()->getStatusCode(); } /** * Count the SQL queries executed while running $fn. Used to assert that a * list endpoint's query count does not grow with the number of rows (N+1). */ protected function countQueries(callable $fn): int { $holder = static::getContainer()->get('doctrine.debug_data_holder'); $holder->reset(); $fn(); return array_sum(array_map('count', $holder->getData())); } }