initializeFlow(); } $this->objectManager = self::$bootstrap->getObjectManager(); $this->graphQLEndpointUrl = $graphQLEndpointUrl ?? 'http://localhost'; $this->testingDBSuffix = $testingDBSuffix ?? '_testing'; /** @var EntityManagerInterface $entityManager */ $entityManager = $this->objectManager->get(EntityManagerInterface::class); $this->dbal = $entityManager->getConnection(); /** @noinspection PhpFieldAssignmentTypeMismatchInspection */ $this->fakeEmailService = $this->objectManager->get(FakeEmailService::class); /** @noinspection PhpFieldAssignmentTypeMismatchInspection */ $this->securityContext = $this->objectManager->get(SecurityContext::class); } /** * @BeforeScenario */ public function resetData(): void { if (!str_ends_with($this->dbal->getDatabase(), $this->testingDBSuffix)) { throw new \RuntimeException(sprintf('Testing database name has to be suffixed with "%s" to prevent data loss. But the current database name is "%s".', $this->testingDBSuffix, $this->dbal->getDatabase()), 1630596717); } // TODO reset your event stores & write/read model states } /** * @AfterScenario */ public function throwGraphQLException(): void { $this->failIfLastGraphQLResponseHasErrors(); } /** * @When I send the following GraphQL query with authorization token :authorizationToken: */ public function iSendTheFollowingGraphQLQueryWithAuthorizationToken(string $authorizationToken, PyStringNode $query): void { $this->sendGraphQLQuery($query->getRaw(), $this->replaceVariables($authorizationToken)); } /** * @When I send the following GraphQL query: */ public function iSendTheFollowingGraphQLQuery(PyStringNode $query): void { $this->sendGraphQLQuery($query->getRaw()); } /** * @When I remember the GraphQL response at :responsePath as :variableName */ public function iRememberTheGraphQLResponseAtAs(string $responsePath, string $variableName): void { $this->failIfLastGraphQLResponseHasErrors(); self::$variables[$variableName] = ObjectAccess::getPropertyPath($this->lastGraphQLResponse->toDataArray(), $responsePath); } /** * @When I expect the GraphQL response to contain an error with code :expectedErrorCode */ public function iExpectTheGraphQLResponseToContainAnErrorWithCode(int $expectedErrorCode): void { Assert::assertContains($expectedErrorCode, $this->lastGraphQLResponse->errorCodes(), sprintf('Expected last GraphQL response to contain error with code "%s", but it didn\'t: %s', $expectedErrorCode, $this->lastGraphQLResponse)); $this->handledLastGraphQLError = true; } /** * @When I remember the claim :claim of JWT :jwt as :variableName */ public function iRememberTheClaimOfJwtAs(string $claim, string $jwt, string $variableName): void { $tks = \explode('.', $this->replaceVariables($jwt)); if (\count($tks) !== 3) { throw new \RuntimeException(sprintf('Failed to parse JWT "%s": Invalid number of segments', $jwt), 1630513785); } $payload = (array)JWT::jsonDecode(JWT::urlsafeB64Decode($tks[1])); if (!array_key_exists($claim, $payload)) { throw new \RuntimeException(sprintf('JWT "%s" does not contain a claim "%s"', $jwt, $claim), 1630513938); } self::$variables[$variableName] = $payload[$claim]; } /** * @When I wait for account with id :accountId to exist */ public function iWaitForAccountWithIdToExist(string $accountId): void { $accountId = $this->replaceVariables($accountId); $this->retry(function (bool &$cancel) use ($accountId) { $result = $this->dbal->fetchOne('SELECT persistence_object_identifier FROM neos_flow_security_account WHERE persistence_object_identifier = :accountId', ['accountId' => $accountId]); if ($result !== false) { $cancel = true; } return $result; }, 50, 100); } /** * @Then I expect the following GraphQL response: */ public function iExpectTheFollowingGraphQLResponse(PyStringNode $string) { try { $expectedResponse = json_decode($string->getRaw(), true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $e) { throw new \InvalidArgumentException(sprintf('Failed to JSON decode expected GraphQL response: %s. %s', $e->getMessage(), $string->getRaw()), 1631036320, $e); } $expectedResponse = $this->replaceVariablesInArray($expectedResponse); Assert::assertSame($expectedResponse, $this->lastGraphQLResponse->toDataArray()); } /** * @Then I expect an array GraphQL response at :propertyPath that contains: */ public function iExpectAGraphQLResponseAtThatContains($propertyPath, PyStringNode $string) { $array = ObjectAccess::getPropertyPath($this->lastGraphQLResponse->toDataArray(), $propertyPath); try { $expectedResponse = json_decode($string->getRaw(), true, 512, JSON_THROW_ON_ERROR); } catch (\JsonException $e) { throw new \InvalidArgumentException(sprintf('Failed to JSON decode expected GraphQL response: %s. %s', $e->getMessage(), $string->getRaw()), 1631117527, $e); } $expectedResponse = $this->replaceVariablesInArray($expectedResponse); Assert::assertContains($expectedResponse, $array, "Item should be in result\n" . $this->lastGraphQLResponse); } // --------------------------------- private function retry(\Closure $callback, int $maxAttempts, int $retryIntervalInMilliseconds) { $attempts = 1; $cancel = false; do { $result = $callback($cancel); if ($cancel) { $this->printDebug(sprintf('Success after %d attempt(s)', $attempts)); return $result; } usleep($retryIntervalInMilliseconds * 1000); } while (++$attempts <= $maxAttempts); throw new \RuntimeException(sprintf('Failed after %d attempts', $maxAttempts), 1630747953); } private function sendGraphQLQuery(string $query, ?string $authorizationToken = null): void { if ($this->lastGraphQLResponse !== null && $this->lastGraphQLResponse->hasErrors()) { Assert::fail(sprintf('Previous GraphQL Response contained unhandled errors: %s', $this->lastGraphQLResponse)); } $this->handledLastGraphQLError = false; $data = ['query' => $this->replaceVariables($query), 'variables' => []]; try { $body = json_encode($data, JSON_THROW_ON_ERROR); } catch (\JsonException $e) { throw new \InvalidArgumentException(sprintf('Failed to JSON encode GraphQL body: %s', $e->getMessage()), 1630511632, $e); } $headers = [ 'Content-Type' => 'application/json' ]; if ($authorizationToken !== null) { $headers['Authorization'] = 'Bearer ' . $authorizationToken; } $request = new ServerRequest('POST', new Uri($this->graphQLEndpointUrl), $headers, $body); $client = new Client(); $response = $client->send($request, ['http_errors' => false]); $this->lastGraphQLResponse = GraphQLResponse::fromResponseBody($response->getBody()->getContents()); } private function failIfLastGraphQLResponseHasErrors(): void { if (!$this->handledLastGraphQLError && $this->lastGraphQLResponse !== null && $this->lastGraphQLResponse->hasErrors()) { Assert::fail(sprintf('Last GraphQL query produced an error "%s" (code: %s)', $this->lastGraphQLResponse->firstErrorMessage(), $this->lastGraphQLResponse->firstErrorCode())); } } /** * @param TableNode $table * @return array */ private function parseJsonTable(TableNode $table): array { return array_map(static function (array $row) { return array_map(static function (string $jsonValue) { if (strncmp($jsonValue, 'date:', 5) === 0) { try { $decodedValue = new DateTime(substr($jsonValue, 5)); } catch (Exception $e) { throw new RuntimeException(sprintf('Failed to decode json value "%s" to DateTime instance: %s', substr($jsonValue, 5), $e->getMessage()), 1636021305, $e); } } else { try { $decodedValue = json_decode($jsonValue, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new \InvalidArgumentException(sprintf('Failed to decode JSON value %s: %s', $jsonValue, $e->getMessage()), 1636021310, $e); } } return $decodedValue; }, $row); }, $table->getHash()); } /** * @param string $string * @return string the original $string with replaced */ private function replaceVariables(string $string): string { return preg_replace_callback('/<([\w\.]+)>/', static function ($matches) { $variableName = $matches[1]; $result = ObjectAccess::getPropertyPath(self::$variables, $variableName); if ($result === null) { throw new \InvalidArgumentException(sprintf('Variable "%s" is not defined!', $variableName), 1630508048); } return $result; }, $string); } private function replaceVariablesInArray(array $array): array { foreach ($array as &$value) { if (is_array($value)) { $value = $this->replaceVariablesInArray($value); } elseif (is_string($value)) { $value = $this->replaceVariables($value); } } return $array; } }