-
-
Notifications
You must be signed in to change notification settings - Fork 452
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5fb16a5
commit 43c6770
Showing
4 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sentry\Tests\HttpClient; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Sentry\HttpClient\HttpClient; | ||
use Sentry\HttpClient\Request; | ||
use Sentry\Options; | ||
use Sentry\Util\Http; | ||
|
||
class HttpClientTest extends TestCase | ||
{ | ||
use TestServer; | ||
|
||
public function testClientMakesRequestWithCorrectHeadersMethodAndPath(): void | ||
{ | ||
$testServer = $this->startTestServer(); | ||
|
||
$options = new Options([ | ||
'dsn' => "http://publicKey@{$testServer}/200", | ||
]); | ||
|
||
$request = new Request(); | ||
$request->setStringBody('test'); | ||
|
||
$client = new HttpClient($sdkIdentifier = 'sentry.php', $sdkVersion = 'testing'); | ||
$response = $client->sendRequest($request, $options); | ||
|
||
$serverOutput = $this->stopTestServer(); | ||
|
||
$this->assertTrue($response->isSuccess()); | ||
$this->assertEquals(200, $response->getStatusCode()); | ||
|
||
// This assertion is here to test that the response headers are correctly parsed | ||
$this->assertEquals(200, (int) $response->getHeaderLine('x-sentry-test-server-status-code')); | ||
|
||
$this->assertTrue($serverOutput['compressed']); | ||
$this->assertEquals($response->getStatusCode(), $serverOutput['status']); | ||
$this->assertEquals($request->getStringBody(), $serverOutput['body']); | ||
$this->assertEquals('/api/200/envelope/', $serverOutput['server']['REQUEST_URI']); | ||
$this->assertEquals("{$sdkIdentifier}/{$sdkVersion}", $serverOutput['headers']['User-Agent']); | ||
|
||
$expectedHeaders = Http::getRequestHeaders($options->getDsn(), $sdkIdentifier, $sdkVersion); | ||
foreach ($expectedHeaders as $expectedHeader) { | ||
[$headerName, $headerValue] = explode(': ', $expectedHeader); | ||
$this->assertEquals($headerValue, $serverOutput['headers'][$headerName]); | ||
} | ||
} | ||
|
||
public function testClientMakesUncompressedRequestWhenCompressionDisabled(): void | ||
{ | ||
$testServer = $this->startTestServer(); | ||
|
||
$options = new Options([ | ||
'dsn' => "http://publicKey@{$testServer}/200", | ||
'http_compression' => false, | ||
]); | ||
|
||
$request = new Request(); | ||
$request->setStringBody('test'); | ||
|
||
$client = new HttpClient('sentry.php', 'testing'); | ||
$response = $client->sendRequest($request, $options); | ||
|
||
$serverOutput = $this->stopTestServer(); | ||
|
||
$this->assertTrue($response->isSuccess()); | ||
$this->assertFalse($serverOutput['compressed']); | ||
$this->assertEquals(200, $response->getStatusCode()); | ||
$this->assertEquals($response->getStatusCode(), $serverOutput['status']); | ||
$this->assertEquals($request->getStringBody(), $serverOutput['body']); | ||
$this->assertEquals(\strlen($request->getStringBody()), $serverOutput['headers']['Content-Length']); | ||
} | ||
|
||
public function testThrowsExceptionIfDsnOptionIsNotSet(): void | ||
{ | ||
$this->expectException(\RuntimeException::class); | ||
$this->expectExceptionMessage('The DSN option must be set to use the HttpClient.'); | ||
|
||
$options = new Options(['dsn' => null]); | ||
|
||
$client = new HttpClient('sentry.php', 'testing'); | ||
$client->sendRequest(new Request(), $options); | ||
} | ||
|
||
public function testThrowsExceptionIfRequestDataIsEmpty(): void | ||
{ | ||
$this->expectException(\RuntimeException::class); | ||
$this->expectExceptionMessage('The request data is empty.'); | ||
|
||
$options = new Options(['dsn' => 'https://publicKey@example.com/1']); | ||
|
||
$client = new HttpClient('sentry.php', 'testing'); | ||
$client->sendRequest(new Request(), $options); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sentry\Tests\HttpClient; | ||
|
||
/** | ||
* This is a test server that can be used to test the HttpClient. | ||
* | ||
* It spawns the PHP development server, captures the output and returns it to the caller. | ||
* | ||
* In your test call `$this->startTestServer()` to start the server and get the address. | ||
* After you have made your request, call `$this->stopTestServer()` to stop the server and get the output. | ||
* | ||
* Thanks to Stripe for the inspiration: https://github.com/stripe/stripe-php/blob/e0a960c8655b21b21c3ba2e5927f432eeda9105f/tests/TestServer.php | ||
*/ | ||
trait TestServer | ||
{ | ||
/** | ||
* @var string the path to the output file | ||
*/ | ||
protected static $serverOutputFile = __DIR__ . '/../testserver/output.json'; | ||
|
||
/** | ||
* @var resource|null the server process handle | ||
*/ | ||
protected $serverProcess; | ||
|
||
/** | ||
* @var resource|null the server stderr handle | ||
*/ | ||
protected $serverStderr; | ||
|
||
/** | ||
* @var int the port on which the server is listening, this default value was randomly chosen | ||
*/ | ||
protected $serverPort = 44884; | ||
|
||
public function startTestServer(): string | ||
{ | ||
if ($this->serverProcess !== null) { | ||
throw new \RuntimeException('There is already a test server instance running.'); | ||
} | ||
|
||
if (file_exists(self::$serverOutputFile)) { | ||
unlink(self::$serverOutputFile); | ||
} | ||
|
||
$pipes = []; | ||
|
||
$this->serverProcess = proc_open( | ||
$command = sprintf( | ||
'php -S localhost:%d -t %s', | ||
$this->serverPort, | ||
realpath(__DIR__ . '/../testserver') | ||
), | ||
[2 => ['pipe', 'w']], | ||
$pipes | ||
); | ||
|
||
$this->serverStderr = $pipes[2]; | ||
|
||
$pid = proc_get_status($this->serverProcess)['pid']; | ||
|
||
if (!\is_resource($this->serverProcess)) { | ||
throw new \RuntimeException("Error starting test server on pid {$pid}, command failed: {$command}"); | ||
} | ||
|
||
while (true) { | ||
$conn = @fsockopen('localhost', $this->serverPort); | ||
if (\is_resource($conn)) { | ||
fclose($conn); | ||
|
||
break; | ||
} | ||
} | ||
|
||
return "localhost:{$this->serverPort}"; | ||
} | ||
|
||
/** | ||
* Stop the test server and return the output from the server. | ||
* | ||
* @return array{ | ||
* body: string, | ||
* status: int, | ||
* server: array<string, string>, | ||
* headers: array<string, string>, | ||
* compressed: bool, | ||
* } | ||
*/ | ||
public function stopTestServer(): array | ||
{ | ||
if (!$this->serverProcess) { | ||
throw new \RuntimeException('There is no test server instance running.'); | ||
} | ||
|
||
for ($i = 0; $i < 20; ++$i) { | ||
$status = proc_get_status($this->serverProcess); | ||
|
||
if (!$status['running']) { | ||
break; | ||
} | ||
|
||
$this->killServerProcess($status['pid']); | ||
|
||
usleep(10000); | ||
} | ||
|
||
if ($status['running']) { | ||
throw new \RuntimeException('Could not kill test server'); | ||
} | ||
|
||
if (!file_exists(self::$serverOutputFile)) { | ||
stream_set_blocking($this->serverStderr, false); | ||
$stderrOutput = stream_get_contents($this->serverStderr); | ||
|
||
echo $stderrOutput . \PHP_EOL; | ||
|
||
throw new \RuntimeException('Test server did not write output file'); | ||
} | ||
|
||
proc_close($this->serverProcess); | ||
|
||
$this->serverProcess = null; | ||
$this->serverStderr = null; | ||
|
||
return json_decode(file_get_contents(self::$serverOutputFile), true); | ||
} | ||
|
||
private function killServerProcess(int $pid): void | ||
{ | ||
if (\PHP_OS_FAMILY === 'Windows') { | ||
exec("taskkill /pid {$pid} /f /t"); | ||
} else { | ||
// Kills any child processes -- the php test server appears to start up a child. | ||
exec("pkill -P {$pid}"); | ||
|
||
// Kill the parent process. | ||
exec("kill {$pid}"); | ||
} | ||
|
||
proc_terminate($this->serverProcess, 9); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
output.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
$outputFile = __DIR__ . '/output.json'; | ||
|
||
// We expect the path to be `/api/<project_id>/envelope/`. | ||
// We use the project ID to determine the status code so we need to extract it from the path | ||
$path = trim(parse_url($_SERVER['REQUEST_URI'], \PHP_URL_PATH), '/'); | ||
|
||
if (!preg_match('/api\/\d+\/envelope/', $path)) { | ||
http_response_code(204); | ||
|
||
return; | ||
} | ||
|
||
$status = (int) explode('/', $path)[1]; | ||
|
||
$headers = getallheaders(); | ||
|
||
$rawBody = file_get_contents('php://input'); | ||
|
||
$compressed = false; | ||
|
||
if (!isset($headers['Content-Encoding'])) { | ||
$body = $rawBody; | ||
} elseif ($headers['Content-Encoding'] === 'gzip') { | ||
$body = gzdecode($rawBody); | ||
$compressed = true; | ||
} else { | ||
$body = '__unable to decode body__'; | ||
} | ||
|
||
$output = [ | ||
'body' => $body, | ||
'status' => $status, | ||
'server' => $_SERVER, | ||
'headers' => $headers, | ||
'compressed' => $compressed, | ||
]; | ||
|
||
file_put_contents(__DIR__ . '/output.json', json_encode($output, \JSON_PRETTY_PRINT)); | ||
|
||
header('X-Sentry-Test-Server-Status-Code: ' . $status); | ||
|
||
http_response_code($status); | ||
|
||
return 'Processed.'; |