get() and assert on the response. */ abstract class AsyncTest extends AsyncTestCase { protected HttpClient $httpClient; protected string $baseUrl; protected App $app; protected function setUp(): void { parent::setUp(); $this->httpClient = (new HttpClientBuilder())->followRedirects(0)->build(); $this->baseUrl = 'http://127.0.0.1:' . ($_ENV['HTTP_PORT'] ?? 8090); $this->app = App::getInstance(); } /** * Send a GET request to the running test server. * * Query building preserves repeated keys like `exclude[group]=a&exclude[group]=b`. * Pass an array value to repeat the key: ['exclude[group]' => ['casino', 'porn']]. * * @param array> $query * @throws HttpException */ protected function get(string $path = '/', array $query = []): Response { return $this->httpClient->request(new Request($this->buildUrl($path, $query), 'GET')); } /** * @param array> $query */ protected function buildUrl(string $path, array $query): string { $url = $this->baseUrl . $path; if (!$query) { return $url; } $parts = []; foreach ($query as $name => $value) { foreach (is_array($value) ? $value : [$value] as $item) { $parts[] = rawurlencode($name) . '=' . rawurlencode((string) $item); } } return $url . '?' . implode('&', $parts); } /** * Buffer and return the response body as a string. * * @throws StreamException */ protected function body(Response $response): string { return $response->getBody()->buffer(); } protected function service(): IPListService { return IPListService::getInstance(); } }