Skip to content

Commit cfa4839

Browse files
Laravel: HTTP client test cover (#197)
* Laravel: added WithInstrumentation test helper. * Laravel: added Http ClientRequestWatcher tests. * Laravel: QueryWatcher constructor property promotion. * Laravel: coverage for ClientRequestWatcher. * Laravel: test instrumentation setUp moved to base TestCase. * Laravel: fixed ClientTest after TraceAttribute changes. * Laravel: ClientRequestWatcher reverted to request hashing, as the Request is immutable so may be a different object depending on middleware execution.
1 parent 9fe9fd5 commit cfa4839

File tree

6 files changed

+106
-85
lines changed

6 files changed

+106
-85
lines changed

src/Watchers/ClientRequestWatcher.php

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Illuminate\Http\Client\Events\ConnectionFailed;
99
use Illuminate\Http\Client\Events\RequestSending;
1010
use Illuminate\Http\Client\Events\ResponseReceived;
11+
use Illuminate\Http\Client\Request;
12+
use Illuminate\Http\Client\Response;
1113
use OpenTelemetry\API\Instrumentation\CachedInstrumentation;
1214
use OpenTelemetry\API\Trace\SpanInterface;
1315
use OpenTelemetry\API\Trace\SpanKind;
@@ -17,15 +19,14 @@
1719

1820
class ClientRequestWatcher extends Watcher
1921
{
20-
private CachedInstrumentation $instrumentation;
2122
/**
2223
* @var array<string, SpanInterface>
2324
*/
2425
protected array $spans = [];
2526

26-
public function __construct(CachedInstrumentation $instr)
27-
{
28-
$this->instrumentation = $instr;
27+
public function __construct(
28+
private CachedInstrumentation $instrumentation,
29+
) {
2930
}
3031

3132
/** @psalm-suppress UndefinedInterfaceMethod */
@@ -42,7 +43,7 @@ public function register(Application $app): void
4243
public function recordRequest(RequestSending $request): void
4344
{
4445
$parsedUrl = collect(parse_url($request->request->url()));
45-
$processedUrl = $parsedUrl->get('scheme') . '://' . $parsedUrl->get('host') . $parsedUrl->get('path', '');
46+
$processedUrl = $parsedUrl->get('scheme', 'http') . '://' . $parsedUrl->get('host') . $parsedUrl->get('path', '');
4647

4748
if ($parsedUrl->has('query')) {
4849
$processedUrl .= '?' . $parsedUrl->get('query');
@@ -95,12 +96,13 @@ public function recordResponse(ResponseReceived $request): void
9596

9697
unset($this->spans[$requestHash]);
9798
}
98-
private function createRequestComparisonHash(\Illuminate\Http\Client\Request $request): string
99+
100+
private function createRequestComparisonHash(Request $request): string
99101
{
100102
return sha1($request->method() . '|' . $request->url() . '|' . $request->body());
101103
}
102104

103-
private function maybeRecordError(SpanInterface $span, \Illuminate\Http\Client\Response $response): void
105+
private function maybeRecordError(SpanInterface $span, Response $response): void
104106
{
105107
if ($response->successful()) {
106108
return;

src/Watchers/QueryWatcher.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@
1414

1515
class QueryWatcher extends Watcher
1616
{
17-
private CachedInstrumentation $instrumentation;
18-
19-
public function __construct(CachedInstrumentation $instr)
20-
{
21-
$this->instrumentation = $instr;
17+
public function __construct(
18+
private CachedInstrumentation $instrumentation,
19+
) {
2220
}
2321

2422
/** @psalm-suppress UndefinedInterfaceMethod */

tests/Integration/ConsoleInstrumentationTest.php

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,11 @@
44

55
namespace OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Integration;
66

7-
use ArrayObject;
87
use Illuminate\Console\Command;
98
use Illuminate\Contracts\Console\Kernel;
10-
use OpenTelemetry\API\Instrumentation\Configurator;
11-
use OpenTelemetry\Context\ScopeInterface;
12-
use OpenTelemetry\SDK\Trace\ImmutableSpan;
13-
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
14-
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
15-
use OpenTelemetry\SDK\Trace\TracerProvider;
169

1710
class ConsoleInstrumentationTest extends TestCase
1811
{
19-
private ScopeInterface $scope;
20-
private ArrayObject $storage;
21-
22-
public function setUp(): void
23-
{
24-
parent::setUp();
25-
26-
$this->storage = new ArrayObject();
27-
$tracerProvider = new TracerProvider(
28-
new SimpleSpanProcessor(
29-
new InMemoryExporter($this->storage)
30-
)
31-
);
32-
33-
$this->scope = Configurator::create()
34-
->withTracerProvider($tracerProvider)
35-
->activate();
36-
}
37-
38-
public function tearDown(): void
39-
{
40-
$this->scope->detach();
41-
parent::tearDown();
42-
}
43-
4412
public function test_command_tracing(): void
4513
{
4614
$this->assertCount(0, $this->storage);
@@ -62,11 +30,10 @@ public function test_command_tracing(): void
6230
$count = 8;
6331
$this->assertCount($count, $this->storage);
6432

65-
/** @var ImmutableSpan $span */
66-
$span = $this->storage->offsetGet(--$count);
33+
$span = $this->storage[--$count];
6734
$this->assertSame('Artisan handler', $span->getName());
6835

69-
$span = $this->storage->offsetGet(--$count);
36+
$span = $this->storage[--$count];
7037
$this->assertSame('Command optimize:clear', $span->getName());
7138
}
7239

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Integration\Http;
6+
7+
use GuzzleHttp\Exception\ConnectException;
8+
use GuzzleHttp\Promise\RejectedPromise;
9+
use Illuminate\Http\Client\Request;
10+
use Illuminate\Support\Facades\Http;
11+
use OpenTelemetry\API\Trace\StatusCode;
12+
use OpenTelemetry\SDK\Trace\StatusData;
13+
use OpenTelemetry\SemConv\TraceAttributes;
14+
use OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Integration\TestCase;
15+
16+
class ClientTest extends TestCase
17+
{
18+
/** @test */
19+
public function it_records_requests(): void
20+
{
21+
Http::fake([
22+
'ok.opentelemetry.io/*' => Http::response(status: 201),
23+
'missing.opentelemetry.io' => Http::response(status: 404),
24+
]);
25+
26+
$response = Http::get('missing.opentelemetry.io');
27+
$span = $this->storage[0];
28+
self::assertEquals(404, $response->status());
29+
self::assertEquals('GET', $span->getName());
30+
self::assertEquals('missing.opentelemetry.io', $span->getAttributes()->get(TraceAttributes::URL_PATH));
31+
32+
$response = Http::post('ok.opentelemetry.io/foo?param=bar');
33+
$span = $this->storage[1];
34+
self::assertEquals(201, $response->status());
35+
self::assertEquals('POST', $span->getName());
36+
self::assertEquals('ok.opentelemetry.io/foo', $span->getAttributes()->get(TraceAttributes::URL_PATH));
37+
}
38+
39+
/** @test */
40+
public function it_records_connection_failures(): void
41+
{
42+
Http::fake(fn (Request $request) => new RejectedPromise(new ConnectException('Failure', $request->toPsrRequest())));
43+
44+
try {
45+
Http::patch('/fail');
46+
} catch (\Exception) {
47+
}
48+
49+
$span = $this->storage[0];
50+
self::assertEquals('PATCH', $span->getName());
51+
self::assertEquals('http://fail', $span->getAttributes()->get(TraceAttributes::URL_FULL));
52+
self::assertEquals(StatusData::create(StatusCode::STATUS_ERROR, 'Connection failed'), $span->getStatus());
53+
}
54+
}

tests/Integration/LaravelInstrumentationTest.php

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,14 @@
44

55
namespace OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Integration;
66

7-
use ArrayObject;
87
use Illuminate\Routing\Router;
98
use Illuminate\Support\Facades\DB;
109
use Illuminate\Support\Facades\Http;
1110
use Illuminate\Support\Facades\Log;
12-
use OpenTelemetry\API\Instrumentation\Configurator;
13-
use OpenTelemetry\Context\ScopeInterface;
14-
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
15-
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
16-
use OpenTelemetry\SDK\Trace\TracerProvider;
1711
use OpenTelemetry\SemConv\TraceAttributes;
1812

1913
class LaravelInstrumentationTest extends TestCase
2014
{
21-
private ScopeInterface $scope;
22-
private ArrayObject $storage;
23-
private TracerProvider $tracerProvider;
24-
25-
public function setUp(): void
26-
{
27-
parent::setUp();
28-
29-
$this->storage = new ArrayObject();
30-
$this->tracerProvider = new TracerProvider(
31-
new SimpleSpanProcessor(
32-
new InMemoryExporter($this->storage)
33-
)
34-
);
35-
36-
$this->scope = Configurator::create()
37-
->withTracerProvider($this->tracerProvider)
38-
->activate();
39-
40-
Http::fake();
41-
}
42-
43-
public function tearDown(): void
44-
{
45-
$this->scope->detach();
46-
parent::tearDown();
47-
}
48-
4915
public function test_request_response(): void
5016
{
5117
$this->router()->get('/', fn () => null);
@@ -54,12 +20,12 @@ public function test_request_response(): void
5420
$response = $this->call('GET', '/');
5521
$this->assertEquals(200, $response->status());
5622
$this->assertCount(1, $this->storage);
57-
$span = $this->storage->offsetGet(0);
23+
$span = $this->storage[0];
5824
$this->assertSame('GET', $span->getName());
5925

6026
$response = Http::get('opentelemetry.io');
6127
$this->assertEquals(200, $response->status());
62-
$span = $this->storage->offsetGet(1);
28+
$span = $this->storage[1];
6329
$this->assertSame('GET', $span->getName());
6430
}
6531
public function test_cache_log_db(): void
@@ -80,7 +46,7 @@ public function test_cache_log_db(): void
8046
$response = $this->call('GET', '/hello');
8147
$this->assertEquals(200, $response->status());
8248
$this->assertCount(2, $this->storage);
83-
$span = $this->storage->offsetGet(1);
49+
$span = $this->storage[1];
8450
$this->assertSame('GET', $span->getName());
8551
$this->assertSame('http://localhost/hello', $span->getAttributes()->get(TraceAttributes::URL_FULL));
8652
$this->assertCount(5, $span->getEvents());
@@ -90,7 +56,7 @@ public function test_cache_log_db(): void
9056
$this->assertSame('cache hit', $span->getEvents()[3]->getName());
9157
$this->assertSame('cache forget', $span->getEvents()[4]->getName());
9258

93-
$span = $this->storage->offsetGet(0);
59+
$span = $this->storage[0];
9460
$this->assertSame('sql SELECT', $span->getName());
9561
$this->assertSame('SELECT', $span->getAttributes()->get('db.operation'));
9662
$this->assertSame(':memory:', $span->getAttributes()->get('db.name'));

tests/Integration/TestCase.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,42 @@
44

55
namespace OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Integration;
66

7+
use ArrayObject;
8+
use OpenTelemetry\API\Instrumentation\Configurator;
9+
use OpenTelemetry\Context\ScopeInterface;
10+
use OpenTelemetry\SDK\Trace\ImmutableSpan;
11+
use OpenTelemetry\SDK\Trace\SpanExporter\InMemoryExporter;
12+
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
13+
use OpenTelemetry\SDK\Trace\TracerProvider;
714
use Orchestra\Testbench\TestCase as BaseTestCase;
815

916
abstract class TestCase extends BaseTestCase
1017
{
18+
protected ScopeInterface $scope;
19+
/** @var ArrayObject|ImmutableSpan[] $storage */
20+
protected ArrayObject $storage;
21+
protected TracerProvider $tracerProvider;
22+
23+
public function setUp(): void
24+
{
25+
parent::setUp();
26+
27+
$this->storage = new ArrayObject();
28+
$this->tracerProvider = new TracerProvider(
29+
new SimpleSpanProcessor(
30+
new InMemoryExporter($this->storage),
31+
),
32+
);
33+
34+
$this->scope = Configurator::create()
35+
->withTracerProvider($this->tracerProvider)
36+
->activate();
37+
}
38+
39+
public function tearDown(): void
40+
{
41+
parent::tearDown();
42+
43+
$this->scope->detach();
44+
}
1145
}

0 commit comments

Comments
 (0)