send($data); Http::assertSent(function ($request) { return $request->url() === 'https://analytics.mattitja.se/api/send' && $request->method() === 'POST' && $request->data() === [ 'k' => 'testsiteky', 'u' => 'https://example.com/test', 'ip' => '192.168.1.1', 'ua' => 'Mozilla/5.0 Chrome', 'r' => 'https://google.com', ]; }); }); it('handles API failures gracefully', function () { Http::fake([ 'analytics.mattitja.se/*' => Http::response('Server Error', 500), ]); $client = new AnalyticsClient; $data = new PageviewData( siteKey: 'testsiteky', url: 'https://example.com/test', ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', referrer: 'https://google.com', ); expect(fn () => $client->send($data))->not->toThrow(Exception::class); }); it('sends data with correct site key', function () { $client = new AnalyticsClient; $data = new PageviewData( siteKey: 'testsiteky', url: 'https://example.com/test', ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', ); $client->send($data); Http::assertSent(function ($request) { return $request->data()['k'] === 'testsiteky'; }); }); it('skips sending when required fields are empty', function () { $client = new AnalyticsClient; $client->send(new PageviewData( siteKey: '', url: 'https://example.com', ip: '192.168.1.1', userAgent: 'Mozilla/5.0', )); $client->send(new PageviewData( siteKey: 'testsiteky', url: '', ip: '192.168.1.1', userAgent: 'Mozilla/5.0', )); $client->send(new PageviewData( siteKey: 'testsiteky', url: 'https://example.com', ip: '', userAgent: 'Mozilla/5.0', )); Http::assertNothingSent(); }); it('skips sending when URL exceeds 512 characters', function () { $client = new AnalyticsClient; $longUrl = 'https://example.com/'.str_repeat('a', 500); $client->send(new PageviewData( siteKey: 'testsiteky', url: $longUrl, ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', )); Http::assertNothingSent(); }); it('excludes empty referrer from payload', function () { $client = new AnalyticsClient; $data = new PageviewData( siteKey: 'testsiteky', url: 'https://example.com/test', ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', referrer: '', ); $client->send($data); Http::assertSent(function ($request) { return ! isset($request->data()['r']); }); }); it('excludes null referrer from payload', function () { $client = new AnalyticsClient; $data = new PageviewData( siteKey: 'testsiteky', url: 'https://example.com/test', ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', referrer: null, ); $client->send($data); Http::assertSent(function ($request) { return ! isset($request->data()['r']); }); }); it('sends the ingest secret as bearer token and forwards Accept-Language', function () { (new AnalyticsClient)->send(new PageviewData( siteKey: 'testsiteky', url: 'https://example.com/test', ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', acceptLanguage: 'sv-SE,sv;q=0.9', )); Http::assertSent(function ($request) { return $request->hasHeader('Authorization', 'Bearer test-secret') && $request->hasHeader('Accept-Language', 'sv-SE,sv;q=0.9') && ! array_key_exists('acceptLanguage', $request->data()); }); }); it('logs a warning when the API rejects the pageview', function () { Http::swap(new \Illuminate\Http\Client\Factory); Http::fake(['*' => Http::response('', 401)]); \Illuminate\Support\Facades\Log::spy(); (new AnalyticsClient)->send(new PageviewData( siteKey: 'testsiteky', url: 'https://example.com/test', ip: '192.168.1.1', userAgent: 'Mozilla/5.0 Chrome', )); \Illuminate\Support\Facades\Log::shouldHaveReceived('warning') ->withArgs(fn ($message, $context) => str_contains($message, 'rejected') && $context['status'] === 401) ->once(); }); it('pauses sending after a connection failure', function () { Http::swap(new \Illuminate\Http\Client\Factory); $attempts = 0; Http::fake(function () use (&$attempts) { $attempts++; throw new \Illuminate\Http\Client\ConnectionException('timeout'); }); $client = new AnalyticsClient; $client->send(testPageview()); $client->send(testPageview()); expect($client->isPaused())->toBeTrue() ->and($attempts)->toBe(1); }); it('pauses sending after a server error', function () { Http::swap(new \Illuminate\Http\Client\Factory); Http::fake(['*' => Http::response('', 503)]); $client = new AnalyticsClient; $client->send(testPageview()); $client->send(testPageview()); expect($client->isPaused())->toBeTrue(); Http::assertSentCount(1); }); it('does not pause on client errors', function () { Http::swap(new \Illuminate\Http\Client\Factory); Http::fake(['*' => Http::response('', 401)]); $client = new AnalyticsClient; $client->send(testPageview()); expect($client->isPaused())->toBeFalse(); }); it('resumes sending after the pause expires', function () { touch(storage_path('framework/mattitja-analytics.paused'), time() - 120); $client = new AnalyticsClient; $client->send(testPageview()); Http::assertSentCount(1); expect(file_exists(storage_path('framework/mattitja-analytics.paused')))->toBeFalse(); });